@endorphinjs/template-runtime
Advanced tools
Comparing version 0.1.6 to 0.1.7
@@ -48,3 +48,3 @@ 'use strict'; | ||
* Creates object for storing change sets, e.g. current and previous values | ||
* @returns {object} | ||
* @returns {ChangeSet} | ||
*/ | ||
@@ -60,5 +60,5 @@ function changeSet() { | ||
* @param {Object} prev | ||
* @return {Object} | ||
* @return {Changes} | ||
*/ | ||
function changed(next, prev) { | ||
function changed(next, prev, prefix = '') { | ||
const result = obj(); | ||
@@ -71,3 +71,6 @@ let dirty = false; | ||
dirty = true; | ||
result[p] = prev[p]; | ||
result[prefix ? prefix + p : p] = { | ||
prev: prev[p], | ||
next: next[p] | ||
}; | ||
} | ||
@@ -102,3 +105,4 @@ } | ||
* Queues given `fn` function to be invoked asynchronously as soon as possible | ||
* @param {function} fn | ||
* @param {(value: void) => void} fn | ||
* @returns {Promise} | ||
*/ | ||
@@ -159,3 +163,3 @@ function nextTick(fn) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -173,3 +177,3 @@ function elem(tagName, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -187,3 +191,3 @@ function elemNS(tagName, ns, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -202,3 +206,3 @@ function elemWithText(tagName, text, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -259,3 +263,3 @@ function elemNSWithText(tagName, ns, text, cssScope) { | ||
* @param {string} name | ||
* @param {function} handler | ||
* @param {EventListener} handler | ||
*/ | ||
@@ -304,4 +308,4 @@ function addStaticEvent(elem, name, handler) { | ||
* @param {string} name | ||
* @param {function} prevValue | ||
* @param {function} newValue | ||
* @param {EventListener} prevValue | ||
* @param {EventListener} newValue | ||
* @param {Element} elem | ||
@@ -400,3 +404,3 @@ */ | ||
if (data || updated) { | ||
injector.parentNode.setProps(changes, true); | ||
/** @type {Component} */ (injector.parentNode).setProps(changes); | ||
return changes; | ||
@@ -426,2 +430,3 @@ } | ||
function normalizeClassName(str) { | ||
/** @type {string[]} */ | ||
const out = []; | ||
@@ -432,3 +437,3 @@ const parts = String(str).split(/\s+/); | ||
cl = parts[i]; | ||
if (cl && !out.includes(cl)) { | ||
if (cl && out.indexOf(cl) === -1) { | ||
out.push(cl); | ||
@@ -489,2 +494,3 @@ } | ||
* @param {Component} host | ||
* @param {Object} [incoming] | ||
* @return {Object} | ||
@@ -556,3 +562,3 @@ */ | ||
* Registers given element as output slot for `host` component | ||
* @param {import('../types').Component} host | ||
* @param {Component} host | ||
* @param {string} name | ||
@@ -573,3 +579,3 @@ * @param {HTMLElement} elem | ||
* Sync slot content if necessary | ||
* @param {import('../types').Component} host | ||
* @param {Component} host | ||
*/ | ||
@@ -585,4 +591,4 @@ function updateSlots(host) { | ||
* Renders incoming contents of given slot | ||
* @param {import('../types').Component} host | ||
* @param {import('../types').Injector} target | ||
* @param {Component} host | ||
* @param {Injector} target | ||
* @returns {boolean} Returns `true` if slot content was filled with incoming data, | ||
@@ -621,8 +627,9 @@ * `false` otherwise | ||
* @param {string} name | ||
* @param {import('../types').ComponentDefinition} definition | ||
* @param {import('../types').Component} [host] | ||
* @returns {import('../types').Component} | ||
* @param {ComponentDefinition} definition | ||
* @param {Component} [host] | ||
* @returns {Component} | ||
*/ | ||
function createComponent(name, definition, host) { | ||
/** @type {import('../types').Component} */ | ||
/** @type {Component} */ | ||
// @ts-ignore | ||
const element = elem(name, host && host.componentModel && host.componentModel.definition.cssScope); | ||
@@ -646,7 +653,8 @@ | ||
element.setProps = function setProps(value, silent) { | ||
if (value != null) { | ||
const changes = value != null && changed(value, element.props); | ||
if (changes) { | ||
assign(element.props, value); | ||
representProps(element, value); | ||
if (!silent && element.componentModel.mounted) { | ||
renderNext(element, value); | ||
renderNext(element, changes); | ||
} | ||
@@ -675,3 +683,5 @@ } | ||
// XXX Should point to Shadow Root in Web Components | ||
element.componentView = definition.componentView ? definition.componentView(element, host) : element; | ||
if (!element.componentView) { | ||
element.componentView = definition.componentView ? definition.componentView(element, host) : element; | ||
} | ||
@@ -711,3 +721,3 @@ if (definition.store) { | ||
*/ | ||
function mountComponent(elem$$1, initialProps) { | ||
function mountComponent(elem$$1, initialProps = obj()) { | ||
const { componentModel } = elem$$1; | ||
@@ -717,6 +727,11 @@ const { input, definition } = componentModel; | ||
finalizeProps(input, initialProps); | ||
const changes = changed(elem$$1.props, obj()); | ||
const args = [changes || {}]; | ||
const args = [initialProps || {}]; | ||
componentModel.rendering = true; | ||
componentModel.rendering = true; | ||
if (changes) { | ||
runHook(elem$$1, 'didChange', args); | ||
} | ||
runHook(elem$$1, 'willMount', args); | ||
@@ -749,3 +764,3 @@ if (definition.default) { | ||
* Destroys given component: removes static event listeners and cleans things up | ||
* @param {import('../types').Component} elem | ||
* @param {Component} elem | ||
*/ | ||
@@ -774,4 +789,4 @@ function unmountComponent(elem$$1) { | ||
* Subscribes to store updates of given component | ||
* @param {import('../types').Component} component | ||
* @param {string} keys | ||
* @param {Component} component | ||
* @param {string[]} keys | ||
*/ | ||
@@ -802,3 +817,3 @@ function subscribeStore(component, keys) { | ||
* @param {Component} elem | ||
* @param {Object} changes | ||
* @param {Object} [changes] | ||
*/ | ||
@@ -821,3 +836,3 @@ function scheduleRender(elem$$1, changes) { | ||
* @param {Component} elem | ||
* @param {Object} changes | ||
* @param {Object} [changes] | ||
*/ | ||
@@ -831,2 +846,6 @@ function renderComponent(elem$$1, changes) { | ||
if (changes) { | ||
runHook(elem$$1, 'didChange', args); | ||
} | ||
// TODO prepare data for hooks in `mountComponent`? | ||
@@ -860,3 +879,3 @@ runHook(elem$$1, 'willUpdate', args); | ||
* Represents given props as attribute values in `elem` | ||
* @param {HTMLElement} elem | ||
* @param {Element} elem | ||
* @param {object} props | ||
@@ -868,2 +887,3 @@ */ | ||
if (!/^partial:/.test(p)) { | ||
const name = p.replace(/[A-Z]/g, kebabCase); | ||
let value = props[p]; | ||
@@ -882,3 +902,3 @@ const type = typeof(value); | ||
isDefined(value) ? elem$$1.setAttribute(p, value) : elem$$1.removeAttribute(p); | ||
isDefined(value) ? elem$$1.setAttribute(name, value) : elem$$1.removeAttribute(name); | ||
} | ||
@@ -892,9 +912,10 @@ } | ||
* @param {ComponentDefinition} definition | ||
* @return {import('../types').AttachedEventsMap} Map of attached event handlers | ||
* @return {AttachedEventsMap} Map of attached event handlers | ||
*/ | ||
function attachStaticEvents(component, definition) { | ||
/** @type {import('../types').AttachedEventsMap} */ | ||
/** @type {AttachedEventsMap} */ | ||
const eventMap = obj(); | ||
const handler = function(evt) { | ||
/** @param {Event} evt */ | ||
const handler = function (evt) { | ||
const listeners = eventMap[evt.type].listeners; | ||
@@ -929,4 +950,4 @@ for (let i = 0; i < listeners.length; i++) { | ||
* Removes attached events from given map | ||
* @param {import('../types').Component} component | ||
* @param {import('../types').AttachedEventsMap} eventMap | ||
* @param {Component} component | ||
* @param {AttachedEventsMap} eventMap | ||
*/ | ||
@@ -939,2 +960,10 @@ function detachStaticEvents(component, eventMap) { | ||
/** | ||
* @param {string} ch | ||
* @returns {string} | ||
*/ | ||
function kebabCase(ch) { | ||
return '-' + ch.toLowerCase(); | ||
} | ||
const blockKey = '&block'; | ||
@@ -945,3 +974,2 @@ | ||
* @param {Element} target | ||
* @param {boolean} slotted Use slotted model for storing elements | ||
* @returns {Injector} | ||
@@ -1048,3 +1076,3 @@ */ | ||
for (let i = blockItems.length - 1, item; i >= 0; i--) { | ||
item = blockItems[i]; | ||
item = /** @type {Element} */ (blockItems[i]); | ||
if (!isBlock(item)) { | ||
@@ -1078,3 +1106,3 @@ domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode)); | ||
if (!isBlock(item)) { | ||
disposeElement(item); | ||
disposeElement(/** @type {Node} */ (item)); | ||
} | ||
@@ -1088,3 +1116,3 @@ } | ||
* @param {Injector} injector | ||
* @param {Block | Node} item | ||
* @param {InjectorItem} item | ||
*/ | ||
@@ -1099,5 +1127,5 @@ function add(injector, item) { | ||
* Get DOM node nearest to given position of items list | ||
* @param {Element[] | Block[]} items | ||
* @param {InjectorItem[]} items | ||
* @param {number} ix | ||
* @param {Element} parent Ensure element has given element as parent node | ||
* @param {Node} parent Ensure element has given element as parent node | ||
* @returns {Node} | ||
@@ -1107,3 +1135,3 @@ */ | ||
while (ix < items.length) { | ||
const item = items[ix++]; | ||
const item = /** @type {Node} */ (items[ix++]); | ||
if (item.parentNode === parent) { | ||
@@ -1164,3 +1192,3 @@ return item; | ||
* with proper lifecycle hooks and detaches given element from DOM | ||
* @param {HTMLElement | Component} elem | ||
* @param {Node | Component} elem | ||
*/ | ||
@@ -1183,3 +1211,3 @@ function disposeElement(elem) { | ||
* Collects all nested components from given node (including node itself) | ||
* @param {HTMLElement | Component} node | ||
* @param {Node | Component} node | ||
* @param {Array} to | ||
@@ -1189,10 +1217,10 @@ * @returns {Component[]} | ||
function collectComponents(node, to) { | ||
if (node.componentModel) { | ||
if (/** @type {Component} */ (node).componentModel) { | ||
to.push(node); | ||
} | ||
node = node.firstChild; | ||
while (node) { | ||
collectComponents(node, to); | ||
node = node.nextSibling; | ||
let child = /** @type {Node} */ (node.firstChild); | ||
while (child) { | ||
collectComponents(child, to); | ||
child = child.nextSibling; | ||
} | ||
@@ -1205,3 +1233,3 @@ | ||
* @param {Node} node | ||
* @param {Element} parent | ||
* @param {Node} parent | ||
* @param {Node} anchor | ||
@@ -1227,6 +1255,6 @@ * @returns {Node} Inserted item | ||
* Initial block rendering | ||
* @param {import('../types').Component} component | ||
* @param {import('../types').Injector} injector | ||
* @param {Component} component | ||
* @param {Injector} injector | ||
* @param {Function} get | ||
* @returns {import('../types').BlockContext} | ||
* @returns {BlockContext} | ||
*/ | ||
@@ -1246,4 +1274,4 @@ function mountBlock(component, injector, get) { | ||
* Updated block, described in `ctx` object | ||
* @param {import('../types').BlockContext} ctx | ||
* @returns {import('../types').BlockContext} | ||
* @param {BlockContext} ctx | ||
* @returns {BlockContext} | ||
*/ | ||
@@ -1436,3 +1464,3 @@ function updateBlock(ctx) { | ||
* @param {string} name | ||
* @param {HTMLElement} value | ||
* @param {Element} value | ||
*/ | ||
@@ -1511,4 +1539,4 @@ function setStaticRef(host, name, value) { | ||
/** | ||
* @param {import('../types').Component} host | ||
* @param {import('../types').Injector} injector | ||
* @param {Component} host | ||
* @param {Injector} injector | ||
* @param {Object} ctx | ||
@@ -1532,3 +1560,3 @@ */ | ||
function scopeDOM(node, cssScope$$1) { | ||
node = node.firstChild; | ||
node = /** @type {Element} */ (node.firstChild); | ||
while (node) { | ||
@@ -1539,3 +1567,3 @@ if (node.nodeType === node.ELEMENT_NODE) { | ||
} | ||
node = node.nextSibling; | ||
node = /** @type {Element} */ (node.nextSibling); | ||
} | ||
@@ -1593,5 +1621,8 @@ } | ||
const prefix = '$'; | ||
class Store { | ||
constructor(data = {}) { | ||
this.data = assign({}, data); | ||
/** @type {StoreUpdateEntry[]} */ | ||
this._listeners = []; | ||
@@ -1616,3 +1647,3 @@ | ||
set(data) { | ||
const updated = changed(data, this.data); | ||
const updated = changed(data, this.data, prefix); | ||
const render = this.sync ? renderComponent : scheduleRender; | ||
@@ -1629,3 +1660,3 @@ | ||
if ('component' in item) { | ||
render(item.component); | ||
render(item.component, updated); | ||
} else if ('handler' in item) { | ||
@@ -1641,3 +1672,3 @@ item.handler(next, updated); | ||
* Subscribes to changes in given store | ||
* @param {Function} handler Function to invoke when store changes | ||
* @param {StoreUpdateHandler} handler Function to invoke when store changes | ||
* @param {string[]} keys Run handler only if given top-level keys are changed | ||
@@ -1647,3 +1678,7 @@ * @returns {Object} Object that should be used to unsubscribe from updates | ||
subscribe(handler, keys) { | ||
const obj$$1 = { handler, keys }; | ||
/** @type {StoreUpdateEntry} */ | ||
const obj$$1 = { | ||
handler, | ||
keys: scopeKeys(keys, prefix) | ||
}; | ||
this._listeners.push(obj$$1); | ||
@@ -1666,7 +1701,10 @@ return obj$$1; | ||
* Watches for updates of given `keys` in store and runs `component` render on change | ||
* @param {import('../types').Component} component | ||
* @param {Component} component | ||
* @param {string[]} keys | ||
*/ | ||
watch(component, keys) { | ||
this._listeners.push({ component, keys }); | ||
this._listeners.push({ | ||
component, | ||
keys: scopeKeys(keys, prefix) | ||
}); | ||
} | ||
@@ -1676,3 +1714,3 @@ | ||
* Stops watching for store updates for given component | ||
* @param {import('../types').Component} component | ||
* @param {Component} component | ||
*/ | ||
@@ -1707,2 +1745,12 @@ unwatch(component) { | ||
/** | ||
* Adds given prefix to keys | ||
* @param {string[]} keys | ||
* @param {string} prefix | ||
* @returns {string[]} | ||
*/ | ||
function scopeKeys(keys, prefix) { | ||
return keys && prefix ? keys.map(key => prefix + key) : keys; | ||
} | ||
/** | ||
* Safe property getter | ||
@@ -1709,0 +1757,0 @@ * @param {*} ctx |
@@ -44,3 +44,3 @@ /** | ||
* Creates object for storing change sets, e.g. current and previous values | ||
* @returns {object} | ||
* @returns {ChangeSet} | ||
*/ | ||
@@ -56,5 +56,5 @@ function changeSet() { | ||
* @param {Object} prev | ||
* @return {Object} | ||
* @return {Changes} | ||
*/ | ||
function changed(next, prev) { | ||
function changed(next, prev, prefix = '') { | ||
const result = obj(); | ||
@@ -67,3 +67,6 @@ let dirty = false; | ||
dirty = true; | ||
result[p] = prev[p]; | ||
result[prefix ? prefix + p : p] = { | ||
prev: prev[p], | ||
next: next[p] | ||
}; | ||
} | ||
@@ -98,3 +101,4 @@ } | ||
* Queues given `fn` function to be invoked asynchronously as soon as possible | ||
* @param {function} fn | ||
* @param {(value: void) => void} fn | ||
* @returns {Promise} | ||
*/ | ||
@@ -155,3 +159,3 @@ function nextTick(fn) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -169,3 +173,3 @@ function elem(tagName, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -183,3 +187,3 @@ function elemNS(tagName, ns, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -198,3 +202,3 @@ function elemWithText(tagName, text, cssScope) { | ||
* @param {string} [cssScope] Scope for CSS isolation | ||
* @return {HTMLElement} | ||
* @return {Element} | ||
*/ | ||
@@ -255,3 +259,3 @@ function elemNSWithText(tagName, ns, text, cssScope) { | ||
* @param {string} name | ||
* @param {function} handler | ||
* @param {EventListener} handler | ||
*/ | ||
@@ -300,4 +304,4 @@ function addStaticEvent(elem, name, handler) { | ||
* @param {string} name | ||
* @param {function} prevValue | ||
* @param {function} newValue | ||
* @param {EventListener} prevValue | ||
* @param {EventListener} newValue | ||
* @param {Element} elem | ||
@@ -396,3 +400,3 @@ */ | ||
if (data || updated) { | ||
injector.parentNode.setProps(changes, true); | ||
/** @type {Component} */ (injector.parentNode).setProps(changes); | ||
return changes; | ||
@@ -422,2 +426,3 @@ } | ||
function normalizeClassName(str) { | ||
/** @type {string[]} */ | ||
const out = []; | ||
@@ -428,3 +433,3 @@ const parts = String(str).split(/\s+/); | ||
cl = parts[i]; | ||
if (cl && !out.includes(cl)) { | ||
if (cl && out.indexOf(cl) === -1) { | ||
out.push(cl); | ||
@@ -485,2 +490,3 @@ } | ||
* @param {Component} host | ||
* @param {Object} [incoming] | ||
* @return {Object} | ||
@@ -552,3 +558,3 @@ */ | ||
* Registers given element as output slot for `host` component | ||
* @param {import('../types').Component} host | ||
* @param {Component} host | ||
* @param {string} name | ||
@@ -569,3 +575,3 @@ * @param {HTMLElement} elem | ||
* Sync slot content if necessary | ||
* @param {import('../types').Component} host | ||
* @param {Component} host | ||
*/ | ||
@@ -581,4 +587,4 @@ function updateSlots(host) { | ||
* Renders incoming contents of given slot | ||
* @param {import('../types').Component} host | ||
* @param {import('../types').Injector} target | ||
* @param {Component} host | ||
* @param {Injector} target | ||
* @returns {boolean} Returns `true` if slot content was filled with incoming data, | ||
@@ -617,8 +623,9 @@ * `false` otherwise | ||
* @param {string} name | ||
* @param {import('../types').ComponentDefinition} definition | ||
* @param {import('../types').Component} [host] | ||
* @returns {import('../types').Component} | ||
* @param {ComponentDefinition} definition | ||
* @param {Component} [host] | ||
* @returns {Component} | ||
*/ | ||
function createComponent(name, definition, host) { | ||
/** @type {import('../types').Component} */ | ||
/** @type {Component} */ | ||
// @ts-ignore | ||
const element = elem(name, host && host.componentModel && host.componentModel.definition.cssScope); | ||
@@ -642,7 +649,8 @@ | ||
element.setProps = function setProps(value, silent) { | ||
if (value != null) { | ||
const changes = value != null && changed(value, element.props); | ||
if (changes) { | ||
assign(element.props, value); | ||
representProps(element, value); | ||
if (!silent && element.componentModel.mounted) { | ||
renderNext(element, value); | ||
renderNext(element, changes); | ||
} | ||
@@ -671,3 +679,5 @@ } | ||
// XXX Should point to Shadow Root in Web Components | ||
element.componentView = definition.componentView ? definition.componentView(element, host) : element; | ||
if (!element.componentView) { | ||
element.componentView = definition.componentView ? definition.componentView(element, host) : element; | ||
} | ||
@@ -707,3 +717,3 @@ if (definition.store) { | ||
*/ | ||
function mountComponent(elem$$1, initialProps) { | ||
function mountComponent(elem$$1, initialProps = obj()) { | ||
const { componentModel } = elem$$1; | ||
@@ -713,6 +723,11 @@ const { input, definition } = componentModel; | ||
finalizeProps(input, initialProps); | ||
const changes = changed(elem$$1.props, obj()); | ||
const args = [changes || {}]; | ||
const args = [initialProps || {}]; | ||
componentModel.rendering = true; | ||
componentModel.rendering = true; | ||
if (changes) { | ||
runHook(elem$$1, 'didChange', args); | ||
} | ||
runHook(elem$$1, 'willMount', args); | ||
@@ -745,3 +760,3 @@ if (definition.default) { | ||
* Destroys given component: removes static event listeners and cleans things up | ||
* @param {import('../types').Component} elem | ||
* @param {Component} elem | ||
*/ | ||
@@ -770,4 +785,4 @@ function unmountComponent(elem$$1) { | ||
* Subscribes to store updates of given component | ||
* @param {import('../types').Component} component | ||
* @param {string} keys | ||
* @param {Component} component | ||
* @param {string[]} keys | ||
*/ | ||
@@ -798,3 +813,3 @@ function subscribeStore(component, keys) { | ||
* @param {Component} elem | ||
* @param {Object} changes | ||
* @param {Object} [changes] | ||
*/ | ||
@@ -817,3 +832,3 @@ function scheduleRender(elem$$1, changes) { | ||
* @param {Component} elem | ||
* @param {Object} changes | ||
* @param {Object} [changes] | ||
*/ | ||
@@ -827,2 +842,6 @@ function renderComponent(elem$$1, changes) { | ||
if (changes) { | ||
runHook(elem$$1, 'didChange', args); | ||
} | ||
// TODO prepare data for hooks in `mountComponent`? | ||
@@ -856,3 +875,3 @@ runHook(elem$$1, 'willUpdate', args); | ||
* Represents given props as attribute values in `elem` | ||
* @param {HTMLElement} elem | ||
* @param {Element} elem | ||
* @param {object} props | ||
@@ -864,2 +883,3 @@ */ | ||
if (!/^partial:/.test(p)) { | ||
const name = p.replace(/[A-Z]/g, kebabCase); | ||
let value = props[p]; | ||
@@ -878,3 +898,3 @@ const type = typeof(value); | ||
isDefined(value) ? elem$$1.setAttribute(p, value) : elem$$1.removeAttribute(p); | ||
isDefined(value) ? elem$$1.setAttribute(name, value) : elem$$1.removeAttribute(name); | ||
} | ||
@@ -888,9 +908,10 @@ } | ||
* @param {ComponentDefinition} definition | ||
* @return {import('../types').AttachedEventsMap} Map of attached event handlers | ||
* @return {AttachedEventsMap} Map of attached event handlers | ||
*/ | ||
function attachStaticEvents(component, definition) { | ||
/** @type {import('../types').AttachedEventsMap} */ | ||
/** @type {AttachedEventsMap} */ | ||
const eventMap = obj(); | ||
const handler = function(evt) { | ||
/** @param {Event} evt */ | ||
const handler = function (evt) { | ||
const listeners = eventMap[evt.type].listeners; | ||
@@ -925,4 +946,4 @@ for (let i = 0; i < listeners.length; i++) { | ||
* Removes attached events from given map | ||
* @param {import('../types').Component} component | ||
* @param {import('../types').AttachedEventsMap} eventMap | ||
* @param {Component} component | ||
* @param {AttachedEventsMap} eventMap | ||
*/ | ||
@@ -935,2 +956,10 @@ function detachStaticEvents(component, eventMap) { | ||
/** | ||
* @param {string} ch | ||
* @returns {string} | ||
*/ | ||
function kebabCase(ch) { | ||
return '-' + ch.toLowerCase(); | ||
} | ||
const blockKey = '&block'; | ||
@@ -941,3 +970,2 @@ | ||
* @param {Element} target | ||
* @param {boolean} slotted Use slotted model for storing elements | ||
* @returns {Injector} | ||
@@ -1044,3 +1072,3 @@ */ | ||
for (let i = blockItems.length - 1, item; i >= 0; i--) { | ||
item = blockItems[i]; | ||
item = /** @type {Element} */ (blockItems[i]); | ||
if (!isBlock(item)) { | ||
@@ -1074,3 +1102,3 @@ domInsert(item, item.parentNode, getAnchorNode(items, pos, item.parentNode)); | ||
if (!isBlock(item)) { | ||
disposeElement(item); | ||
disposeElement(/** @type {Node} */ (item)); | ||
} | ||
@@ -1084,3 +1112,3 @@ } | ||
* @param {Injector} injector | ||
* @param {Block | Node} item | ||
* @param {InjectorItem} item | ||
*/ | ||
@@ -1095,5 +1123,5 @@ function add(injector, item) { | ||
* Get DOM node nearest to given position of items list | ||
* @param {Element[] | Block[]} items | ||
* @param {InjectorItem[]} items | ||
* @param {number} ix | ||
* @param {Element} parent Ensure element has given element as parent node | ||
* @param {Node} parent Ensure element has given element as parent node | ||
* @returns {Node} | ||
@@ -1103,3 +1131,3 @@ */ | ||
while (ix < items.length) { | ||
const item = items[ix++]; | ||
const item = /** @type {Node} */ (items[ix++]); | ||
if (item.parentNode === parent) { | ||
@@ -1160,3 +1188,3 @@ return item; | ||
* with proper lifecycle hooks and detaches given element from DOM | ||
* @param {HTMLElement | Component} elem | ||
* @param {Node | Component} elem | ||
*/ | ||
@@ -1179,3 +1207,3 @@ function disposeElement(elem) { | ||
* Collects all nested components from given node (including node itself) | ||
* @param {HTMLElement | Component} node | ||
* @param {Node | Component} node | ||
* @param {Array} to | ||
@@ -1185,10 +1213,10 @@ * @returns {Component[]} | ||
function collectComponents(node, to) { | ||
if (node.componentModel) { | ||
if (/** @type {Component} */ (node).componentModel) { | ||
to.push(node); | ||
} | ||
node = node.firstChild; | ||
while (node) { | ||
collectComponents(node, to); | ||
node = node.nextSibling; | ||
let child = /** @type {Node} */ (node.firstChild); | ||
while (child) { | ||
collectComponents(child, to); | ||
child = child.nextSibling; | ||
} | ||
@@ -1201,3 +1229,3 @@ | ||
* @param {Node} node | ||
* @param {Element} parent | ||
* @param {Node} parent | ||
* @param {Node} anchor | ||
@@ -1223,6 +1251,6 @@ * @returns {Node} Inserted item | ||
* Initial block rendering | ||
* @param {import('../types').Component} component | ||
* @param {import('../types').Injector} injector | ||
* @param {Component} component | ||
* @param {Injector} injector | ||
* @param {Function} get | ||
* @returns {import('../types').BlockContext} | ||
* @returns {BlockContext} | ||
*/ | ||
@@ -1242,4 +1270,4 @@ function mountBlock(component, injector, get) { | ||
* Updated block, described in `ctx` object | ||
* @param {import('../types').BlockContext} ctx | ||
* @returns {import('../types').BlockContext} | ||
* @param {BlockContext} ctx | ||
* @returns {BlockContext} | ||
*/ | ||
@@ -1432,3 +1460,3 @@ function updateBlock(ctx) { | ||
* @param {string} name | ||
* @param {HTMLElement} value | ||
* @param {Element} value | ||
*/ | ||
@@ -1507,4 +1535,4 @@ function setStaticRef(host, name, value) { | ||
/** | ||
* @param {import('../types').Component} host | ||
* @param {import('../types').Injector} injector | ||
* @param {Component} host | ||
* @param {Injector} injector | ||
* @param {Object} ctx | ||
@@ -1528,3 +1556,3 @@ */ | ||
function scopeDOM(node, cssScope$$1) { | ||
node = node.firstChild; | ||
node = /** @type {Element} */ (node.firstChild); | ||
while (node) { | ||
@@ -1535,3 +1563,3 @@ if (node.nodeType === node.ELEMENT_NODE) { | ||
} | ||
node = node.nextSibling; | ||
node = /** @type {Element} */ (node.nextSibling); | ||
} | ||
@@ -1589,5 +1617,8 @@ } | ||
const prefix = '$'; | ||
class Store { | ||
constructor(data = {}) { | ||
this.data = assign({}, data); | ||
/** @type {StoreUpdateEntry[]} */ | ||
this._listeners = []; | ||
@@ -1612,3 +1643,3 @@ | ||
set(data) { | ||
const updated = changed(data, this.data); | ||
const updated = changed(data, this.data, prefix); | ||
const render = this.sync ? renderComponent : scheduleRender; | ||
@@ -1625,3 +1656,3 @@ | ||
if ('component' in item) { | ||
render(item.component); | ||
render(item.component, updated); | ||
} else if ('handler' in item) { | ||
@@ -1637,3 +1668,3 @@ item.handler(next, updated); | ||
* Subscribes to changes in given store | ||
* @param {Function} handler Function to invoke when store changes | ||
* @param {StoreUpdateHandler} handler Function to invoke when store changes | ||
* @param {string[]} keys Run handler only if given top-level keys are changed | ||
@@ -1643,3 +1674,7 @@ * @returns {Object} Object that should be used to unsubscribe from updates | ||
subscribe(handler, keys) { | ||
const obj$$1 = { handler, keys }; | ||
/** @type {StoreUpdateEntry} */ | ||
const obj$$1 = { | ||
handler, | ||
keys: scopeKeys(keys, prefix) | ||
}; | ||
this._listeners.push(obj$$1); | ||
@@ -1662,7 +1697,10 @@ return obj$$1; | ||
* Watches for updates of given `keys` in store and runs `component` render on change | ||
* @param {import('../types').Component} component | ||
* @param {Component} component | ||
* @param {string[]} keys | ||
*/ | ||
watch(component, keys) { | ||
this._listeners.push({ component, keys }); | ||
this._listeners.push({ | ||
component, | ||
keys: scopeKeys(keys, prefix) | ||
}); | ||
} | ||
@@ -1672,3 +1710,3 @@ | ||
* Stops watching for store updates for given component | ||
* @param {import('../types').Component} component | ||
* @param {Component} component | ||
*/ | ||
@@ -1703,2 +1741,12 @@ unwatch(component) { | ||
/** | ||
* Adds given prefix to keys | ||
* @param {string[]} keys | ||
* @param {string} prefix | ||
* @returns {string[]} | ||
*/ | ||
function scopeKeys(keys, prefix) { | ||
return keys && prefix ? keys.map(key => prefix + key) : keys; | ||
} | ||
/** | ||
* Safe property getter | ||
@@ -1705,0 +1753,0 @@ * @param {*} ctx |
{ | ||
"name": "@endorphinjs/template-runtime", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "EndorphinJS template runtime, embedded with template bundles", | ||
@@ -5,0 +5,0 @@ "main": "./dist/runtime.cjs.js", |
550
types.d.ts
import { Store } from './lib/store'; | ||
export interface Component extends HTMLElement { | ||
/** | ||
* Pointer to component view container. By default, it’s the same as component | ||
* element, but for native Web Components it points to shadow root | ||
*/ | ||
componentView: HTMLElement | ShadowRoot; | ||
declare global { | ||
type InjectorNode = Node; | ||
type InjectorItem = any; | ||
/** | ||
* Internal component model | ||
*/ | ||
readonly componentModel: ComponentModel; | ||
interface Component extends Element { | ||
/** | ||
* Pointer to component view container. By default, it’s the same as component | ||
* element, but for native Web Components it points to shadow root | ||
*/ | ||
componentView: Element; | ||
/** | ||
* Component properties (external contract) | ||
*/ | ||
readonly props: object; | ||
/** | ||
* Internal component model | ||
*/ | ||
componentModel: ComponentModel; | ||
/** | ||
* Component state (internal props) | ||
*/ | ||
readonly state: object; | ||
/** | ||
* Component properties (external contract) | ||
*/ | ||
props: object; | ||
/** | ||
* Named references to elements rendered inside current component | ||
*/ | ||
readonly refs: RefMap; | ||
/** | ||
* Component state (internal props) | ||
*/ | ||
state: object; | ||
/** | ||
* A store, bound to current component | ||
*/ | ||
readonly store?: Store; | ||
/** | ||
* Named references to elements rendered inside current component | ||
*/ | ||
refs: RefMap; | ||
/** | ||
* References to component slot containers. Default slot is available as `slot['']` | ||
*/ | ||
readonly slots: RefMap; | ||
/** | ||
* A store, bound to current component | ||
*/ | ||
store?: Store; | ||
/** | ||
* Reference to the root component of the current app | ||
*/ | ||
readonly root?: Component; | ||
/** | ||
* References to component slot containers. Default slot is available as `slot['']` | ||
*/ | ||
slots: RefMap; | ||
/** | ||
* Updates props with data from `value` | ||
* @param value Updated props | ||
* @returns Final props | ||
*/ | ||
setProps(value: object): object; | ||
/** | ||
* Reference to the root component of the current app | ||
*/ | ||
root?: Component; | ||
/** | ||
* Updates state with data from `value` | ||
* @param value Updated values | ||
* @returns Final state | ||
*/ | ||
setState(value: object): object; | ||
} | ||
/** | ||
* Updates props with data from `value` | ||
* @param value Updated props | ||
* @returns Final props | ||
*/ | ||
setProps(value: object, silent?: boolean): void; | ||
/** | ||
* Internal Endorphin component descriptor | ||
*/ | ||
export interface ComponentModel { | ||
/** | ||
* Component’s definition | ||
*/ | ||
definition: ComponentDefinition; | ||
/** | ||
* Updates state with data from `value` | ||
* @param value Updated values | ||
* @returns Final state | ||
*/ | ||
setState(value: object, silent: boolean): void; | ||
} | ||
/** | ||
* Injector for incoming component data | ||
* @private | ||
* Internal Endorphin component descriptor | ||
*/ | ||
input: Injector; | ||
interface ComponentModel { | ||
/** | ||
* Component’s definition | ||
*/ | ||
definition: ComponentDefinition; | ||
/** | ||
* Change set for component refs | ||
* @private | ||
*/ | ||
refs: ChangeSet; | ||
/** | ||
* Injector for incoming component data | ||
* @private | ||
*/ | ||
input: Injector; | ||
/** | ||
* Runtime variables | ||
*/ | ||
vars: object; | ||
/** | ||
* Change set for component refs | ||
* @private | ||
*/ | ||
refs: ChangeSet; | ||
/** | ||
* List of redefined partials | ||
*/ | ||
partials: { | ||
[name: string]: (host: Component, injector: Injector) => void; | ||
} | ||
/** | ||
* Runtime variables | ||
*/ | ||
vars: object; | ||
/** | ||
* A function for updating rendered component content. Might be available | ||
* after component was mounted and only if component has update cycle | ||
*/ | ||
update?: UpdateView; | ||
/** | ||
* A function for updating rendered component content. Might be available | ||
* after component was mounted and only if component has update cycle | ||
*/ | ||
update?: UpdateView; | ||
/** | ||
* List of attached event handlers | ||
*/ | ||
events: AttachedEventsMap; | ||
/** | ||
* List of attached event handlers | ||
*/ | ||
events: AttachedEventsMap; | ||
/** Slot output for component */ | ||
slots: { | ||
[name: string]: BlockContext | ||
/** Slot output for component */ | ||
slots: { | ||
[name: string]: BlockContext | ||
} | ||
/** | ||
* Indicates that component was mounted | ||
* @private | ||
*/ | ||
mounted: boolean; | ||
/** | ||
* Component render is queued | ||
* @private | ||
*/ | ||
queued: Promise; | ||
/** | ||
* Indicates that component is currently rendering | ||
* @private | ||
*/ | ||
rendering: boolean; | ||
} | ||
/** | ||
* Indicates that component was mounted | ||
* A definition of component, written as ES module | ||
*/ | ||
mounted: boolean; | ||
interface ComponentDefinition { | ||
/** | ||
* Initial props factory | ||
*/ | ||
props?(): object; | ||
/** | ||
* Indicates that component is currently rendering | ||
* @private | ||
*/ | ||
rendering: boolean; | ||
} | ||
/** | ||
* Initial state factory | ||
*/ | ||
state?(): object; | ||
/** | ||
* A definition of component, written as ES module | ||
*/ | ||
export interface ComponentDefinition { | ||
/** | ||
* Initial props factory | ||
*/ | ||
props?(): object; | ||
/** | ||
* Returns instance of store used for components | ||
*/ | ||
store?(): Store; | ||
/** | ||
* Initial state factory | ||
*/ | ||
state?(): object; | ||
/** | ||
* Returns pointer to element where contents of component should be rendered | ||
*/ | ||
componentView?(component: Component, parentComponent?: Component | Element): Element; | ||
/** | ||
* Returns instance of store used for components | ||
*/ | ||
store?(): Store; | ||
/** | ||
* Listeners for events bubbling from component contents | ||
*/ | ||
events?: { | ||
[type: string]: (component: Component, event: Event, target: HTMLElement) => void; | ||
}; | ||
/** | ||
* Returns pointer to element where contents of component should be rendered | ||
*/ | ||
componentView?(component: Component, parentComponent?: Component | Element): Element; | ||
/** | ||
* Public methods to attach to component element | ||
*/ | ||
methods?: { | ||
[name: string]: (this: Component) => void; | ||
}; | ||
/** | ||
* Listeners for events bubbling from component contents | ||
*/ | ||
events?: { | ||
[type: string]: (event: Event, component: Component) => void; | ||
}; | ||
/** | ||
* List of plugins for current component | ||
*/ | ||
plugins?: ComponentDefinition[]; | ||
/** | ||
* Public methods to attach to component element | ||
*/ | ||
methods?: { | ||
[name: string]: (this: Component) => void; | ||
}; | ||
/** | ||
* A scope token to be added for every element, created inside current component | ||
* bound | ||
*/ | ||
cssScope?: string; | ||
/** | ||
* List of plugins for current component | ||
*/ | ||
plugins?: ComponentDefinition[]; | ||
/** | ||
* A function for rendering component contents. Will be added automatically | ||
* in compilation step with compiled HTML template, if not provided. | ||
* If rendered result must be updated, should return function that will be | ||
* invoked for update | ||
*/ | ||
default(component: Component, scope: object): UpdateView; | ||
/** | ||
* A scope token to be added for every element, created inside current component | ||
* bound | ||
*/ | ||
cssScope?: string; | ||
/** | ||
* Component created | ||
*/ | ||
init?(component: Component): void; | ||
/** | ||
* A function for rendering component contents. Will be added automatically | ||
* in compilation step with compiled HTML template, if not provided. | ||
* If rendered result must be updated, should return function that will be | ||
* invoked for update | ||
*/ | ||
default(component: Component): UpdateView; | ||
/** | ||
* Component is about to be mounted (will be initially rendered) | ||
*/ | ||
willMount?(component: Component): void; | ||
/** | ||
* Component created | ||
*/ | ||
init?(component: Component): void; | ||
/** | ||
* Component just mounted (initially rendered) | ||
* @param component | ||
*/ | ||
didMount?(component: Component): void; | ||
/** | ||
* Component is about to be mounted (will be initially rendered) | ||
*/ | ||
willMount?(component: Component): void; | ||
/** | ||
* Component props changed | ||
*/ | ||
didChange?(component: Component, changes: Changes): void; | ||
/** | ||
* Component just mounted (initially rendered) | ||
* @param component | ||
*/ | ||
didMount?(component: Component): void; | ||
/** | ||
* Component is about to be updated (next renders after mount) | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
willUpdate?(component: Component, changes: Changes): void; | ||
/** | ||
* Component is about to be updated (next renders after mount) | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
willUpdate?(component: Component, changedProps: ChangeSet, changedState: ChangeSet): void; | ||
/** | ||
* Component just updated (next renders after mount) | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
didUpdate?(component: Component, changes: Changes): void; | ||
/** | ||
* Component just updated (next renders after mount) | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
didUpdate?(component: Component, changedProps: ChangeSet, changedState: ChangeSet): void; | ||
/** | ||
* Component is about to be rendered. If `false` value is returned, component | ||
* rendering will be cancelled | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
willRender?(component: Component, changes: Changes): boolean; | ||
/** | ||
* Component is about to be rendered. If `false` value is returned, component | ||
* rendering will be cancelled | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
willRender?(component: Component, changedProps?: ChangeSet, changedState?: ChangeSet): boolean; | ||
/** | ||
* Component just rendered | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
didRender?(component: Component, changes: Changes): void; | ||
/** | ||
* Component just rendered | ||
* @param component | ||
* @param changedProps List of changed properties which caused component update | ||
* @param changedState List of changed state which caused component update | ||
*/ | ||
didRender?(component: Component, changedProps?: ChangeSet, changedState?: ChangeSet): void; | ||
/** | ||
* Component is about to be removed | ||
*/ | ||
willUnmount?(component: Component): void; | ||
/** | ||
* Component is about to be removed | ||
*/ | ||
willUnmount?(component: Component): void; | ||
/** | ||
* Component was removed | ||
*/ | ||
didUnmount?(component: Component): void; | ||
} | ||
/** | ||
* Component was removed | ||
*/ | ||
didUnmount?(component: Component): void; | ||
} | ||
interface Injector { | ||
/** | ||
* Injector DOM target | ||
*/ | ||
parentNode: Element; | ||
interface Injector { | ||
/** | ||
* Injector DOM target | ||
*/ | ||
parentNode: Element; | ||
/** | ||
* Current injector contents | ||
*/ | ||
items: InjectorItem[]; | ||
/** | ||
* Current injector contents | ||
*/ | ||
items: Node[] | Block[]; | ||
/** | ||
* Current insertion pointer | ||
*/ | ||
ptr: number; | ||
/** | ||
* Current insertion pointer | ||
*/ | ||
ptr: number; | ||
/** | ||
* Current block context | ||
*/ | ||
ctx: Block; | ||
/** | ||
* Current block context | ||
*/ | ||
ctx: Block; | ||
/** | ||
* Slots container | ||
*/ | ||
slots: object; | ||
/** | ||
* Slots container | ||
*/ | ||
slots: object; | ||
/** | ||
* Pending attributes updates | ||
*/ | ||
attributes: ChangeSet; | ||
/** | ||
* Pending attributes updates | ||
*/ | ||
attributes: ChangeSet; | ||
/** | ||
* Current event handlers | ||
*/ | ||
events: ChangeSet; | ||
} | ||
/** | ||
* Current event handlers | ||
* A structure that holds data about elements owned by given block context | ||
* right below it in `Injector` list | ||
*/ | ||
events: ChangeSet; | ||
} | ||
type Block = { | ||
/** @private */ | ||
'&block': true; | ||
/** | ||
* A structure that holds data about elements owned by given block context | ||
* right below it in `Injector` list | ||
*/ | ||
interface Block { | ||
/** | ||
* Number of inserted items in block context | ||
*/ | ||
inserted: number; | ||
/** | ||
* Number of inserted items in block context | ||
*/ | ||
inserted: number; | ||
/** | ||
* Number of deleted items in block context | ||
*/ | ||
deleted: number; | ||
/** | ||
* Number of deleted items in block context | ||
*/ | ||
deleted: number; | ||
/** | ||
* Amount of items in current block | ||
*/ | ||
size: number; | ||
} | ||
/** | ||
* Amount of items in current block | ||
*/ | ||
size: number; | ||
} | ||
interface AttachedEventsMap { | ||
[event: string]: { | ||
listeners: Function[]; | ||
handler: Function; | ||
interface AttachedEventsMap { | ||
[event: string]: { | ||
listeners: Function[]; | ||
handler: EventListener; | ||
} | ||
} | ||
} | ||
interface RefMap { | ||
[key: string]: HTMLElement; | ||
} | ||
interface RefMap { | ||
[key: string]: Element; | ||
} | ||
interface ChangeSet { | ||
prev: object; | ||
next: object; | ||
} | ||
interface ChangeSet { | ||
prev: object; | ||
cur: object; | ||
} | ||
interface UpdateView { | ||
(): void; | ||
} | ||
interface Changes { | ||
[key: string]: { | ||
next: any, | ||
prev: any | ||
} | ||
}; | ||
interface BlockContext { | ||
component: Component; | ||
injector: Injector; | ||
block: Block, | ||
get: Function; | ||
fn?: Function, | ||
update?: Function, | ||
interface UpdateView { | ||
(host: Component, scope: object): void; | ||
} | ||
interface BlockContext { | ||
component: Component; | ||
injector: Injector; | ||
block: Block, | ||
get: Function; | ||
fn?: Function, | ||
update?: Function, | ||
} | ||
interface StoreUpdateHandler { | ||
(state: object, changes: object): void | ||
} | ||
interface StoreUpdateEntry { | ||
keys?: string[]; | ||
component?: Component; | ||
handler?: StoreUpdateHandler; | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
263757
3419