Socket
Socket
Sign inDemoInstall

@endorphinjs/template-runtime

Package Overview
Dependencies
0
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.11 to 0.1.12

1582

dist/runtime.cjs.js

@@ -147,18 +147,153 @@ 'use strict';

/**
* Invokes `fn` for each `name` hook in given definition
* @param {ComponentDefinition} definition
* @param {string} name
* @param {function} fn
* Marks given item as explicitly disposable for given host
* @param {Component | Injector} host
* @param {DisposeCallback} callback
* @return {Component | Injector}
*/
function forEachHook(definition, name, fn) {
const { plugins } = definition;
function addDisposeCallback(host, callback) {
if (/** @type {Component} */ (host).componentModel) {
/** @type {Component} */ (host).componentModel.dispose = callback;
} else {
/** @type {Injector} */ (host).ctx.dispose = callback;
}
if (plugins) {
for (let i = 0; i < plugins.length; i++) {
forEachHook(plugins[i], name, fn);
return host;
}
const blockKey = '&block';
/**
* Creates injector instance for given target, if required
* @param {Element} target
* @returns {Injector}
*/
function createInjector(target) {
return {
parentNode: target,
items: [],
ctx: null,
ptr: 0,
// NB create `slots` placeholder to promote object to hidden class.
// Do not use any additional function argument for adding value to `slots`
// to reduce runtime checks and keep functions in monomorphic state
slots: null,
attributes: changeSet(),
events: changeSet()
};
}
/**
* Creates block for given injector
* @param {Injector} injector
* @returns {Block}
*/
function block(injector) {
return add(injector, {
[blockKey]: true,
inserted: 0,
deleted: 0,
size: 0,
dispose: null
});
}
/**
* Runs `fn` template function in context of given `block`
* @param {Injector} injector
* @param {Block} block
* @param {Function} fn
* @param {Component} component
* @param {*} data
* @returns {*} Result of `fn` function call
*/
function run(injector, block, fn, component, data) {
let result;
const ix = injector.items.indexOf(block);
if (typeof fn === 'function') {
const ctx = injector.ctx;
injector.ptr = ix + 1;
injector.ctx = block;
result = fn(component, injector, data);
injector.ctx = ctx;
ctx ? consume(ctx, block) : reset(block);
}
injector.ptr = ix + block.size + 1;
return result;
}
/**
* Inserts given node into current context
* @param {Injector} injector
* @param {Node} node
* @returns {Node}
*/
function insert(injector, node, slotName = '') {
let target;
const { slots } = injector;
if (slots) {
target = slots[slotName] || (slots[slotName] = document.createDocumentFragment());
} else {
target = injector.parentNode;
}
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target));
return add(injector, node);
}
/**
* Moves contents of given block at `pos` location, effectively updating
* inserted nodes in parent context
* @param {Injector} injector
* @param {Block} block
* @param {number} pos
*/
function move(injector, block, pos) {
const { items } = injector;
if (items[pos] === block) {
return;
}
// Move block contents at given position
const curPos = items.indexOf(block);
const blockItems = items.splice(curPos, block.size + 1);
if (curPos < pos) {
pos -= blockItems.length;
}
for (let i = blockItems.length - 1, item; i >= 0; i--) {
item = /** @type {Element} */ (blockItems[i]);
if (!isBlock(item)) {
domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode));
}
items.splice(pos, 0, item);
}
}
if (name in definition) {
return fn(definition[name], definition);
/**
* Disposes contents of given block
* @param {Injector} injector
* @param {Block} block
* @param {Object} scope
* @param {boolean} self Remove block item as well
*/
function dispose(injector, block, scope, self) {
disposeBlock(block, scope, self);
const { items, ctx } = injector;
const ix = items.indexOf(block) + (self ? 0 : 1);
const size = block.deleted;
if (size) {
ctx && consume(ctx, block);
const removed = items.splice(ix, size);
for (let i = 0; i < removed.length; i++) {
domRemove(removed[i]);
}
}

@@ -168,166 +303,464 @@ }

/**
* Invokes `name` hook for given component definition
* @param {Component} elem
* @param {string} name
* @param {Array} [args]
* Disposes given block
* @param {Block} block
* @param {Object} scope
* @param {boolean} self Dispose block itself
* @returns {void} Should return nothing since function result will be used
* as shorthand to reset cached value
*/
function runHook(elem, name, args) {
const hookArgs = args ? [elem].concat(args) : [elem];
function disposeBlock(block, scope, self) {
if (block.dispose) {
block.dispose(scope);
block.dispose = null;
}
block.deleted += block.size + (self ? 1 : 0);
block.size = 0;
}
return forEachHook(elem.componentModel.definition, name, hook => hook.apply(null, hookArgs));
/**
* Adds given item into current injector position
* @param {Injector} injector
* @param {InjectorItem} item
*/
function add(injector, item) {
injector.items.splice(injector.ptr++, 0, item);
injector.ctx && markInsert(injector.ctx);
return item;
}
/**
* Creates element with given tag name
* @param {string} tagName
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Get DOM node nearest to given position of items list
* @param {InjectorItem[]} items
* @param {number} ix
* @param {Node} parent Ensure element has given element as parent node
* @returns {Node}
*/
function elem(tagName, cssScope) {
const el = document.createElement(tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
function getAnchorNode(items, ix, parent) {
while (ix < items.length) {
const item = /** @type {Node} */ (items[ix++]);
if (item.parentNode === parent) {
return item;
}
}
}
/**
* Creates element with given tag name under `ns` namespace
* @param {string} tagName
* @param {string} ns
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* @param {Block} block
*/
function elemNS(tagName, ns, cssScope) {
const el = document.createElementNS(ns, tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
function markInsert(block) {
block.inserted++;
block.size++;
}
/**
* Creates element with given tag name and text
* @param {string} tagName
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Consumes data from given `child` block by parent `block`
* @param {Block} block
*/
function elemWithText(tagName, text, cssScope) {
const el = elem(tagName, cssScope);
el.textContent = textValue(text);
return el;
function consume(block, child) {
block.inserted += child.inserted;
block.deleted += child.deleted;
block.size += child.inserted - child.deleted;
reset(child);
}
/**
* Creates element with given tag name under `ns` namespace and text
* @param {string} tagName
* @param {string} ns
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Reset session data from given block
* @param {Block} block
*/
function elemNSWithText(tagName, ns, text, cssScope) {
const el = elemNS(tagName, ns, cssScope);
el.textContent = textValue(text);
return el;
function reset(block) {
block.inserted = block.deleted = 0;
}
/**
* Creates text node with given value
* @param {String} value
* @returns {Text}
* Check if given value is a block
* @param {*} obj
* @returns {boolean}
*/
function text(value) {
const node = document.createTextNode(textValue(value));
node['$value'] = value;
return node;
function isBlock(obj$$1) {
return blockKey in obj$$1;
}
/**
* Updates given text node value, if required
* @param {Text} node
* @param {Node} node
* @param {Node} parent
* @param {Node} anchor
* @returns {Node} Inserted item
*/
function domInsert(node, parent, anchor) {
return anchor
? parent.insertBefore(node, anchor)
: parent.appendChild(node);
}
/**
* Removes given DOM node from its tree
* @param {Node} node
*/
function domRemove(node) {
const parent = node.parentNode;
parent && parent.removeChild(node);
}
/**
* Enters new variable scope context
* @param {Component} host
* @param {object} incoming
* @return {Object}
*/
function enterScope(host, incoming) {
return setScope(host, createScope(host, incoming));
}
/**
* Exit from current variable scope
* @param {Component} host
* @returns {Object}
*/
function exitScope(host) {
return setScope(host, Object.getPrototypeOf(host.componentModel.vars));
}
/**
* Creates new scope from given component state
* @param {Component} host
* @param {Object} [incoming]
* @return {Object}
*/
function createScope(host, incoming) {
return assign(obj(host.componentModel.vars), incoming);
}
/**
* Sets given object as current component scope
* @param {Component} host
* @param {Object} scope
*/
function setScope(host, scope) {
return host.componentModel.vars = scope;
}
/**
* Returns current variable scope
* @param {Component} elem
* @returns {object}
*/
function getScope(elem) {
return elem.componentModel.vars;
}
/**
* Returns property with given name from component
* @param {Component} elem
* @param {string} name
* @return {*}
*/
function getProp(elem, name) {
return elem.props[name];
}
/**
* Returns state value with given name from component
* @param {Component} elem
* @param {string} name
* @return {*}
*/
function getState(elem, name) {
return elem.state[name];
}
/**
* Returns value of given runtime variable from component
* @param {Component} elem
* @param {string} name
* @returns {*}
*/
function getVar(elem, name) {
return elem.componentModel.vars[name];
}
/**
* Sets value of given runtime variable for component
* @param {Component} elem
* @param {string} name
* @param {*} value
* @returns {number} Returns `1` if text was updated, `0` otherwise
*/
function updateText(node, value) {
if (value !== node['$value']) {
node.nodeValue = textValue(value);
node['$value'] = value;
return 1;
function setVar(elem, name, value) {
elem.componentModel.vars[name] = value;
}
/**
* Initial block rendering
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @returns {BlockContext}
*/
function mountBlock(host, injector, get) {
/** @type {BlockContext} */
const ctx = {
host,
injector,
block: block(injector),
scope: getScope(host),
get,
fn: undefined,
update: undefined
};
updateBlock(ctx);
return ctx;
}
/**
* Updated block, described in `ctx` object
* @param {BlockContext} ctx
* @returns {number} Returns `1` if block was updated, `0` otherwise
*/
function updateBlock(ctx) {
let updated = 0;
const { host, injector, scope, block: block$$1, update } = ctx;
const fn = ctx.get(host, scope, injector);
if (ctx.fn !== fn) {
updated = 1;
// Unmount previously rendered content
ctx.fn && dispose(injector, block$$1, scope, false);
// Mount new block content
ctx.update = fn ? run(injector, block$$1, fn, host, scope) : null;
ctx.fn = fn;
} else if (update) {
// Update rendered result
updated = run(injector, block$$1, update, host, scope) ? 1 : 0;
}
return 0;
return updated;
}
/**
* Returns textual representation of given `value` object
* @param {*} value
* @returns {string}
* @param {BlockContext} ctx
*/
function textValue(value) {
return value != null ? value : '';
function unmountBlock(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
/**
* Adds pending event `name` handler
* Mounts iterator block
* @param {Component} host
* @param {Injector} injector
* @param {string} name
* @param {function} handler
* @param {Function} get A function that returns collection to iterate
* @param {Function} body A function that renders item of iterated collection
* @returns {IteratorContext}
*/
function addEvent(injector, name, handler) {
injector.events.cur[name] = handler;
function mountIterator(host, injector, get, body) {
/** @type {IteratorContext} */
const ctx = {
host,
injector,
get,
body,
block: block(injector),
scope: getScope(host),
index: 0,
rendered: [],
updated: 0
};
updateIterator(ctx);
return ctx;
}
/**
* Adds given `handler` as event `name` listener
* @param {Element} elem
* @param {string} name
* @param {EventListener} handler
* Updates iterator block defined in `ctx`
* @param {IteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function addStaticEvent(elem, name, handler) {
handler && elem.addEventListener(name, handler);
function updateIterator(ctx) {
run(ctx.injector, ctx.block, iteratorHost, ctx.host, ctx) ? 1 : 0;
return ctx.updated;
}
/**
* Finalizes events of given injector
*
* @param {IteratorContext} ctx
*/
function unmountIterator(ctx) {
const { rendered, injector } = ctx;
let item;
while (item = rendered.pop()) {
dispose(injector, item[0], item[2], true);
}
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @returns {number} Update status
* @param {IteratorContext} ctx
*/
function finalizeEvents(injector) {
return finalizeItems(injector.events, changeEvent, injector.parentNode);
function iteratorHost(host, injector, ctx) {
ctx.index = 0;
ctx.updated = 0;
const collection = ctx.get(host, ctx.scope);
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator, ctx);
}
// Remove remaining blocks
let item;
while (ctx.rendered.length > ctx.index) {
ctx.updated = 1;
item = ctx.rendered.pop();
dispose(injector, item[0], item[2], true);
}
}
/**
* Returns function that must be invoked as event handler for given component
* @param {Component} component
* @param {string} name Method name
* @param {HTMLElement} ctx Context element where event listener was added
* @returns {function?}
* @this {IteratorContext}
* @param {*} value
* @param {*} key
*/
function getEventHandler(component, name, ctx) {
let fn;
function iterator(value, key) {
const { host, injector, rendered, index } = this;
const localScope = { index, key, value };
if (typeof component[name] === 'function') {
fn = component[name].bind(component);
if (index < rendered.length) {
// Update existing block
const [b, update, scope] = rendered[index];
setScope(host, assign(scope, localScope));
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
const handler = component.componentModel.definition[name];
if (typeof handler === 'function') {
fn = handler.bind(ctx);
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, this.body, host, scope);
exitScope(host);
rendered.push([b, update, scope]);
this.updated = 1;
}
this.index++;
}
/**
* Renders key iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @param {Function} keyExpr
* @param {Function} body
* @returns {KeyIteratorContext}
*/
function mountKeyIterator(host, injector, get, keyExpr, body) {
/** @type {KeyIteratorContext} */
const ctx = {
host,
injector,
keyExpr,
body,
get,
rendered: obj(),
block: block(injector),
scope: getScope(host),
index: 0,
updated: 1,
used: null
};
updateKeyIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {KeyIteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateKeyIterator(ctx) {
run(ctx.injector, ctx.block, keyIteratorHost, ctx.host, ctx);
return ctx.updated;
}
/**
* @param {KeyIteratorContext} ctx
*/
function unmountKeyIterator(ctx) {
const { rendered, injector } = ctx;
let items, item;
for (let k in rendered) {
items = rendered[k];
while (item = items.pop()) {
dispose(injector, item[0], item[2], true);
}
}
}
if (fn) {
fn.displayName = name;
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {KeyIteratorContext} ctx
*/
function keyIteratorHost(host, injector, ctx) {
ctx.used = obj();
ctx.index = 0;
ctx.updated = 1;
const collection = ctx.get(host, ctx.scope);
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator$1, ctx);
}
return fn;
// Remove remaining blocks
for (let k in ctx.rendered) {
for (let i = 0, items = ctx.rendered[k]; i < items.length; i++) {
ctx.updated = 1;
dispose(injector, items[i][0], items[i][2], true);
}
}
ctx.rendered = ctx.used;
}
/**
* Invoked when event handler was changed
* @param {string} name
* @param {EventListener} prevValue
* @param {EventListener} newValue
* @param {Element} elem
* @this {KeyIteratorContext}
* @param {*} value
* @param {*} key
*/
function changeEvent(name, prevValue, newValue, elem) {
prevValue && elem.removeEventListener(name, prevValue);
addStaticEvent(elem, name, newValue);
function iterator$1(value, key) {
const { host, injector, index, used, rendered, keyExpr, body } = this;
const localScope = { index, key, value };
const id = keyExpr(value, createScope(host, localScope));
let entry = id in rendered && rendered[id].shift();
if (entry) {
// Update existing block
const [b, update, scope] = entry;
setScope(host, assign(scope, localScope));
move(injector, b, injector.ptr);
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, body, host, scope);
this.updated = 1;
exitScope(host);
entry = [b, update, scope];
}
// Mark block as used.
// We allow multiple items key in case of poorly prepared data.
if (id in used) {
used[id].push(entry);
} else {
used[id] = [entry];
}
this.index++;
}

@@ -454,86 +887,98 @@

/**
* Enters new variable scope context
* @param {Component} host
* @param {object} incoming
* @return {Object}
* Adds pending event `name` handler
* @param {Injector} injector
* @param {string} name
* @param {function} handler
*/
function enterScope(host, incoming) {
return setScope(host, createScope(host, incoming));
function addEvent(injector, name, handler) {
injector.events.cur[name] = handler;
}
/**
* Exit from current variable scope
* @param {Component} host
* @returns {Object}
* Adds given `handler` as event `name` listener
* @param {Element} elem
* @param {string} name
* @param {EventListener} handler
*/
function exitScope(host) {
return setScope(host, Object.getPrototypeOf(host.componentModel.vars));
function addStaticEvent(elem, name, handler) {
handler && elem.addEventListener(name, handler);
}
/**
* Creates new scope from given component state
* @param {Component} host
* @param {Object} [incoming]
* @return {Object}
* Finalizes events of given injector
* @param {Injector} injector
* @returns {number} Update status
*/
function createScope(host, incoming) {
return assign(obj(host.componentModel.vars), incoming);
function finalizeEvents(injector) {
return finalizeItems(injector.events, changeEvent, injector.parentNode);
}
/**
* Sets given object as current component scope
* @param {Component} host
* @param {Object} scope
* Returns function that must be invoked as event handler for given component
* @param {Component} component
* @param {string} name Method name
* @param {HTMLElement} ctx Context element where event listener was added
* @returns {function?}
*/
function setScope(host, scope) {
return host.componentModel.vars = scope;
}
function getEventHandler(component, name, ctx) {
let fn;
/**
* Returns current variable scope
* @param {Component} elem
* @returns {object}
*/
function getScope(elem) {
return elem.componentModel.vars;
if (typeof component[name] === 'function') {
fn = component[name].bind(component);
} else {
const handler = component.componentModel.definition[name];
if (typeof handler === 'function') {
fn = handler.bind(ctx);
}
}
if (fn) {
fn.displayName = name;
}
return fn;
}
/**
* Returns property with given name from component
* @param {Component} elem
* Invoked when event handler was changed
* @param {string} name
* @return {*}
* @param {EventListener} prevValue
* @param {EventListener} newValue
* @param {Element} elem
*/
function getProp(elem, name) {
return elem.props[name];
function changeEvent(name, prevValue, newValue, elem) {
prevValue && elem.removeEventListener(name, prevValue);
addStaticEvent(elem, name, newValue);
}
/**
* Returns state value with given name from component
* @param {Component} elem
* Invokes `fn` for each `name` hook in given definition
* @param {ComponentDefinition} definition
* @param {string} name
* @return {*}
* @param {function} fn
*/
function getState(elem, name) {
return elem.state[name];
function forEachHook(definition, name, fn) {
const { plugins } = definition;
if (plugins) {
for (let i = 0; i < plugins.length; i++) {
forEachHook(plugins[i], name, fn);
}
}
if (name in definition) {
return fn(definition[name], definition);
}
}
/**
* Returns value of given runtime variable from component
* Invokes `name` hook for given component definition
* @param {Component} elem
* @param {string} name
* @returns {*}
* @param {Array} [args]
*/
function getVar(elem, name) {
return elem.componentModel.vars[name];
}
function runHook(elem, name, args) {
const hookArgs = args ? [elem].concat(args) : [elem];
/**
* Sets value of given runtime variable for component
* @param {Component} elem
* @param {string} name
* @param {*} value
*/
function setVar(elem, name, value) {
elem.componentModel.vars[name] = value;
return forEachHook(elem.componentModel.definition, name, hook => hook.apply(null, hookArgs));
}

@@ -547,15 +992,41 @@

* @param {Function} [defaultContent] Function for rendering default slot content
* @return {SlotContext}
*/
function mountSlot(host, name, elem, defaultContent) {
const blockEntry = (host, scope, injector) => {
if (!renderSlot(host, injector)) {
return defaultContent;
}
};
/** @type {SlotContext} */
const ctx = { host, name, defaultContent, isDefault: false };
const { slots } = host.componentModel;
const { slots } = host.componentModel;
/**
* @param {Component} host
* @param {Object} scope
* @param {Injector} injector
*/
function blockEntry(host, scope, injector) {
ctx.isDefault = !renderSlot(host, injector);
return ctx.isDefault ? ctx.defaultContent : null;
}
slots[name] = mountBlock(host, createInjector(elem), blockEntry);
return ctx;
}
/**
* Unmounts given slot
* @param {SlotContext} ctx
*/
function unmountSlot(ctx) {
const { host, name } = ctx;
const { slots } = host.componentModel;
if (ctx.isDefault) {
unmountBlock(slots[name]);
}
ctx.defaultContent = null;
delete slots[name];
}
/**
* Sync slot content if necessary

@@ -628,2 +1099,145 @@ * @param {Component} host

/**
* Sets runtime ref (e.g. ref which will be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {HTMLElement} elem
* @returns {number} Update status. Refs must be explicitly finalized, thus
* we always return `0` as nothing was changed
*/
function setRef(host, name, elem) {
host.componentModel.refs.cur[name] = elem;
return 0;
}
/**
* Sets static ref (e.g. ref which won’t be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {Element} value
*/
function setStaticRef(host, name, value) {
value && value.setAttribute(getRefAttr(name, host), '');
host.refs[name] = value;
}
/**
* Finalizes refs on given scope
* @param {Component} host
* @returns {number} Update status
*/
function finalizeRefs(host) {
return finalizeItems(host.componentModel.refs, changeRef, host);
}
/**
* Invoked when element reference was changed
* @param {string} name
* @param {Element} prevValue
* @param {Element} newValue
* @param {Component} host
*/
function changeRef(name, prevValue, newValue, host) {
prevValue && prevValue.removeAttribute(getRefAttr(name, host));
setStaticRef(host, name, newValue);
}
/**
* Returns attribute name to identify element in CSS
* @param {String} name
* @param {Component} host
*/
function getRefAttr(name, host) {
const cssScope$$1 = host.componentModel.definition.cssScope;
return 'ref-' + name + (cssScope$$1 ? '-' + cssScope$$1 : '');
}
/**
* Creates element with given tag name
* @param {string} tagName
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elem(tagName, cssScope) {
const el = document.createElement(tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
}
/**
* Creates element with given tag name under `ns` namespace
* @param {string} tagName
* @param {string} ns
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemNS(tagName, ns, cssScope) {
const el = document.createElementNS(ns, tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
}
/**
* Creates element with given tag name and text
* @param {string} tagName
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemWithText(tagName, text, cssScope) {
const el = elem(tagName, cssScope);
el.textContent = textValue(text);
return el;
}
/**
* Creates element with given tag name under `ns` namespace and text
* @param {string} tagName
* @param {string} ns
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemNSWithText(tagName, ns, text, cssScope) {
const el = elemNS(tagName, ns, cssScope);
el.textContent = textValue(text);
return el;
}
/**
* Creates text node with given value
* @param {String} value
* @returns {Text}
*/
function text(value) {
const node = document.createTextNode(textValue(value));
node['$value'] = value;
return node;
}
/**
* Updates given text node value, if required
* @param {Text} node
* @param {*} value
* @returns {number} Returns `1` if text was updated, `0` otherwise
*/
function updateText(node, value) {
if (value !== node['$value']) {
node.nodeValue = textValue(value);
node['$value'] = value;
return 1;
}
return 0;
}
/**
* Returns textual representation of given `value` object
* @param {*} value
* @returns {string}
*/
function textValue(value) {
return value != null ? value : '';
}
/**
* Creates internal lightweight Endorphin component with given definition

@@ -715,2 +1329,3 @@ * @param {string} name

events: attachStaticEvents(element, definition),
dispose: null,
defaultProps

@@ -785,5 +1400,12 @@ };

* @param {Component} elem
* @returns {void} Should return nothing since function result will be used
* as shorthand to reset cached value
*/
function unmountComponent(elem$$1) {
const { componentModel } = elem$$1;
const { slots, input, dispose: dispose$$1 } = componentModel;
const scope = getScope(elem$$1);
runHook(elem$$1, 'willUnmount');
componentModel.mounted = false;

@@ -798,3 +1420,3 @@ detachStaticEvents(elem$$1, componentModel.events);

// XXX doesn’t remove static events (via direct call of `addStaticEvent()`)
const ownHandlers = componentModel.input.events.prev;
const ownHandlers = input.events.prev;
for (let p in ownHandlers) {

@@ -804,2 +1426,8 @@ elem$$1.removeEventListener(p, ownHandlers[p]);

dispose$$1 && dispose$$1(scope);
for (const slotName in slots) {
disposeBlock(slots[slotName].block, scope, true);
}
runHook(elem$$1, 'didUnmount');

@@ -812,7 +1440,7 @@ elem$$1.componentModel = null;

* @param {Component} component
* @param {string[]} keys
* @param {string[]} [keys]
*/
function subscribeStore(component, keys) {
if (!component.store) {
throw new Error(`Store is not defined in ${component.nodeName} component definition`);
throw new Error(`Store is not defined for ${component.nodeName} component`);
}

@@ -1014,570 +1642,3 @@

const blockKey = '&block';
/**
* Creates injector instance for given target, if required
* @param {Element} target
* @returns {Injector}
*/
function createInjector(target) {
return {
parentNode: target,
items: [],
ctx: null,
ptr: 0,
// NB create `slots` placeholder to promote object to hidden class.
// Do not use any additional function argument for adding value to `slots`
// to reduce runtime checks and keep functions in monomorphic state
slots: null,
attributes: changeSet(),
events: changeSet()
};
}
/**
* Creates block for given injector
* @param {Injector} injector
* @returns {Block}
*/
function block(injector) {
return add(injector, {
[blockKey]: true,
inserted: 0,
deleted: 0,
size: 0
});
}
/**
* Runs `fn` template function in context of given `block`
* @param {Injector} injector
* @param {Block} block
* @param {Function} fn
* @param {Component} component
* @param {*} data
* @returns {*} Result of `fn` function call
*/
function run(injector, block, fn, component, data) {
let result;
const ix = injector.items.indexOf(block);
if (typeof fn === 'function') {
const ctx = injector.ctx;
injector.ptr = ix + 1;
injector.ctx = block;
result = fn(component, injector, data);
injector.ctx = ctx;
ctx ? consume(ctx, block) : reset(block);
}
injector.ptr = ix + block.size + 1;
return result;
}
/**
* Inserts given node into current context
* @param {Injector} injector
* @param {Node} node
* @returns {Node}
*/
function insert(injector, node, slotName = '') {
let target;
const { slots } = injector;
if (slots) {
target = slots[slotName] || (slots[slotName] = document.createDocumentFragment());
} else {
target = injector.parentNode;
}
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target));
return add(injector, node);
}
/**
* Moves contents of given block at `pos` location, effectively updating
* inserted nodes in parent context
* @param {Injector} injector
* @param {Block} block
* @param {number} pos
*/
function move(injector, block, pos) {
const { items } = injector;
if (items[pos] === block) {
return;
}
// Move block contents at given position
const curPos = items.indexOf(block);
const blockItems = items.splice(curPos, block.size + 1);
if (curPos < pos) {
pos -= blockItems.length;
}
for (let i = blockItems.length - 1, item; i >= 0; i--) {
item = /** @type {Element} */ (blockItems[i]);
if (!isBlock(item)) {
domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode));
}
items.splice(pos, 0, item);
}
}
/**
* Disposes contents of given block
* @param {Injector} injector
* @param {Block} block
* @param {boolean} self Remove block item as well
*/
function dispose(injector, block, self) {
markDisposed(block, self);
const { items, ctx } = injector;
const ix = items.indexOf(block) + (self ? 0 : 1);
const size = block.deleted;
if (size) {
ctx && consume(ctx, block);
const removed = items.splice(ix, size);
for (let i = 0, item; i < removed.length; i++) {
item = removed[i];
if (!isBlock(item)) {
disposeElement(/** @type {Node} */ (item));
}
}
}
}
/**
* Adds given item into current injector position
* @param {Injector} injector
* @param {InjectorItem} item
*/
function add(injector, item) {
injector.items.splice(injector.ptr++, 0, item);
injector.ctx && markInsert(injector.ctx);
return item;
}
/**
* Get DOM node nearest to given position of items list
* @param {InjectorItem[]} items
* @param {number} ix
* @param {Node} parent Ensure element has given element as parent node
* @returns {Node}
*/
function getAnchorNode(items, ix, parent) {
while (ix < items.length) {
const item = /** @type {Node} */ (items[ix++]);
if (item.parentNode === parent) {
return item;
}
}
}
/**
* @param {Block} block
*/
function markInsert(block) {
block.inserted++;
block.size++;
}
/**
* Marks current block content as disposed
* @param {Block} block
* @param {boolean} self Marks block itself as removed
*/
function markDisposed(block, self) {
block.deleted += block.size + (self ? 1 : 0);
block.size = 0;
}
/**
* Consumes data from given `child` block by parent `block`
* @param {Block} block
*/
function consume(block, child) {
block.inserted += child.inserted;
block.deleted += child.deleted;
block.size += child.inserted - child.deleted;
reset(child);
}
/**
* Reset session data from given block
* @param {Block} block
*/
function reset(block) {
block.inserted = block.deleted = 0;
}
/**
* Check if given value is a block
* @param {*} obj
* @returns {boolean}
*/
function isBlock(obj$$1) {
return blockKey in obj$$1;
}
/**
* Disposes given element or component: notifies all descending components
* with proper lifecycle hooks and detaches given element from DOM
* @param {Node | Component} elem
*/
function disposeElement(elem) {
const components = collectComponents(elem, []);
for (let i = 0; i < components.length; i++) {
runHook(components[i], 'willUnmount');
}
domRemove(elem);
for (let i = 0; i < components.length; i++) {
unmountComponent(components[i]);
}
}
/**
* Collects all nested components from given node (including node itself)
* @param {Node | Component} node
* @param {Array} to
* @returns {Component[]}
*/
function collectComponents(node, to) {
if (/** @type {Component} */ (node).componentModel) {
to.push(node);
}
let child = /** @type {Node} */ (node.firstChild);
while (child) {
collectComponents(child, to);
child = child.nextSibling;
}
return to;
}
/**
* @param {Node} node
* @param {Node} parent
* @param {Node} anchor
* @returns {Node} Inserted item
*/
function domInsert(node, parent, anchor) {
return anchor
? parent.insertBefore(node, anchor)
: parent.appendChild(node);
}
/**
* Removes given DOM node from its tree
* @param {Node} node
*/
function domRemove(node) {
const parent = node.parentNode;
parent && parent.removeChild(node);
}
/**
* Initial block rendering
* @param {Component} component
* @param {Injector} injector
* @param {Function} get
* @returns {BlockContext}
*/
function mountBlock(component, injector, get) {
/** @type {BlockContext} */
const ctx = {
component,
injector,
block: block(injector),
get,
fn: null,
update: null
};
updateBlock(ctx);
return ctx;
}
/**
* Updated block, described in `ctx` object
* @param {BlockContext} ctx
* @returns {number} Returns `1` if block was updated, `0` otherwise
*/
function updateBlock(ctx) {
let updated = 0;
const { component, injector, block: block$$1, update } = ctx;
const scope = getScope(component);
const fn = ctx.get(component, scope, injector);
if (ctx.fn !== fn) {
updated = 1;
// Unmount previously rendered content
ctx.fn && dispose(injector, block$$1, false);
// Mount new block content
ctx.update = fn ? run(injector, block$$1, fn, component, scope) : null;
ctx.fn = fn;
} else if (update) {
// Update rendered result
updated = run(injector, block$$1, update, component, scope) ? 1 : 0;
}
return updated;
}
/**
* Mounts iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get A function that returns collection to iterate
* @param {Function} body A function that renders item of iterated collection
* @returns {IteratorContext}
*/
function mountIterator(host, injector, get, body) {
/** @type {IteratorContext} */
const ctx = {
host,
injector,
get,
body,
block: block(injector),
index: 0,
rendered: [],
updated: 0
};
updateIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {IteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateIterator(ctx) {
run(ctx.injector, ctx.block, iteratorHost, ctx.host, ctx) ? 1 : 0;
return ctx.updated;
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {IteratorContext} ctx
*/
function iteratorHost(host, injector, ctx) {
ctx.index = 0;
ctx.updated = 0;
const collection = ctx.get(host, getScope(host));
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator, ctx);
}
// Remove remaining blocks
while (ctx.rendered.length > ctx.index) {
ctx.updated = 1;
dispose(injector, ctx.rendered.pop()[0], true);
}
}
/**
* @this {IteratorContext}
* @param {*} value
* @param {*} key
*/
function iterator(value, key) {
const { host, injector, rendered, index } = this;
const localScope = { index, key, value };
if (index < rendered.length) {
// Update existing block
const [b, update, scope] = rendered[index];
setScope(host, assign(scope, localScope));
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, this.body, host, scope);
exitScope(host);
rendered.push([b, update, scope]);
this.updated = 1;
}
this.index++;
}
/**
* Renders key iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @param {Function} keyExpr
* @param {Function} body
* @returns {KeyIteratorContext}
*/
function mountKeyIterator(host, injector, get, keyExpr, body) {
/** @type {KeyIteratorContext} */
const ctx = {
host,
injector,
keyExpr,
body,
get,
rendered: obj(),
block: block(injector),
index: 0,
updated: 1,
used: null
};
updateKeyIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {KeyIteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateKeyIterator(ctx) {
run(ctx.injector, ctx.block, keyIteratorHost, ctx.host, ctx);
return ctx.updated;
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {KeyIteratorContext} ctx
*/
function keyIteratorHost(host, injector, ctx) {
ctx.used = obj();
ctx.index = 0;
ctx.updated = 1;
const collection = ctx.get(host, getScope(host));
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator$1, ctx);
}
// Remove remaining blocks
for (let k in ctx.rendered) {
for (let i = 0, items = ctx.rendered[k]; i < items.length; i++) {
ctx.updated = 1;
dispose(injector, items[i][0], true);
}
}
ctx.rendered = ctx.used;
}
/**
* @this {KeyIteratorContext}
* @param {*} value
* @param {*} key
*/
function iterator$1(value, key) {
const { host, injector, index, used, rendered, keyExpr, body } = this;
const localScope = { index, key, value };
const id = keyExpr(value, createScope(host, localScope));
let entry = id in rendered && rendered[id].shift();
if (entry) {
// Update existing block
const [b, update, scope] = entry;
setScope(host, assign(scope, localScope));
move(injector, b, injector.ptr);
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, body, host, scope);
this.updated = 1;
exitScope(host);
entry = [b, update, scope];
}
// Mark block as used.
// We allow multiple items key in case of poorly prepared data.
if (id in used) {
used[id].push(entry);
} else {
used[id] = [entry];
}
this.index++;
}
/**
* Sets runtime ref (e.g. ref which will be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {HTMLElement} elem
* @returns {number} Update status. Refs must be explicitly finalized, thus
* we always return `0` as nothing was changed
*/
function setRef(host, name, elem) {
host.componentModel.refs.cur[name] = elem;
return 0;
}
/**
* Sets static ref (e.g. ref which won’t be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {Element} value
*/
function setStaticRef(host, name, value) {
value && value.setAttribute(getRefAttr(name, host), '');
host.refs[name] = value;
}
/**
* Finalizes refs on given scope
* @param {Component} host
* @returns {number} Update status
*/
function finalizeRefs(host) {
return finalizeItems(host.componentModel.refs, changeRef, host);
}
/**
* Invoked when element reference was changed
* @param {string} name
* @param {Element} prevValue
* @param {Element} newValue
* @param {Component} host
*/
function changeRef(name, prevValue, newValue, host) {
prevValue && prevValue.removeAttribute(getRefAttr(name, host));
setStaticRef(host, name, newValue);
}
/**
* Returns attribute name to identify element in CSS
* @param {String} name
* @param {Component} host
*/
function getRefAttr(name, host) {
const cssScope$$1 = host.componentModel.definition.cssScope;
return 'ref-' + name + (cssScope$$1 ? '-' + cssScope$$1 : '');
}
/**
* Renders code, returned from `get` function, as HTML

@@ -1596,2 +1657,3 @@ * @param {Component} host

block: block(injector),
scope: getScope(host),
get,

@@ -1611,3 +1673,3 @@ code: null,

function updateInnerHTML(ctx) {
const { host, injector, block: block$$1 } = ctx;
const { host, injector, block: block$$1, scope } = ctx;
const code = ctx.get(host, injector);

@@ -1619,3 +1681,3 @@ let updated = 0;

ctx.code = code;
dispose(injector, block$$1, false);
dispose(injector, block$$1, scope, false);
isDefined(code) && run(injector, block$$1, renderHTML, host, ctx);

@@ -1628,2 +1690,9 @@ }

/**
* @param {InnerHtmlContext} ctx
*/
function unmountInnerHTML(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
/**
* @param {Component} host

@@ -1668,4 +1737,2 @@ * @param {Injector} injector

function mountPartial(host, injector, partial, args) {
// NB freeze scope context so all partial runtime objects can be reused
// across renders
/** @type {PartialContext} */

@@ -1676,2 +1743,3 @@ const ctx = {

block: block(injector),
baseScope: getScope(host),
scope: null,

@@ -1693,3 +1761,3 @@ update: null,

function updatePartial(ctx, partial, args) {
const { host, injector, block: block$$1 } = ctx;
const { host, injector, block: block$$1, baseScope } = ctx;
let updated = 0;

@@ -1699,6 +1767,7 @@

// Unmount previously rendered partial
ctx.partial && dispose(injector, block$$1, false);
ctx.partial && dispose(injector, block$$1, ctx.scope, false);
// Mount new partial
const scope = ctx.scope = enterScope(host, assign(obj(partial.defaults), args));
const scope = ctx.scope = assign(obj(baseScope), partial.defaults, args);
setScope(host, scope);
ctx.update = partial ? run(injector, block$$1, partial.body, host, scope) : null;

@@ -1720,2 +1789,9 @@ ctx.partial = partial;

/**
* @param {PartialContext} ctx
*/
function unmountPartial(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
const prefix = '$';

@@ -1890,8 +1966,12 @@

exports.filter = filter;
exports.addDisposeCallback = addDisposeCallback;
exports.mountBlock = mountBlock;
exports.updateBlock = updateBlock;
exports.unmountBlock = unmountBlock;
exports.mountIterator = mountIterator;
exports.updateIterator = updateIterator;
exports.unmountIterator = unmountIterator;
exports.mountKeyIterator = mountKeyIterator;
exports.updateKeyIterator = updateKeyIterator;
exports.unmountKeyIterator = unmountKeyIterator;
exports.createInjector = createInjector;

@@ -1903,2 +1983,3 @@ exports.block = block;

exports.dispose = dispose;
exports.disposeBlock = disposeBlock;
exports.enterScope = enterScope;

@@ -1924,2 +2005,3 @@ exports.exitScope = exitScope;

exports.mountSlot = mountSlot;
exports.unmountSlot = unmountSlot;
exports.updateSlots = updateSlots;

@@ -1939,2 +2021,3 @@ exports.markSlotUpdate = markSlotUpdate;

exports.updateInnerHTML = updateInnerHTML;
exports.unmountInnerHTML = unmountInnerHTML;
exports.elem = elem;

@@ -1948,3 +2031,4 @@ exports.elemNS = elemNS;

exports.updatePartial = updatePartial;
exports.unmountPartial = unmountPartial;
exports.Store = Store;
//# sourceMappingURL=runtime.cjs.js.map

@@ -143,18 +143,153 @@ /**

/**
* Invokes `fn` for each `name` hook in given definition
* @param {ComponentDefinition} definition
* @param {string} name
* @param {function} fn
* Marks given item as explicitly disposable for given host
* @param {Component | Injector} host
* @param {DisposeCallback} callback
* @return {Component | Injector}
*/
function forEachHook(definition, name, fn) {
const { plugins } = definition;
function addDisposeCallback(host, callback) {
if (/** @type {Component} */ (host).componentModel) {
/** @type {Component} */ (host).componentModel.dispose = callback;
} else {
/** @type {Injector} */ (host).ctx.dispose = callback;
}
if (plugins) {
for (let i = 0; i < plugins.length; i++) {
forEachHook(plugins[i], name, fn);
return host;
}
const blockKey = '&block';
/**
* Creates injector instance for given target, if required
* @param {Element} target
* @returns {Injector}
*/
function createInjector(target) {
return {
parentNode: target,
items: [],
ctx: null,
ptr: 0,
// NB create `slots` placeholder to promote object to hidden class.
// Do not use any additional function argument for adding value to `slots`
// to reduce runtime checks and keep functions in monomorphic state
slots: null,
attributes: changeSet(),
events: changeSet()
};
}
/**
* Creates block for given injector
* @param {Injector} injector
* @returns {Block}
*/
function block(injector) {
return add(injector, {
[blockKey]: true,
inserted: 0,
deleted: 0,
size: 0,
dispose: null
});
}
/**
* Runs `fn` template function in context of given `block`
* @param {Injector} injector
* @param {Block} block
* @param {Function} fn
* @param {Component} component
* @param {*} data
* @returns {*} Result of `fn` function call
*/
function run(injector, block, fn, component, data) {
let result;
const ix = injector.items.indexOf(block);
if (typeof fn === 'function') {
const ctx = injector.ctx;
injector.ptr = ix + 1;
injector.ctx = block;
result = fn(component, injector, data);
injector.ctx = ctx;
ctx ? consume(ctx, block) : reset(block);
}
injector.ptr = ix + block.size + 1;
return result;
}
/**
* Inserts given node into current context
* @param {Injector} injector
* @param {Node} node
* @returns {Node}
*/
function insert(injector, node, slotName = '') {
let target;
const { slots } = injector;
if (slots) {
target = slots[slotName] || (slots[slotName] = document.createDocumentFragment());
} else {
target = injector.parentNode;
}
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target));
return add(injector, node);
}
/**
* Moves contents of given block at `pos` location, effectively updating
* inserted nodes in parent context
* @param {Injector} injector
* @param {Block} block
* @param {number} pos
*/
function move(injector, block, pos) {
const { items } = injector;
if (items[pos] === block) {
return;
}
// Move block contents at given position
const curPos = items.indexOf(block);
const blockItems = items.splice(curPos, block.size + 1);
if (curPos < pos) {
pos -= blockItems.length;
}
for (let i = blockItems.length - 1, item; i >= 0; i--) {
item = /** @type {Element} */ (blockItems[i]);
if (!isBlock(item)) {
domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode));
}
items.splice(pos, 0, item);
}
}
if (name in definition) {
return fn(definition[name], definition);
/**
* Disposes contents of given block
* @param {Injector} injector
* @param {Block} block
* @param {Object} scope
* @param {boolean} self Remove block item as well
*/
function dispose(injector, block, scope, self) {
disposeBlock(block, scope, self);
const { items, ctx } = injector;
const ix = items.indexOf(block) + (self ? 0 : 1);
const size = block.deleted;
if (size) {
ctx && consume(ctx, block);
const removed = items.splice(ix, size);
for (let i = 0; i < removed.length; i++) {
domRemove(removed[i]);
}
}

@@ -164,166 +299,464 @@ }

/**
* Invokes `name` hook for given component definition
* @param {Component} elem
* @param {string} name
* @param {Array} [args]
* Disposes given block
* @param {Block} block
* @param {Object} scope
* @param {boolean} self Dispose block itself
* @returns {void} Should return nothing since function result will be used
* as shorthand to reset cached value
*/
function runHook(elem, name, args) {
const hookArgs = args ? [elem].concat(args) : [elem];
function disposeBlock(block, scope, self) {
if (block.dispose) {
block.dispose(scope);
block.dispose = null;
}
block.deleted += block.size + (self ? 1 : 0);
block.size = 0;
}
return forEachHook(elem.componentModel.definition, name, hook => hook.apply(null, hookArgs));
/**
* Adds given item into current injector position
* @param {Injector} injector
* @param {InjectorItem} item
*/
function add(injector, item) {
injector.items.splice(injector.ptr++, 0, item);
injector.ctx && markInsert(injector.ctx);
return item;
}
/**
* Creates element with given tag name
* @param {string} tagName
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Get DOM node nearest to given position of items list
* @param {InjectorItem[]} items
* @param {number} ix
* @param {Node} parent Ensure element has given element as parent node
* @returns {Node}
*/
function elem(tagName, cssScope) {
const el = document.createElement(tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
function getAnchorNode(items, ix, parent) {
while (ix < items.length) {
const item = /** @type {Node} */ (items[ix++]);
if (item.parentNode === parent) {
return item;
}
}
}
/**
* Creates element with given tag name under `ns` namespace
* @param {string} tagName
* @param {string} ns
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* @param {Block} block
*/
function elemNS(tagName, ns, cssScope) {
const el = document.createElementNS(ns, tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
function markInsert(block) {
block.inserted++;
block.size++;
}
/**
* Creates element with given tag name and text
* @param {string} tagName
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Consumes data from given `child` block by parent `block`
* @param {Block} block
*/
function elemWithText(tagName, text, cssScope) {
const el = elem(tagName, cssScope);
el.textContent = textValue(text);
return el;
function consume(block, child) {
block.inserted += child.inserted;
block.deleted += child.deleted;
block.size += child.inserted - child.deleted;
reset(child);
}
/**
* Creates element with given tag name under `ns` namespace and text
* @param {string} tagName
* @param {string} ns
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
* Reset session data from given block
* @param {Block} block
*/
function elemNSWithText(tagName, ns, text, cssScope) {
const el = elemNS(tagName, ns, cssScope);
el.textContent = textValue(text);
return el;
function reset(block) {
block.inserted = block.deleted = 0;
}
/**
* Creates text node with given value
* @param {String} value
* @returns {Text}
* Check if given value is a block
* @param {*} obj
* @returns {boolean}
*/
function text(value) {
const node = document.createTextNode(textValue(value));
node['$value'] = value;
return node;
function isBlock(obj$$1) {
return blockKey in obj$$1;
}
/**
* Updates given text node value, if required
* @param {Text} node
* @param {Node} node
* @param {Node} parent
* @param {Node} anchor
* @returns {Node} Inserted item
*/
function domInsert(node, parent, anchor) {
return anchor
? parent.insertBefore(node, anchor)
: parent.appendChild(node);
}
/**
* Removes given DOM node from its tree
* @param {Node} node
*/
function domRemove(node) {
const parent = node.parentNode;
parent && parent.removeChild(node);
}
/**
* Enters new variable scope context
* @param {Component} host
* @param {object} incoming
* @return {Object}
*/
function enterScope(host, incoming) {
return setScope(host, createScope(host, incoming));
}
/**
* Exit from current variable scope
* @param {Component} host
* @returns {Object}
*/
function exitScope(host) {
return setScope(host, Object.getPrototypeOf(host.componentModel.vars));
}
/**
* Creates new scope from given component state
* @param {Component} host
* @param {Object} [incoming]
* @return {Object}
*/
function createScope(host, incoming) {
return assign(obj(host.componentModel.vars), incoming);
}
/**
* Sets given object as current component scope
* @param {Component} host
* @param {Object} scope
*/
function setScope(host, scope) {
return host.componentModel.vars = scope;
}
/**
* Returns current variable scope
* @param {Component} elem
* @returns {object}
*/
function getScope(elem) {
return elem.componentModel.vars;
}
/**
* Returns property with given name from component
* @param {Component} elem
* @param {string} name
* @return {*}
*/
function getProp(elem, name) {
return elem.props[name];
}
/**
* Returns state value with given name from component
* @param {Component} elem
* @param {string} name
* @return {*}
*/
function getState(elem, name) {
return elem.state[name];
}
/**
* Returns value of given runtime variable from component
* @param {Component} elem
* @param {string} name
* @returns {*}
*/
function getVar(elem, name) {
return elem.componentModel.vars[name];
}
/**
* Sets value of given runtime variable for component
* @param {Component} elem
* @param {string} name
* @param {*} value
* @returns {number} Returns `1` if text was updated, `0` otherwise
*/
function updateText(node, value) {
if (value !== node['$value']) {
node.nodeValue = textValue(value);
node['$value'] = value;
return 1;
function setVar(elem, name, value) {
elem.componentModel.vars[name] = value;
}
/**
* Initial block rendering
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @returns {BlockContext}
*/
function mountBlock(host, injector, get) {
/** @type {BlockContext} */
const ctx = {
host,
injector,
block: block(injector),
scope: getScope(host),
get,
fn: undefined,
update: undefined
};
updateBlock(ctx);
return ctx;
}
/**
* Updated block, described in `ctx` object
* @param {BlockContext} ctx
* @returns {number} Returns `1` if block was updated, `0` otherwise
*/
function updateBlock(ctx) {
let updated = 0;
const { host, injector, scope, block: block$$1, update } = ctx;
const fn = ctx.get(host, scope, injector);
if (ctx.fn !== fn) {
updated = 1;
// Unmount previously rendered content
ctx.fn && dispose(injector, block$$1, scope, false);
// Mount new block content
ctx.update = fn ? run(injector, block$$1, fn, host, scope) : null;
ctx.fn = fn;
} else if (update) {
// Update rendered result
updated = run(injector, block$$1, update, host, scope) ? 1 : 0;
}
return 0;
return updated;
}
/**
* Returns textual representation of given `value` object
* @param {*} value
* @returns {string}
* @param {BlockContext} ctx
*/
function textValue(value) {
return value != null ? value : '';
function unmountBlock(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
/**
* Adds pending event `name` handler
* Mounts iterator block
* @param {Component} host
* @param {Injector} injector
* @param {string} name
* @param {function} handler
* @param {Function} get A function that returns collection to iterate
* @param {Function} body A function that renders item of iterated collection
* @returns {IteratorContext}
*/
function addEvent(injector, name, handler) {
injector.events.cur[name] = handler;
function mountIterator(host, injector, get, body) {
/** @type {IteratorContext} */
const ctx = {
host,
injector,
get,
body,
block: block(injector),
scope: getScope(host),
index: 0,
rendered: [],
updated: 0
};
updateIterator(ctx);
return ctx;
}
/**
* Adds given `handler` as event `name` listener
* @param {Element} elem
* @param {string} name
* @param {EventListener} handler
* Updates iterator block defined in `ctx`
* @param {IteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function addStaticEvent(elem, name, handler) {
handler && elem.addEventListener(name, handler);
function updateIterator(ctx) {
run(ctx.injector, ctx.block, iteratorHost, ctx.host, ctx) ? 1 : 0;
return ctx.updated;
}
/**
* Finalizes events of given injector
*
* @param {IteratorContext} ctx
*/
function unmountIterator(ctx) {
const { rendered, injector } = ctx;
let item;
while (item = rendered.pop()) {
dispose(injector, item[0], item[2], true);
}
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @returns {number} Update status
* @param {IteratorContext} ctx
*/
function finalizeEvents(injector) {
return finalizeItems(injector.events, changeEvent, injector.parentNode);
function iteratorHost(host, injector, ctx) {
ctx.index = 0;
ctx.updated = 0;
const collection = ctx.get(host, ctx.scope);
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator, ctx);
}
// Remove remaining blocks
let item;
while (ctx.rendered.length > ctx.index) {
ctx.updated = 1;
item = ctx.rendered.pop();
dispose(injector, item[0], item[2], true);
}
}
/**
* Returns function that must be invoked as event handler for given component
* @param {Component} component
* @param {string} name Method name
* @param {HTMLElement} ctx Context element where event listener was added
* @returns {function?}
* @this {IteratorContext}
* @param {*} value
* @param {*} key
*/
function getEventHandler(component, name, ctx) {
let fn;
function iterator(value, key) {
const { host, injector, rendered, index } = this;
const localScope = { index, key, value };
if (typeof component[name] === 'function') {
fn = component[name].bind(component);
if (index < rendered.length) {
// Update existing block
const [b, update, scope] = rendered[index];
setScope(host, assign(scope, localScope));
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
const handler = component.componentModel.definition[name];
if (typeof handler === 'function') {
fn = handler.bind(ctx);
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, this.body, host, scope);
exitScope(host);
rendered.push([b, update, scope]);
this.updated = 1;
}
this.index++;
}
/**
* Renders key iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @param {Function} keyExpr
* @param {Function} body
* @returns {KeyIteratorContext}
*/
function mountKeyIterator(host, injector, get, keyExpr, body) {
/** @type {KeyIteratorContext} */
const ctx = {
host,
injector,
keyExpr,
body,
get,
rendered: obj(),
block: block(injector),
scope: getScope(host),
index: 0,
updated: 1,
used: null
};
updateKeyIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {KeyIteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateKeyIterator(ctx) {
run(ctx.injector, ctx.block, keyIteratorHost, ctx.host, ctx);
return ctx.updated;
}
/**
* @param {KeyIteratorContext} ctx
*/
function unmountKeyIterator(ctx) {
const { rendered, injector } = ctx;
let items, item;
for (let k in rendered) {
items = rendered[k];
while (item = items.pop()) {
dispose(injector, item[0], item[2], true);
}
}
}
if (fn) {
fn.displayName = name;
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {KeyIteratorContext} ctx
*/
function keyIteratorHost(host, injector, ctx) {
ctx.used = obj();
ctx.index = 0;
ctx.updated = 1;
const collection = ctx.get(host, ctx.scope);
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator$1, ctx);
}
return fn;
// Remove remaining blocks
for (let k in ctx.rendered) {
for (let i = 0, items = ctx.rendered[k]; i < items.length; i++) {
ctx.updated = 1;
dispose(injector, items[i][0], items[i][2], true);
}
}
ctx.rendered = ctx.used;
}
/**
* Invoked when event handler was changed
* @param {string} name
* @param {EventListener} prevValue
* @param {EventListener} newValue
* @param {Element} elem
* @this {KeyIteratorContext}
* @param {*} value
* @param {*} key
*/
function changeEvent(name, prevValue, newValue, elem) {
prevValue && elem.removeEventListener(name, prevValue);
addStaticEvent(elem, name, newValue);
function iterator$1(value, key) {
const { host, injector, index, used, rendered, keyExpr, body } = this;
const localScope = { index, key, value };
const id = keyExpr(value, createScope(host, localScope));
let entry = id in rendered && rendered[id].shift();
if (entry) {
// Update existing block
const [b, update, scope] = entry;
setScope(host, assign(scope, localScope));
move(injector, b, injector.ptr);
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, body, host, scope);
this.updated = 1;
exitScope(host);
entry = [b, update, scope];
}
// Mark block as used.
// We allow multiple items key in case of poorly prepared data.
if (id in used) {
used[id].push(entry);
} else {
used[id] = [entry];
}
this.index++;
}

@@ -450,86 +883,98 @@

/**
* Enters new variable scope context
* @param {Component} host
* @param {object} incoming
* @return {Object}
* Adds pending event `name` handler
* @param {Injector} injector
* @param {string} name
* @param {function} handler
*/
function enterScope(host, incoming) {
return setScope(host, createScope(host, incoming));
function addEvent(injector, name, handler) {
injector.events.cur[name] = handler;
}
/**
* Exit from current variable scope
* @param {Component} host
* @returns {Object}
* Adds given `handler` as event `name` listener
* @param {Element} elem
* @param {string} name
* @param {EventListener} handler
*/
function exitScope(host) {
return setScope(host, Object.getPrototypeOf(host.componentModel.vars));
function addStaticEvent(elem, name, handler) {
handler && elem.addEventListener(name, handler);
}
/**
* Creates new scope from given component state
* @param {Component} host
* @param {Object} [incoming]
* @return {Object}
* Finalizes events of given injector
* @param {Injector} injector
* @returns {number} Update status
*/
function createScope(host, incoming) {
return assign(obj(host.componentModel.vars), incoming);
function finalizeEvents(injector) {
return finalizeItems(injector.events, changeEvent, injector.parentNode);
}
/**
* Sets given object as current component scope
* @param {Component} host
* @param {Object} scope
* Returns function that must be invoked as event handler for given component
* @param {Component} component
* @param {string} name Method name
* @param {HTMLElement} ctx Context element where event listener was added
* @returns {function?}
*/
function setScope(host, scope) {
return host.componentModel.vars = scope;
}
function getEventHandler(component, name, ctx) {
let fn;
/**
* Returns current variable scope
* @param {Component} elem
* @returns {object}
*/
function getScope(elem) {
return elem.componentModel.vars;
if (typeof component[name] === 'function') {
fn = component[name].bind(component);
} else {
const handler = component.componentModel.definition[name];
if (typeof handler === 'function') {
fn = handler.bind(ctx);
}
}
if (fn) {
fn.displayName = name;
}
return fn;
}
/**
* Returns property with given name from component
* @param {Component} elem
* Invoked when event handler was changed
* @param {string} name
* @return {*}
* @param {EventListener} prevValue
* @param {EventListener} newValue
* @param {Element} elem
*/
function getProp(elem, name) {
return elem.props[name];
function changeEvent(name, prevValue, newValue, elem) {
prevValue && elem.removeEventListener(name, prevValue);
addStaticEvent(elem, name, newValue);
}
/**
* Returns state value with given name from component
* @param {Component} elem
* Invokes `fn` for each `name` hook in given definition
* @param {ComponentDefinition} definition
* @param {string} name
* @return {*}
* @param {function} fn
*/
function getState(elem, name) {
return elem.state[name];
function forEachHook(definition, name, fn) {
const { plugins } = definition;
if (plugins) {
for (let i = 0; i < plugins.length; i++) {
forEachHook(plugins[i], name, fn);
}
}
if (name in definition) {
return fn(definition[name], definition);
}
}
/**
* Returns value of given runtime variable from component
* Invokes `name` hook for given component definition
* @param {Component} elem
* @param {string} name
* @returns {*}
* @param {Array} [args]
*/
function getVar(elem, name) {
return elem.componentModel.vars[name];
}
function runHook(elem, name, args) {
const hookArgs = args ? [elem].concat(args) : [elem];
/**
* Sets value of given runtime variable for component
* @param {Component} elem
* @param {string} name
* @param {*} value
*/
function setVar(elem, name, value) {
elem.componentModel.vars[name] = value;
return forEachHook(elem.componentModel.definition, name, hook => hook.apply(null, hookArgs));
}

@@ -543,15 +988,41 @@

* @param {Function} [defaultContent] Function for rendering default slot content
* @return {SlotContext}
*/
function mountSlot(host, name, elem, defaultContent) {
const blockEntry = (host, scope, injector) => {
if (!renderSlot(host, injector)) {
return defaultContent;
}
};
/** @type {SlotContext} */
const ctx = { host, name, defaultContent, isDefault: false };
const { slots } = host.componentModel;
const { slots } = host.componentModel;
/**
* @param {Component} host
* @param {Object} scope
* @param {Injector} injector
*/
function blockEntry(host, scope, injector) {
ctx.isDefault = !renderSlot(host, injector);
return ctx.isDefault ? ctx.defaultContent : null;
}
slots[name] = mountBlock(host, createInjector(elem), blockEntry);
return ctx;
}
/**
* Unmounts given slot
* @param {SlotContext} ctx
*/
function unmountSlot(ctx) {
const { host, name } = ctx;
const { slots } = host.componentModel;
if (ctx.isDefault) {
unmountBlock(slots[name]);
}
ctx.defaultContent = null;
delete slots[name];
}
/**
* Sync slot content if necessary

@@ -624,2 +1095,145 @@ * @param {Component} host

/**
* Sets runtime ref (e.g. ref which will be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {HTMLElement} elem
* @returns {number} Update status. Refs must be explicitly finalized, thus
* we always return `0` as nothing was changed
*/
function setRef(host, name, elem) {
host.componentModel.refs.cur[name] = elem;
return 0;
}
/**
* Sets static ref (e.g. ref which won’t be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {Element} value
*/
function setStaticRef(host, name, value) {
value && value.setAttribute(getRefAttr(name, host), '');
host.refs[name] = value;
}
/**
* Finalizes refs on given scope
* @param {Component} host
* @returns {number} Update status
*/
function finalizeRefs(host) {
return finalizeItems(host.componentModel.refs, changeRef, host);
}
/**
* Invoked when element reference was changed
* @param {string} name
* @param {Element} prevValue
* @param {Element} newValue
* @param {Component} host
*/
function changeRef(name, prevValue, newValue, host) {
prevValue && prevValue.removeAttribute(getRefAttr(name, host));
setStaticRef(host, name, newValue);
}
/**
* Returns attribute name to identify element in CSS
* @param {String} name
* @param {Component} host
*/
function getRefAttr(name, host) {
const cssScope$$1 = host.componentModel.definition.cssScope;
return 'ref-' + name + (cssScope$$1 ? '-' + cssScope$$1 : '');
}
/**
* Creates element with given tag name
* @param {string} tagName
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elem(tagName, cssScope) {
const el = document.createElement(tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
}
/**
* Creates element with given tag name under `ns` namespace
* @param {string} tagName
* @param {string} ns
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemNS(tagName, ns, cssScope) {
const el = document.createElementNS(ns, tagName);
cssScope && el.setAttribute(cssScope, '');
return el;
}
/**
* Creates element with given tag name and text
* @param {string} tagName
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemWithText(tagName, text, cssScope) {
const el = elem(tagName, cssScope);
el.textContent = textValue(text);
return el;
}
/**
* Creates element with given tag name under `ns` namespace and text
* @param {string} tagName
* @param {string} ns
* @param {string} text
* @param {string} [cssScope] Scope for CSS isolation
* @return {Element}
*/
function elemNSWithText(tagName, ns, text, cssScope) {
const el = elemNS(tagName, ns, cssScope);
el.textContent = textValue(text);
return el;
}
/**
* Creates text node with given value
* @param {String} value
* @returns {Text}
*/
function text(value) {
const node = document.createTextNode(textValue(value));
node['$value'] = value;
return node;
}
/**
* Updates given text node value, if required
* @param {Text} node
* @param {*} value
* @returns {number} Returns `1` if text was updated, `0` otherwise
*/
function updateText(node, value) {
if (value !== node['$value']) {
node.nodeValue = textValue(value);
node['$value'] = value;
return 1;
}
return 0;
}
/**
* Returns textual representation of given `value` object
* @param {*} value
* @returns {string}
*/
function textValue(value) {
return value != null ? value : '';
}
/**
* Creates internal lightweight Endorphin component with given definition

@@ -711,2 +1325,3 @@ * @param {string} name

events: attachStaticEvents(element, definition),
dispose: null,
defaultProps

@@ -781,5 +1396,12 @@ };

* @param {Component} elem
* @returns {void} Should return nothing since function result will be used
* as shorthand to reset cached value
*/
function unmountComponent(elem$$1) {
const { componentModel } = elem$$1;
const { slots, input, dispose: dispose$$1 } = componentModel;
const scope = getScope(elem$$1);
runHook(elem$$1, 'willUnmount');
componentModel.mounted = false;

@@ -794,3 +1416,3 @@ detachStaticEvents(elem$$1, componentModel.events);

// XXX doesn’t remove static events (via direct call of `addStaticEvent()`)
const ownHandlers = componentModel.input.events.prev;
const ownHandlers = input.events.prev;
for (let p in ownHandlers) {

@@ -800,2 +1422,8 @@ elem$$1.removeEventListener(p, ownHandlers[p]);

dispose$$1 && dispose$$1(scope);
for (const slotName in slots) {
disposeBlock(slots[slotName].block, scope, true);
}
runHook(elem$$1, 'didUnmount');

@@ -808,7 +1436,7 @@ elem$$1.componentModel = null;

* @param {Component} component
* @param {string[]} keys
* @param {string[]} [keys]
*/
function subscribeStore(component, keys) {
if (!component.store) {
throw new Error(`Store is not defined in ${component.nodeName} component definition`);
throw new Error(`Store is not defined for ${component.nodeName} component`);
}

@@ -1010,570 +1638,3 @@

const blockKey = '&block';
/**
* Creates injector instance for given target, if required
* @param {Element} target
* @returns {Injector}
*/
function createInjector(target) {
return {
parentNode: target,
items: [],
ctx: null,
ptr: 0,
// NB create `slots` placeholder to promote object to hidden class.
// Do not use any additional function argument for adding value to `slots`
// to reduce runtime checks and keep functions in monomorphic state
slots: null,
attributes: changeSet(),
events: changeSet()
};
}
/**
* Creates block for given injector
* @param {Injector} injector
* @returns {Block}
*/
function block(injector) {
return add(injector, {
[blockKey]: true,
inserted: 0,
deleted: 0,
size: 0
});
}
/**
* Runs `fn` template function in context of given `block`
* @param {Injector} injector
* @param {Block} block
* @param {Function} fn
* @param {Component} component
* @param {*} data
* @returns {*} Result of `fn` function call
*/
function run(injector, block, fn, component, data) {
let result;
const ix = injector.items.indexOf(block);
if (typeof fn === 'function') {
const ctx = injector.ctx;
injector.ptr = ix + 1;
injector.ctx = block;
result = fn(component, injector, data);
injector.ctx = ctx;
ctx ? consume(ctx, block) : reset(block);
}
injector.ptr = ix + block.size + 1;
return result;
}
/**
* Inserts given node into current context
* @param {Injector} injector
* @param {Node} node
* @returns {Node}
*/
function insert(injector, node, slotName = '') {
let target;
const { slots } = injector;
if (slots) {
target = slots[slotName] || (slots[slotName] = document.createDocumentFragment());
} else {
target = injector.parentNode;
}
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target));
return add(injector, node);
}
/**
* Moves contents of given block at `pos` location, effectively updating
* inserted nodes in parent context
* @param {Injector} injector
* @param {Block} block
* @param {number} pos
*/
function move(injector, block, pos) {
const { items } = injector;
if (items[pos] === block) {
return;
}
// Move block contents at given position
const curPos = items.indexOf(block);
const blockItems = items.splice(curPos, block.size + 1);
if (curPos < pos) {
pos -= blockItems.length;
}
for (let i = blockItems.length - 1, item; i >= 0; i--) {
item = /** @type {Element} */ (blockItems[i]);
if (!isBlock(item)) {
domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode));
}
items.splice(pos, 0, item);
}
}
/**
* Disposes contents of given block
* @param {Injector} injector
* @param {Block} block
* @param {boolean} self Remove block item as well
*/
function dispose(injector, block, self) {
markDisposed(block, self);
const { items, ctx } = injector;
const ix = items.indexOf(block) + (self ? 0 : 1);
const size = block.deleted;
if (size) {
ctx && consume(ctx, block);
const removed = items.splice(ix, size);
for (let i = 0, item; i < removed.length; i++) {
item = removed[i];
if (!isBlock(item)) {
disposeElement(/** @type {Node} */ (item));
}
}
}
}
/**
* Adds given item into current injector position
* @param {Injector} injector
* @param {InjectorItem} item
*/
function add(injector, item) {
injector.items.splice(injector.ptr++, 0, item);
injector.ctx && markInsert(injector.ctx);
return item;
}
/**
* Get DOM node nearest to given position of items list
* @param {InjectorItem[]} items
* @param {number} ix
* @param {Node} parent Ensure element has given element as parent node
* @returns {Node}
*/
function getAnchorNode(items, ix, parent) {
while (ix < items.length) {
const item = /** @type {Node} */ (items[ix++]);
if (item.parentNode === parent) {
return item;
}
}
}
/**
* @param {Block} block
*/
function markInsert(block) {
block.inserted++;
block.size++;
}
/**
* Marks current block content as disposed
* @param {Block} block
* @param {boolean} self Marks block itself as removed
*/
function markDisposed(block, self) {
block.deleted += block.size + (self ? 1 : 0);
block.size = 0;
}
/**
* Consumes data from given `child` block by parent `block`
* @param {Block} block
*/
function consume(block, child) {
block.inserted += child.inserted;
block.deleted += child.deleted;
block.size += child.inserted - child.deleted;
reset(child);
}
/**
* Reset session data from given block
* @param {Block} block
*/
function reset(block) {
block.inserted = block.deleted = 0;
}
/**
* Check if given value is a block
* @param {*} obj
* @returns {boolean}
*/
function isBlock(obj$$1) {
return blockKey in obj$$1;
}
/**
* Disposes given element or component: notifies all descending components
* with proper lifecycle hooks and detaches given element from DOM
* @param {Node | Component} elem
*/
function disposeElement(elem) {
const components = collectComponents(elem, []);
for (let i = 0; i < components.length; i++) {
runHook(components[i], 'willUnmount');
}
domRemove(elem);
for (let i = 0; i < components.length; i++) {
unmountComponent(components[i]);
}
}
/**
* Collects all nested components from given node (including node itself)
* @param {Node | Component} node
* @param {Array} to
* @returns {Component[]}
*/
function collectComponents(node, to) {
if (/** @type {Component} */ (node).componentModel) {
to.push(node);
}
let child = /** @type {Node} */ (node.firstChild);
while (child) {
collectComponents(child, to);
child = child.nextSibling;
}
return to;
}
/**
* @param {Node} node
* @param {Node} parent
* @param {Node} anchor
* @returns {Node} Inserted item
*/
function domInsert(node, parent, anchor) {
return anchor
? parent.insertBefore(node, anchor)
: parent.appendChild(node);
}
/**
* Removes given DOM node from its tree
* @param {Node} node
*/
function domRemove(node) {
const parent = node.parentNode;
parent && parent.removeChild(node);
}
/**
* Initial block rendering
* @param {Component} component
* @param {Injector} injector
* @param {Function} get
* @returns {BlockContext}
*/
function mountBlock(component, injector, get) {
/** @type {BlockContext} */
const ctx = {
component,
injector,
block: block(injector),
get,
fn: null,
update: null
};
updateBlock(ctx);
return ctx;
}
/**
* Updated block, described in `ctx` object
* @param {BlockContext} ctx
* @returns {number} Returns `1` if block was updated, `0` otherwise
*/
function updateBlock(ctx) {
let updated = 0;
const { component, injector, block: block$$1, update } = ctx;
const scope = getScope(component);
const fn = ctx.get(component, scope, injector);
if (ctx.fn !== fn) {
updated = 1;
// Unmount previously rendered content
ctx.fn && dispose(injector, block$$1, false);
// Mount new block content
ctx.update = fn ? run(injector, block$$1, fn, component, scope) : null;
ctx.fn = fn;
} else if (update) {
// Update rendered result
updated = run(injector, block$$1, update, component, scope) ? 1 : 0;
}
return updated;
}
/**
* Mounts iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get A function that returns collection to iterate
* @param {Function} body A function that renders item of iterated collection
* @returns {IteratorContext}
*/
function mountIterator(host, injector, get, body) {
/** @type {IteratorContext} */
const ctx = {
host,
injector,
get,
body,
block: block(injector),
index: 0,
rendered: [],
updated: 0
};
updateIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {IteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateIterator(ctx) {
run(ctx.injector, ctx.block, iteratorHost, ctx.host, ctx) ? 1 : 0;
return ctx.updated;
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {IteratorContext} ctx
*/
function iteratorHost(host, injector, ctx) {
ctx.index = 0;
ctx.updated = 0;
const collection = ctx.get(host, getScope(host));
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator, ctx);
}
// Remove remaining blocks
while (ctx.rendered.length > ctx.index) {
ctx.updated = 1;
dispose(injector, ctx.rendered.pop()[0], true);
}
}
/**
* @this {IteratorContext}
* @param {*} value
* @param {*} key
*/
function iterator(value, key) {
const { host, injector, rendered, index } = this;
const localScope = { index, key, value };
if (index < rendered.length) {
// Update existing block
const [b, update, scope] = rendered[index];
setScope(host, assign(scope, localScope));
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, this.body, host, scope);
exitScope(host);
rendered.push([b, update, scope]);
this.updated = 1;
}
this.index++;
}
/**
* Renders key iterator block
* @param {Component} host
* @param {Injector} injector
* @param {Function} get
* @param {Function} keyExpr
* @param {Function} body
* @returns {KeyIteratorContext}
*/
function mountKeyIterator(host, injector, get, keyExpr, body) {
/** @type {KeyIteratorContext} */
const ctx = {
host,
injector,
keyExpr,
body,
get,
rendered: obj(),
block: block(injector),
index: 0,
updated: 1,
used: null
};
updateKeyIterator(ctx);
return ctx;
}
/**
* Updates iterator block defined in `ctx`
* @param {KeyIteratorContext} ctx
* @returns {number} Returns `1` if iterator was updated, `0` otherwise
*/
function updateKeyIterator(ctx) {
run(ctx.injector, ctx.block, keyIteratorHost, ctx.host, ctx);
return ctx.updated;
}
/**
*
* @param {Component} host
* @param {Injector} injector
* @param {KeyIteratorContext} ctx
*/
function keyIteratorHost(host, injector, ctx) {
ctx.used = obj();
ctx.index = 0;
ctx.updated = 1;
const collection = ctx.get(host, getScope(host));
if (collection && typeof collection.forEach === 'function') {
collection.forEach(iterator$1, ctx);
}
// Remove remaining blocks
for (let k in ctx.rendered) {
for (let i = 0, items = ctx.rendered[k]; i < items.length; i++) {
ctx.updated = 1;
dispose(injector, items[i][0], true);
}
}
ctx.rendered = ctx.used;
}
/**
* @this {KeyIteratorContext}
* @param {*} value
* @param {*} key
*/
function iterator$1(value, key) {
const { host, injector, index, used, rendered, keyExpr, body } = this;
const localScope = { index, key, value };
const id = keyExpr(value, createScope(host, localScope));
let entry = id in rendered && rendered[id].shift();
if (entry) {
// Update existing block
const [b, update, scope] = entry;
setScope(host, assign(scope, localScope));
move(injector, b, injector.ptr);
if (run(injector, b, update, host, scope)) {
this.updated = 1;
}
exitScope(host);
} else {
// Create & render new block
const b = block(injector);
const scope = enterScope(host, localScope);
const update = run(injector, b, body, host, scope);
this.updated = 1;
exitScope(host);
entry = [b, update, scope];
}
// Mark block as used.
// We allow multiple items key in case of poorly prepared data.
if (id in used) {
used[id].push(entry);
} else {
used[id] = [entry];
}
this.index++;
}
/**
* Sets runtime ref (e.g. ref which will be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {HTMLElement} elem
* @returns {number} Update status. Refs must be explicitly finalized, thus
* we always return `0` as nothing was changed
*/
function setRef(host, name, elem) {
host.componentModel.refs.cur[name] = elem;
return 0;
}
/**
* Sets static ref (e.g. ref which won’t be changed over time) to given host
* @param {Component} host
* @param {string} name
* @param {Element} value
*/
function setStaticRef(host, name, value) {
value && value.setAttribute(getRefAttr(name, host), '');
host.refs[name] = value;
}
/**
* Finalizes refs on given scope
* @param {Component} host
* @returns {number} Update status
*/
function finalizeRefs(host) {
return finalizeItems(host.componentModel.refs, changeRef, host);
}
/**
* Invoked when element reference was changed
* @param {string} name
* @param {Element} prevValue
* @param {Element} newValue
* @param {Component} host
*/
function changeRef(name, prevValue, newValue, host) {
prevValue && prevValue.removeAttribute(getRefAttr(name, host));
setStaticRef(host, name, newValue);
}
/**
* Returns attribute name to identify element in CSS
* @param {String} name
* @param {Component} host
*/
function getRefAttr(name, host) {
const cssScope$$1 = host.componentModel.definition.cssScope;
return 'ref-' + name + (cssScope$$1 ? '-' + cssScope$$1 : '');
}
/**
* Renders code, returned from `get` function, as HTML

@@ -1592,2 +1653,3 @@ * @param {Component} host

block: block(injector),
scope: getScope(host),
get,

@@ -1607,3 +1669,3 @@ code: null,

function updateInnerHTML(ctx) {
const { host, injector, block: block$$1 } = ctx;
const { host, injector, block: block$$1, scope } = ctx;
const code = ctx.get(host, injector);

@@ -1615,3 +1677,3 @@ let updated = 0;

ctx.code = code;
dispose(injector, block$$1, false);
dispose(injector, block$$1, scope, false);
isDefined(code) && run(injector, block$$1, renderHTML, host, ctx);

@@ -1624,2 +1686,9 @@ }

/**
* @param {InnerHtmlContext} ctx
*/
function unmountInnerHTML(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
/**
* @param {Component} host

@@ -1664,4 +1733,2 @@ * @param {Injector} injector

function mountPartial(host, injector, partial, args) {
// NB freeze scope context so all partial runtime objects can be reused
// across renders
/** @type {PartialContext} */

@@ -1672,2 +1739,3 @@ const ctx = {

block: block(injector),
baseScope: getScope(host),
scope: null,

@@ -1689,3 +1757,3 @@ update: null,

function updatePartial(ctx, partial, args) {
const { host, injector, block: block$$1 } = ctx;
const { host, injector, block: block$$1, baseScope } = ctx;
let updated = 0;

@@ -1695,6 +1763,7 @@

// Unmount previously rendered partial
ctx.partial && dispose(injector, block$$1, false);
ctx.partial && dispose(injector, block$$1, ctx.scope, false);
// Mount new partial
const scope = ctx.scope = enterScope(host, assign(obj(partial.defaults), args));
const scope = ctx.scope = assign(obj(baseScope), partial.defaults, args);
setScope(host, scope);
ctx.update = partial ? run(injector, block$$1, partial.body, host, scope) : null;

@@ -1716,2 +1785,9 @@ ctx.partial = partial;

/**
* @param {PartialContext} ctx
*/
function unmountPartial(ctx) {
dispose(ctx.injector, ctx.block, ctx.scope, true);
}
const prefix = '$';

@@ -1884,3 +1960,3 @@

export { get, filter, mountBlock, updateBlock, mountIterator, updateIterator, mountKeyIterator, updateKeyIterator, createInjector, block, run, insert, move, dispose, enterScope, exitScope, createScope, setScope, getScope, getProp, getState, getVar, setVar, setAttribute, updateAttribute, updateProps, addClass, finalizeAttributes, normalizeClassName, addEvent, addStaticEvent, finalizeEvents, getEventHandler, mountSlot, updateSlots, markSlotUpdate, setRef, setStaticRef, finalizeRefs, createComponent, mountComponent, updateComponent, unmountComponent, subscribeStore, scheduleRender, renderComponent, mountInnerHTML, updateInnerHTML, elem, elemNS, elemWithText, elemNSWithText, text, updateText, mountPartial, updatePartial, Store };
export { get, filter, addDisposeCallback, mountBlock, updateBlock, unmountBlock, mountIterator, updateIterator, unmountIterator, mountKeyIterator, updateKeyIterator, unmountKeyIterator, createInjector, block, run, insert, move, dispose, disposeBlock, enterScope, exitScope, createScope, setScope, getScope, getProp, getState, getVar, setVar, setAttribute, updateAttribute, updateProps, addClass, finalizeAttributes, normalizeClassName, addEvent, addStaticEvent, finalizeEvents, getEventHandler, mountSlot, unmountSlot, updateSlots, markSlotUpdate, setRef, setStaticRef, finalizeRefs, createComponent, mountComponent, updateComponent, unmountComponent, subscribeStore, scheduleRender, renderComponent, mountInnerHTML, updateInnerHTML, unmountInnerHTML, elem, elemNS, elemWithText, elemNSWithText, text, updateText, mountPartial, updatePartial, unmountPartial, Store };
//# sourceMappingURL=runtime.es.js.map
{
"name": "@endorphinjs/template-runtime",
"version": "0.1.11",
"version": "0.1.12",
"description": "EndorphinJS template runtime, embedded with template bundles",

@@ -5,0 +5,0 @@ "main": "./dist/runtime.cjs.js",

@@ -7,2 +7,6 @@ import { Store } from './lib/store';

interface DisposeCallback {
(scope: object): void
}
interface Component extends Element {

@@ -136,2 +140,7 @@ /**

defaultProps: object;
/**
* A function for disposing component contents
*/
dispose?: DisposeCallback
}

@@ -322,2 +331,5 @@

size: number;
/** A function to dispose block contents */
dispose?: DisposeCallback;
}

@@ -352,6 +364,10 @@

interface BlockContext {
component: Component;
interface BaseContext {
host: Component;
injector: Injector;
block: Block,
block: Block;
scope: Object;
}
interface BlockContext extends BaseContext {
get: Function;

@@ -362,8 +378,5 @@ fn?: Function,

interface IteratorContext {
host: Component;
injector: Injector;
interface IteratorContext extends BaseContext {
get: Function;
body: Function;
block: Block;
index: number;

@@ -384,6 +397,10 @@ updated: number;

interface InnerHtmlContext {
interface SlotContext {
host: Component;
injector: Injector;
block: Block;
name: string;
isDefault: boolean;
defaultContent: Function;
}
interface InnerHtmlContext extends BaseContext {
get: Function;

@@ -394,9 +411,6 @@ code?: string;

interface PartialContext {
host: Component;
injector: Injector;
block: Block,
update?: Function,
scope?: object,
partial?: object
interface PartialContext extends BaseContext {
baseScope?: Object;
update?: Function;
partial?: Object;
}

@@ -403,0 +417,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc