@endorphinjs/template-runtime
Advanced tools
Comparing version 0.1.12 to 0.1.13
@@ -6,2 +6,113 @@ 'use strict'; | ||
/** | ||
* Creates linted list | ||
* @return {LinkedList} | ||
*/ | ||
function createList() { | ||
return { head: null }; | ||
} | ||
/** | ||
* Creates linked list item | ||
* @template T | ||
* @param {T} value | ||
* @returns {LinkedListItem<T>} | ||
*/ | ||
function createListItem(value) { | ||
return { value, next: null, prev: null }; | ||
} | ||
/** | ||
* Prepends given value to linked list | ||
* @template T | ||
* @param {LinkedList} list | ||
* @param {T} value | ||
* @return {LinkedListItem<T>} | ||
*/ | ||
function listPrependValue(list, value) { | ||
const item = createListItem(value); | ||
if (item.next = list.head) { | ||
item.next.prev = item; | ||
} | ||
return list.head = item; | ||
} | ||
/** | ||
* Inserts given value after given `ref` item | ||
* @template T | ||
* @param {T} value | ||
* @param {LinkedListItem<any>} ref | ||
* @return {LinkedListItem<T>} | ||
*/ | ||
function listInsertValueAfter(value, ref) { | ||
const item = createListItem(value); | ||
const { next } = ref; | ||
ref.next = item; | ||
item.prev = ref; | ||
if (item.next = next) { | ||
next.prev = item; | ||
} | ||
return item; | ||
} | ||
/** | ||
* Moves list fragment with `start` and `end` bounds right after `ref` item | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
* @param {LinkedListItem} ref | ||
*/ | ||
function listMoveFragmentAfter(list, start, end, ref) { | ||
listDetachFragment(list, start, end); | ||
if (end.next = ref.next) { | ||
end.next.prev = end; | ||
} | ||
ref.next = start; | ||
start.prev = ref; | ||
} | ||
/** | ||
* Moves list fragment with `start` and `end` to list head | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
*/ | ||
function listMoveFragmentFirst(list, start, end) { | ||
listDetachFragment(list, start, end); | ||
if (end.next = list.head) { | ||
end.next.prev = end; | ||
} | ||
list.head = start; | ||
} | ||
/** | ||
* Detaches list fragment with `start` and `end` from list | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
*/ | ||
function listDetachFragment(list, start, end) { | ||
const { prev } = start; | ||
const { next } = end; | ||
if (prev) { | ||
prev.next = next; | ||
} else { | ||
list.head = next; | ||
} | ||
if (next) { | ||
next.prev = prev; | ||
} | ||
start.prev = end.next = null; | ||
} | ||
/** | ||
* Creates fast object | ||
@@ -163,4 +274,2 @@ * @param {Object} [proto] | ||
const blockKey = '&block'; | ||
/** | ||
@@ -174,5 +283,5 @@ * Creates injector instance for given target, if required | ||
parentNode: target, | ||
items: [], | ||
items: createList(), | ||
ctx: null, | ||
ptr: 0, | ||
ptr: null, | ||
@@ -189,43 +298,2 @@ // NB create `slots` placeholder to promote object to hidden class. | ||
/** | ||
* 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 | ||
@@ -238,3 +306,3 @@ * @param {Injector} injector | ||
let target; | ||
const { slots } = injector; | ||
const { items, slots, ptr } = injector; | ||
@@ -247,34 +315,73 @@ if (slots) { | ||
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target)); | ||
return add(injector, node); | ||
domInsert(node, target, ptr && getAnchorNode(ptr.next, target)); | ||
injector.ptr = ptr ? listInsertValueAfter(node, ptr) : listPrependValue(items, node); | ||
return node; | ||
} | ||
/** | ||
* Moves contents of given block at `pos` location, effectively updating | ||
* inserted nodes in parent context | ||
* Injects given block | ||
* @template {BaseBlock} T | ||
* @param {Injector} injector | ||
* @param {Block} block | ||
* @param {number} pos | ||
* @param {T} block | ||
* @returns {T} | ||
*/ | ||
function move(injector, block, pos) { | ||
const { items } = injector; | ||
function injectBlock(injector, block) { | ||
const { items, ptr } = injector; | ||
if (items[pos] === block) { | ||
return; | ||
if (ptr) { | ||
block.end = listInsertValueAfter(block, ptr); | ||
block.start = listInsertValueAfter(block, ptr); | ||
} else { | ||
block.end = listPrependValue(items, block); | ||
block.start = listPrependValue(items, block); | ||
} | ||
// Move block contents at given position | ||
const curPos = items.indexOf(block); | ||
const blockItems = items.splice(curPos, block.size + 1); | ||
injector.ptr = block.end; | ||
return block; | ||
} | ||
if (curPos < pos) { | ||
pos -= blockItems.length; | ||
/** | ||
* Runs `fn` template function in context of given `block` | ||
* @param {BaseBlock} block | ||
* @param {Function} fn | ||
* @param {*} data | ||
* @returns {*} Result of `fn` function call | ||
*/ | ||
function run(block, fn, data) { | ||
const { host, injector } = block; | ||
const { ctx } = injector; | ||
injector.ctx = block; | ||
injector.ptr = block.start; | ||
const result = fn(host, injector, data); | ||
injector.ptr = block.end; | ||
injector.ctx = ctx; | ||
return result; | ||
} | ||
/** | ||
* Empties content of given block | ||
* @param {BaseBlock} block | ||
*/ | ||
function emptyBlockContent(block) { | ||
if (block.dispose) { | ||
block.dispose(block.scope); | ||
block.dispose = null; | ||
} | ||
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)); | ||
let item = block.start.next; | ||
while (item && item !== block.end) { | ||
let { value, next, prev } = item; | ||
if (isBlock(value)) { | ||
next = value.end.next; | ||
disposeBlock(value); | ||
} else { | ||
domRemove(value); | ||
} | ||
items.splice(pos, 0, item); | ||
prev.next = next; | ||
next.prev = prev; | ||
item = next; | ||
} | ||
@@ -284,22 +391,33 @@ } | ||
/** | ||
* Disposes contents of given block | ||
* Moves contents of `block` after `ref` list item | ||
* @param {Injector} injector | ||
* @param {Block} block | ||
* @param {Object} scope | ||
* @param {boolean} self Remove block item as well | ||
* @param {BaseBlock} block | ||
* @param {LinkedListItem<any>} [ref] | ||
*/ | ||
function dispose(injector, block, scope, self) { | ||
disposeBlock(block, scope, self); | ||
function move(injector, block, ref) { | ||
if (ref && ref.next && ref.next.value === block) { | ||
return; | ||
} | ||
const { items, ctx } = injector; | ||
const ix = items.indexOf(block) + (self ? 0 : 1); | ||
const size = block.deleted; | ||
// Update linked list | ||
const { start, end } = block; | ||
if (size) { | ||
ctx && consume(ctx, block); | ||
const removed = items.splice(ix, size); | ||
if (ref) { | ||
listMoveFragmentAfter(injector.items, start, end, ref); | ||
} else { | ||
listMoveFragmentFirst(injector.items, start, end); | ||
} | ||
for (let i = 0; i < removed.length; i++) { | ||
domRemove(removed[i]); | ||
// Move block contents in DOM | ||
let item = start.next, node; | ||
while (item !== end) { | ||
if (!isBlock(item.value)) { | ||
/** @type {Node} */ | ||
node = item.value; | ||
// NB it’s possible that a single block contains nodes from different | ||
// slots so we have to find anchor for each node individually | ||
domInsert(node, node.parentNode, getAnchorNode(end.next, node.parentNode)); | ||
} | ||
item = item.next; | ||
} | ||
@@ -310,26 +428,17 @@ } | ||
* 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 | ||
* @param {BaseBlock} block | ||
*/ | ||
function disposeBlock(block, scope, self) { | ||
if (block.dispose) { | ||
block.dispose(scope); | ||
block.dispose = null; | ||
} | ||
block.deleted += block.size + (self ? 1 : 0); | ||
block.size = 0; | ||
function disposeBlock(block) { | ||
emptyBlockContent(block); | ||
listDetachFragment(block.injector.items, block.start, block.end); | ||
block.start = block.end = null; | ||
} | ||
/** | ||
* Adds given item into current injector position | ||
* @param {Injector} injector | ||
* @param {InjectorItem} item | ||
* Check if given value is a block | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
function add(injector, item) { | ||
injector.items.splice(injector.ptr++, 0, item); | ||
injector.ctx && markInsert(injector.ctx); | ||
return item; | ||
function isBlock(obj$$1) { | ||
return '$$block' in obj$$1; | ||
} | ||
@@ -339,13 +448,13 @@ | ||
* Get DOM node nearest to given position of items list | ||
* @param {InjectorItem[]} items | ||
* @param {number} ix | ||
* @param {LinkedListItem} item | ||
* @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; | ||
function getAnchorNode(item, parent) { | ||
while (item) { | ||
if (item.value.parentNode === parent) { | ||
return item.value; | ||
} | ||
item = item.next; | ||
} | ||
@@ -355,38 +464,2 @@ } | ||
/** | ||
* @param {Block} block | ||
*/ | ||
function markInsert(block) { | ||
block.inserted++; | ||
block.size++; | ||
} | ||
/** | ||
* 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; | ||
} | ||
/** | ||
* @param {Node} node | ||
@@ -408,4 +481,4 @@ * @param {Node} parent | ||
function domRemove(node) { | ||
const parent = node.parentNode; | ||
parent && parent.removeChild(node); | ||
const { parentNode } = node; | ||
parentNode && parentNode.removeChild(node); | ||
} | ||
@@ -446,2 +519,3 @@ | ||
* @param {Object} scope | ||
* @returns {Object} | ||
*/ | ||
@@ -502,21 +576,23 @@ function setScope(host, scope) { | ||
/** | ||
* Initial block rendering | ||
* @param {Component} host | ||
* @param {Injector} injector | ||
* @param {Function} get | ||
* @returns {BlockContext} | ||
* @returns {FunctionBlock} | ||
*/ | ||
function mountBlock(host, injector, get) { | ||
/** @type {BlockContext} */ | ||
const ctx = { | ||
/** @type {FunctionBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
fn: undefined, | ||
update: undefined | ||
}; | ||
updateBlock(ctx); | ||
return ctx; | ||
update: undefined, | ||
start: null, | ||
end: null | ||
}); | ||
updateBlock(block); | ||
return block; | ||
} | ||
@@ -526,23 +602,24 @@ | ||
* Updated block, described in `ctx` object | ||
* @param {BlockContext} ctx | ||
* @param {FunctionBlock} block | ||
* @returns {number} Returns `1` if block was updated, `0` otherwise | ||
*/ | ||
function updateBlock(ctx) { | ||
function updateBlock(block) { | ||
let updated = 0; | ||
const { host, injector, scope, block: block$$1, update } = ctx; | ||
const fn = ctx.get(host, scope, injector); | ||
const { scope } = block; | ||
const fn = block.get(block.host, scope); | ||
if (ctx.fn !== fn) { | ||
if (block.fn !== fn) { | ||
updated = 1; | ||
// Unmount previously rendered content | ||
ctx.fn && dispose(injector, block$$1, scope, false); | ||
block.fn && emptyBlockContent(block); | ||
// Mount new block content | ||
ctx.update = fn ? run(injector, block$$1, fn, host, scope) : null; | ||
ctx.fn = fn; | ||
} else if (update) { | ||
block.update = fn && run(block, fn, scope); | ||
block.fn = fn; | ||
} else if (block.update) { | ||
// Update rendered result | ||
updated = run(injector, block$$1, update, host, scope) ? 1 : 0; | ||
updated = run(block, block.update, scope) ? 1 : 0; | ||
} | ||
block.injector.ptr = block.end; | ||
return updated; | ||
@@ -552,6 +629,6 @@ } | ||
/** | ||
* @param {BlockContext} ctx | ||
* @param {FunctionBlock} block | ||
*/ | ||
function unmountBlock(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
function unmountBlock(block) { | ||
disposeBlock(block); | ||
} | ||
@@ -565,19 +642,21 @@ | ||
* @param {Function} body A function that renders item of iterated collection | ||
* @returns {IteratorContext} | ||
* @returns {IteratorBlock} | ||
*/ | ||
function mountIterator(host, injector, get, body) { | ||
/** @type {IteratorContext} */ | ||
const ctx = { | ||
/** @type {IteratorBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
body, | ||
block: block(injector), | ||
scope: getScope(host), | ||
index: 0, | ||
rendered: [], | ||
updated: 0 | ||
}; | ||
updateIterator(ctx); | ||
return ctx; | ||
updated: 0, | ||
start: null, | ||
end: null | ||
}); | ||
updateIterator(block); | ||
return block; | ||
} | ||
@@ -587,21 +666,15 @@ | ||
* Updates iterator block defined in `ctx` | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
* @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; | ||
function updateIterator(block) { | ||
run(block, iteratorHost, block); | ||
return block.updated; | ||
} | ||
/** | ||
* | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
*/ | ||
function unmountIterator(ctx) { | ||
const { rendered, injector } = ctx; | ||
let item; | ||
while (item = rendered.pop()) { | ||
dispose(injector, item[0], item[2], true); | ||
} | ||
function unmountIterator(block) { | ||
disposeBlock(block); | ||
} | ||
@@ -613,18 +686,27 @@ | ||
* @param {Injector} injector | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
*/ | ||
function iteratorHost(host, injector, ctx) { | ||
ctx.index = 0; | ||
ctx.updated = 0; | ||
const collection = ctx.get(host, ctx.scope); | ||
function iteratorHost(host, injector, block) { | ||
block.index = 0; | ||
block.updated = 0; | ||
const collection = block.get(host, block.scope); | ||
if (collection && typeof collection.forEach === 'function') { | ||
collection.forEach(iterator, ctx); | ||
collection.forEach(iterator, block); | ||
} | ||
// 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); | ||
trimIteratorItems(block); | ||
} | ||
/** | ||
* Removes remaining iterator items from current context | ||
* @param {IteratorBlock} block | ||
*/ | ||
function trimIteratorItems(block) { | ||
/** @type {LinkedListItem<IteratorItemBlock>} */ | ||
let item = block.injector.ptr.next, listItem; | ||
while (item.value.owner === block) { | ||
block.updated = 1; | ||
listItem = item.value; | ||
item = listItem.end.next; | ||
disposeBlock(listItem); | ||
} | ||
@@ -634,3 +716,3 @@ } | ||
/** | ||
* @this {IteratorContext} | ||
* @this {IteratorBlock} | ||
* @param {*} value | ||
@@ -640,23 +722,39 @@ * @param {*} key | ||
function iterator(value, key) { | ||
const { host, injector, rendered, index } = this; | ||
const { host, injector, index } = this; | ||
const { ptr } = injector; | ||
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; | ||
/** @type {IteratorItemBlock} */ | ||
let rendered = ptr.next.value; | ||
if (rendered.owner === this) { | ||
// We have rendered item, update it | ||
if (rendered.update) { | ||
setScope(host, assign(rendered.scope, localScope)); | ||
if (run(rendered, rendered.update, rendered.scope)) { | ||
this.updated = 1; | ||
} | ||
exitScope(host); | ||
} | ||
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); | ||
/** @type {IteratorItemBlock} */ | ||
rendered = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: enterScope(host, localScope), | ||
dispose: null, | ||
update: undefined, | ||
owner: this, | ||
start: null, | ||
end: null | ||
}); | ||
rendered.update = run(rendered, this.body, rendered.scope); | ||
exitScope(host); | ||
rendered.push([b, update, scope]); | ||
this.updated = 1; | ||
} | ||
injector.ptr = rendered.end; | ||
this.index++; | ||
@@ -672,21 +770,24 @@ } | ||
* @param {Function} body | ||
* @returns {KeyIteratorContext} | ||
* @returns {KeyIteratorBlock} | ||
*/ | ||
function mountKeyIterator(host, injector, get, keyExpr, body) { | ||
/** @type {KeyIteratorContext} */ | ||
const ctx = { | ||
/** @type {KeyIteratorBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
body, | ||
keyExpr, | ||
body, | ||
get, | ||
index: 0, | ||
updated: 0, | ||
rendered: obj(), | ||
block: block(injector), | ||
scope: getScope(host), | ||
index: 0, | ||
updated: 1, | ||
used: null | ||
}; | ||
updateKeyIterator(ctx); | ||
return ctx; | ||
used: null, | ||
start: null, | ||
end: null | ||
}); | ||
updateKeyIterator(block); | ||
return block; | ||
} | ||
@@ -696,23 +797,15 @@ | ||
* Updates iterator block defined in `ctx` | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} block | ||
* @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; | ||
function updateKeyIterator(block) { | ||
run(block, keyIteratorHost, block); | ||
return block.updated; | ||
} | ||
/** | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} 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); | ||
} | ||
} | ||
disposeBlock(ctx); | ||
} | ||
@@ -724,27 +817,20 @@ | ||
* @param {Injector} injector | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} block | ||
*/ | ||
function keyIteratorHost(host, injector, ctx) { | ||
ctx.used = obj(); | ||
ctx.index = 0; | ||
ctx.updated = 1; | ||
function keyIteratorHost(host, injector, block) { | ||
block.used = obj(); | ||
block.index = 0; | ||
block.updated = 0; | ||
const collection = ctx.get(host, ctx.scope); | ||
const collection = block.get(host, block.scope); | ||
if (collection && typeof collection.forEach === 'function') { | ||
collection.forEach(iterator$1, ctx); | ||
collection.forEach(iterator$1, block); | ||
} | ||
// 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; | ||
trimIteratorItems(block); | ||
block.rendered = block.used; | ||
} | ||
/** | ||
* @this {KeyIteratorContext} | ||
* @this {KeyIteratorBlock} | ||
* @param {*} value | ||
@@ -754,24 +840,35 @@ * @param {*} key | ||
function iterator$1(value, key) { | ||
const { host, injector, index, used, rendered, keyExpr, body } = this; | ||
const { host, injector, index, rendered, used, body } = this; | ||
const localScope = { index, key, value }; | ||
const id = keyExpr(value, createScope(host, localScope)); | ||
const id = this.keyExpr(value, createScope(host, localScope)); | ||
let entry = id in rendered ? rendered[id].shift() : null; | ||
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; | ||
move(injector, entry, injector.ptr); | ||
if (entry.update) { | ||
setScope(host, assign(entry.scope, localScope)); | ||
if (run(entry, entry.update, entry.scope)) { | ||
this.updated = 1; | ||
} | ||
exitScope(host); | ||
} | ||
exitScope(host); | ||
} else { | ||
// Create & render new block | ||
const b = block(injector); | ||
const scope = enterScope(host, localScope); | ||
const update = run(injector, b, body, host, scope); | ||
/** @type {IteratorItemBlock} */ | ||
entry = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: enterScope(host, localScope), | ||
dispose: null, | ||
update: undefined, | ||
owner: this, | ||
start: null, | ||
end: null | ||
}); | ||
entry.update = run(entry, body, entry.scope); | ||
this.updated = 1; | ||
exitScope(host); | ||
entry = [b, update, scope]; | ||
} | ||
@@ -787,2 +884,3 @@ | ||
injector.ptr = entry.end; | ||
this.index++; | ||
@@ -1020,9 +1118,5 @@ } | ||
const { slots } = host.componentModel; | ||
const injector = createInjector(elem); | ||
/** | ||
* @param {Component} host | ||
* @param {Object} scope | ||
* @param {Injector} injector | ||
*/ | ||
function blockEntry(host, scope, injector) { | ||
function blockEntry() { | ||
ctx.isDefault = !renderSlot(host, injector); | ||
@@ -1032,3 +1126,3 @@ return ctx.isDefault ? ctx.defaultContent : null; | ||
slots[name] = mountBlock(host, createInjector(elem), blockEntry); | ||
slots[name] = mountBlock(host, injector, blockEntry); | ||
@@ -1411,6 +1505,7 @@ return ctx; | ||
finalizeEvents(input); | ||
updateSlots(elem$$1); | ||
if (changes) { | ||
renderNext(elem$$1, changes); | ||
} else { | ||
updateSlots(elem$$1); | ||
} | ||
@@ -1427,3 +1522,3 @@ } | ||
const { componentModel } = elem$$1; | ||
const { slots, input, dispose: dispose$$1 } = componentModel; | ||
const { slots, input, dispose } = componentModel; | ||
const scope = getScope(elem$$1); | ||
@@ -1447,6 +1542,6 @@ | ||
dispose$$1 && dispose$$1(scope); | ||
dispose && dispose(scope); | ||
for (const slotName in slots) { | ||
disposeBlock(slots[slotName].block, scope, true); | ||
disposeBlock(slots[slotName]); | ||
} | ||
@@ -1494,4 +1589,6 @@ | ||
// (for example, if parent node updated component props). | ||
// Check if it’s still queued then render | ||
if (elem$$1.componentModel.queued) { | ||
// Check if it’s still queued then render. | ||
// Also, component can be unmounted after it’s rendering was scheduled | ||
const { componentModel } = elem$$1; | ||
if (componentModel && componentModel.queued) { | ||
renderComponent(elem$$1, changes); | ||
@@ -1531,2 +1628,3 @@ } | ||
runHook(elem$$1, 'didUpdate', args); | ||
updateSlots(elem$$1); | ||
} | ||
@@ -1670,17 +1768,20 @@ | ||
* @param {string} slotName | ||
* @returns {InnerHtmlContext} | ||
* @returns {InnerHtmlBlock} | ||
*/ | ||
function mountInnerHTML(host, injector, get, slotName) { | ||
/** @type {InnerHtmlContext} */ | ||
const ctx = { | ||
/** @type {InnerHtmlBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
code: null, | ||
slotName | ||
}; | ||
updateInnerHTML(ctx); | ||
return ctx; | ||
slotName, | ||
start: null, | ||
end: null | ||
}); | ||
updateInnerHTML(block); | ||
return block; | ||
} | ||
@@ -1690,25 +1791,25 @@ | ||
* Updates inner HTML of block, defined in `ctx` | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} block | ||
* @returns {number} Returns `1` if inner HTML was updated, `0` otherwise | ||
*/ | ||
function updateInnerHTML(ctx) { | ||
const { host, injector, block: block$$1, scope } = ctx; | ||
const code = ctx.get(host, injector); | ||
let updated = 0; | ||
function updateInnerHTML(block) { | ||
const code = block.get(block.host, block.scope); | ||
if (code !== ctx.code) { | ||
updated = 1; | ||
ctx.code = code; | ||
dispose(injector, block$$1, scope, false); | ||
isDefined(code) && run(injector, block$$1, renderHTML, host, ctx); | ||
if (code !== block.code) { | ||
emptyBlockContent(block); | ||
if (isDefined(block.code = code)) { | ||
run(block, renderHTML, block); | ||
} | ||
block.injector.ptr = block.end; | ||
return 1; | ||
} | ||
return updated; | ||
return 0; | ||
} | ||
/** | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} ctx | ||
*/ | ||
function unmountInnerHTML(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
disposeBlock(ctx); | ||
} | ||
@@ -1719,3 +1820,3 @@ | ||
* @param {Injector} injector | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} ctx | ||
*/ | ||
@@ -1725,3 +1826,3 @@ function renderHTML(host, injector, ctx) { | ||
div.innerHTML = ctx.code; | ||
const cssScope$$1 = host.componentModel.definition.cssScope; | ||
const { cssScope: cssScope$$1 } = host.componentModel.definition; | ||
cssScope$$1 && scopeDOM(div, cssScope$$1); | ||
@@ -1755,17 +1856,20 @@ while (div.firstChild) { | ||
* @param {Object} args | ||
* @return {PartialContext} | ||
* @return {PartialBlock} | ||
*/ | ||
function mountPartial(host, injector, partial, args) { | ||
/** @type {PartialContext} */ | ||
const ctx = { | ||
/** @type {PartialBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
baseScope: getScope(host), | ||
scope: null, | ||
scope: getScope(host), | ||
dispose: null, | ||
childScope: null, | ||
update: null, | ||
partial: null | ||
}; | ||
updatePartial(ctx, partial, args); | ||
return ctx; | ||
partial: null, | ||
start: null, | ||
end: null | ||
}); | ||
updatePartial(block, partial, args); | ||
return block; | ||
} | ||
@@ -1775,3 +1879,3 @@ | ||
* Updates mounted partial | ||
* @param {PartialContext} ctx | ||
* @param {PartialBlock} ctx | ||
* @param {Object} partial | ||
@@ -1782,3 +1886,3 @@ * @param {Object} args | ||
function updatePartial(ctx, partial, args) { | ||
const { host, injector, block: block$$1, baseScope } = ctx; | ||
const { host, injector } = ctx; | ||
let updated = 0; | ||
@@ -1788,8 +1892,8 @@ | ||
// Unmount previously rendered partial | ||
ctx.partial && dispose(injector, block$$1, ctx.scope, false); | ||
ctx.partial && emptyBlockContent(ctx); | ||
// Mount new partial | ||
const scope = ctx.scope = assign(obj(baseScope), partial.defaults, args); | ||
const scope = ctx.childScope = assign(obj(ctx.scope), partial.defaults, args); | ||
setScope(host, scope); | ||
ctx.update = partial ? run(injector, block$$1, partial.body, host, scope) : null; | ||
ctx.update = partial ? run(ctx, partial.body, scope) : null; | ||
ctx.partial = partial; | ||
@@ -1800,4 +1904,4 @@ exitScope(host); | ||
// Update rendered partial | ||
setScope(host, assign(ctx.scope, args)); | ||
if (run(injector, block$$1, ctx.update, host, ctx.scope)) { | ||
const scope = setScope(host, assign(ctx.childScope, args)); | ||
if (run(ctx, ctx.update, scope)) { | ||
updated = 1; | ||
@@ -1808,2 +1912,3 @@ } | ||
injector.ptr = ctx.end; | ||
return updated; | ||
@@ -1813,6 +1918,6 @@ } | ||
/** | ||
* @param {PartialContext} ctx | ||
* @param {PartialBlock} ctx | ||
*/ | ||
function unmountPartial(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
disposeBlock(ctx); | ||
} | ||
@@ -1996,2 +2101,4 @@ | ||
exports.unmountIterator = unmountIterator; | ||
exports.iteratorHost = iteratorHost; | ||
exports.trimIteratorItems = trimIteratorItems; | ||
exports.mountKeyIterator = mountKeyIterator; | ||
@@ -2001,7 +2108,7 @@ exports.updateKeyIterator = updateKeyIterator; | ||
exports.createInjector = createInjector; | ||
exports.block = block; | ||
exports.insert = insert; | ||
exports.injectBlock = injectBlock; | ||
exports.run = run; | ||
exports.insert = insert; | ||
exports.emptyBlockContent = emptyBlockContent; | ||
exports.move = move; | ||
exports.dispose = dispose; | ||
exports.disposeBlock = disposeBlock; | ||
@@ -2008,0 +2115,0 @@ exports.enterScope = enterScope; |
/** | ||
* Creates linted list | ||
* @return {LinkedList} | ||
*/ | ||
function createList() { | ||
return { head: null }; | ||
} | ||
/** | ||
* Creates linked list item | ||
* @template T | ||
* @param {T} value | ||
* @returns {LinkedListItem<T>} | ||
*/ | ||
function createListItem(value) { | ||
return { value, next: null, prev: null }; | ||
} | ||
/** | ||
* Prepends given value to linked list | ||
* @template T | ||
* @param {LinkedList} list | ||
* @param {T} value | ||
* @return {LinkedListItem<T>} | ||
*/ | ||
function listPrependValue(list, value) { | ||
const item = createListItem(value); | ||
if (item.next = list.head) { | ||
item.next.prev = item; | ||
} | ||
return list.head = item; | ||
} | ||
/** | ||
* Inserts given value after given `ref` item | ||
* @template T | ||
* @param {T} value | ||
* @param {LinkedListItem<any>} ref | ||
* @return {LinkedListItem<T>} | ||
*/ | ||
function listInsertValueAfter(value, ref) { | ||
const item = createListItem(value); | ||
const { next } = ref; | ||
ref.next = item; | ||
item.prev = ref; | ||
if (item.next = next) { | ||
next.prev = item; | ||
} | ||
return item; | ||
} | ||
/** | ||
* Moves list fragment with `start` and `end` bounds right after `ref` item | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
* @param {LinkedListItem} ref | ||
*/ | ||
function listMoveFragmentAfter(list, start, end, ref) { | ||
listDetachFragment(list, start, end); | ||
if (end.next = ref.next) { | ||
end.next.prev = end; | ||
} | ||
ref.next = start; | ||
start.prev = ref; | ||
} | ||
/** | ||
* Moves list fragment with `start` and `end` to list head | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
*/ | ||
function listMoveFragmentFirst(list, start, end) { | ||
listDetachFragment(list, start, end); | ||
if (end.next = list.head) { | ||
end.next.prev = end; | ||
} | ||
list.head = start; | ||
} | ||
/** | ||
* Detaches list fragment with `start` and `end` from list | ||
* @param {LinkedList} list | ||
* @param {LinkedListItem} start | ||
* @param {LinkedListItem} end | ||
*/ | ||
function listDetachFragment(list, start, end) { | ||
const { prev } = start; | ||
const { next } = end; | ||
if (prev) { | ||
prev.next = next; | ||
} else { | ||
list.head = next; | ||
} | ||
if (next) { | ||
next.prev = prev; | ||
} | ||
start.prev = end.next = null; | ||
} | ||
/** | ||
* Creates fast object | ||
@@ -158,4 +269,2 @@ * @param {Object} [proto] | ||
const blockKey = '&block'; | ||
/** | ||
@@ -169,5 +278,5 @@ * Creates injector instance for given target, if required | ||
parentNode: target, | ||
items: [], | ||
items: createList(), | ||
ctx: null, | ||
ptr: 0, | ||
ptr: null, | ||
@@ -184,43 +293,2 @@ // NB create `slots` placeholder to promote object to hidden class. | ||
/** | ||
* 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 | ||
@@ -233,3 +301,3 @@ * @param {Injector} injector | ||
let target; | ||
const { slots } = injector; | ||
const { items, slots, ptr } = injector; | ||
@@ -242,34 +310,73 @@ if (slots) { | ||
domInsert(node, target, getAnchorNode(injector.items, injector.ptr, target)); | ||
return add(injector, node); | ||
domInsert(node, target, ptr && getAnchorNode(ptr.next, target)); | ||
injector.ptr = ptr ? listInsertValueAfter(node, ptr) : listPrependValue(items, node); | ||
return node; | ||
} | ||
/** | ||
* Moves contents of given block at `pos` location, effectively updating | ||
* inserted nodes in parent context | ||
* Injects given block | ||
* @template {BaseBlock} T | ||
* @param {Injector} injector | ||
* @param {Block} block | ||
* @param {number} pos | ||
* @param {T} block | ||
* @returns {T} | ||
*/ | ||
function move(injector, block, pos) { | ||
const { items } = injector; | ||
function injectBlock(injector, block) { | ||
const { items, ptr } = injector; | ||
if (items[pos] === block) { | ||
return; | ||
if (ptr) { | ||
block.end = listInsertValueAfter(block, ptr); | ||
block.start = listInsertValueAfter(block, ptr); | ||
} else { | ||
block.end = listPrependValue(items, block); | ||
block.start = listPrependValue(items, block); | ||
} | ||
// Move block contents at given position | ||
const curPos = items.indexOf(block); | ||
const blockItems = items.splice(curPos, block.size + 1); | ||
injector.ptr = block.end; | ||
return block; | ||
} | ||
if (curPos < pos) { | ||
pos -= blockItems.length; | ||
/** | ||
* Runs `fn` template function in context of given `block` | ||
* @param {BaseBlock} block | ||
* @param {Function} fn | ||
* @param {*} data | ||
* @returns {*} Result of `fn` function call | ||
*/ | ||
function run(block, fn, data) { | ||
const { host, injector } = block; | ||
const { ctx } = injector; | ||
injector.ctx = block; | ||
injector.ptr = block.start; | ||
const result = fn(host, injector, data); | ||
injector.ptr = block.end; | ||
injector.ctx = ctx; | ||
return result; | ||
} | ||
/** | ||
* Empties content of given block | ||
* @param {BaseBlock} block | ||
*/ | ||
function emptyBlockContent(block) { | ||
if (block.dispose) { | ||
block.dispose(block.scope); | ||
block.dispose = null; | ||
} | ||
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)); | ||
let item = block.start.next; | ||
while (item && item !== block.end) { | ||
let { value, next, prev } = item; | ||
if (isBlock(value)) { | ||
next = value.end.next; | ||
disposeBlock(value); | ||
} else { | ||
domRemove(value); | ||
} | ||
items.splice(pos, 0, item); | ||
prev.next = next; | ||
next.prev = prev; | ||
item = next; | ||
} | ||
@@ -279,22 +386,33 @@ } | ||
/** | ||
* Disposes contents of given block | ||
* Moves contents of `block` after `ref` list item | ||
* @param {Injector} injector | ||
* @param {Block} block | ||
* @param {Object} scope | ||
* @param {boolean} self Remove block item as well | ||
* @param {BaseBlock} block | ||
* @param {LinkedListItem<any>} [ref] | ||
*/ | ||
function dispose(injector, block, scope, self) { | ||
disposeBlock(block, scope, self); | ||
function move(injector, block, ref) { | ||
if (ref && ref.next && ref.next.value === block) { | ||
return; | ||
} | ||
const { items, ctx } = injector; | ||
const ix = items.indexOf(block) + (self ? 0 : 1); | ||
const size = block.deleted; | ||
// Update linked list | ||
const { start, end } = block; | ||
if (size) { | ||
ctx && consume(ctx, block); | ||
const removed = items.splice(ix, size); | ||
if (ref) { | ||
listMoveFragmentAfter(injector.items, start, end, ref); | ||
} else { | ||
listMoveFragmentFirst(injector.items, start, end); | ||
} | ||
for (let i = 0; i < removed.length; i++) { | ||
domRemove(removed[i]); | ||
// Move block contents in DOM | ||
let item = start.next, node; | ||
while (item !== end) { | ||
if (!isBlock(item.value)) { | ||
/** @type {Node} */ | ||
node = item.value; | ||
// NB it’s possible that a single block contains nodes from different | ||
// slots so we have to find anchor for each node individually | ||
domInsert(node, node.parentNode, getAnchorNode(end.next, node.parentNode)); | ||
} | ||
item = item.next; | ||
} | ||
@@ -305,26 +423,17 @@ } | ||
* 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 | ||
* @param {BaseBlock} block | ||
*/ | ||
function disposeBlock(block, scope, self) { | ||
if (block.dispose) { | ||
block.dispose(scope); | ||
block.dispose = null; | ||
} | ||
block.deleted += block.size + (self ? 1 : 0); | ||
block.size = 0; | ||
function disposeBlock(block) { | ||
emptyBlockContent(block); | ||
listDetachFragment(block.injector.items, block.start, block.end); | ||
block.start = block.end = null; | ||
} | ||
/** | ||
* Adds given item into current injector position | ||
* @param {Injector} injector | ||
* @param {InjectorItem} item | ||
* Check if given value is a block | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
function add(injector, item) { | ||
injector.items.splice(injector.ptr++, 0, item); | ||
injector.ctx && markInsert(injector.ctx); | ||
return item; | ||
function isBlock(obj$$1) { | ||
return '$$block' in obj$$1; | ||
} | ||
@@ -334,13 +443,13 @@ | ||
* Get DOM node nearest to given position of items list | ||
* @param {InjectorItem[]} items | ||
* @param {number} ix | ||
* @param {LinkedListItem} item | ||
* @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; | ||
function getAnchorNode(item, parent) { | ||
while (item) { | ||
if (item.value.parentNode === parent) { | ||
return item.value; | ||
} | ||
item = item.next; | ||
} | ||
@@ -350,38 +459,2 @@ } | ||
/** | ||
* @param {Block} block | ||
*/ | ||
function markInsert(block) { | ||
block.inserted++; | ||
block.size++; | ||
} | ||
/** | ||
* 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; | ||
} | ||
/** | ||
* @param {Node} node | ||
@@ -403,4 +476,4 @@ * @param {Node} parent | ||
function domRemove(node) { | ||
const parent = node.parentNode; | ||
parent && parent.removeChild(node); | ||
const { parentNode } = node; | ||
parentNode && parentNode.removeChild(node); | ||
} | ||
@@ -441,2 +514,3 @@ | ||
* @param {Object} scope | ||
* @returns {Object} | ||
*/ | ||
@@ -497,21 +571,23 @@ function setScope(host, scope) { | ||
/** | ||
* Initial block rendering | ||
* @param {Component} host | ||
* @param {Injector} injector | ||
* @param {Function} get | ||
* @returns {BlockContext} | ||
* @returns {FunctionBlock} | ||
*/ | ||
function mountBlock(host, injector, get) { | ||
/** @type {BlockContext} */ | ||
const ctx = { | ||
/** @type {FunctionBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
fn: undefined, | ||
update: undefined | ||
}; | ||
updateBlock(ctx); | ||
return ctx; | ||
update: undefined, | ||
start: null, | ||
end: null | ||
}); | ||
updateBlock(block); | ||
return block; | ||
} | ||
@@ -521,23 +597,24 @@ | ||
* Updated block, described in `ctx` object | ||
* @param {BlockContext} ctx | ||
* @param {FunctionBlock} block | ||
* @returns {number} Returns `1` if block was updated, `0` otherwise | ||
*/ | ||
function updateBlock(ctx) { | ||
function updateBlock(block) { | ||
let updated = 0; | ||
const { host, injector, scope, block: block$$1, update } = ctx; | ||
const fn = ctx.get(host, scope, injector); | ||
const { scope } = block; | ||
const fn = block.get(block.host, scope); | ||
if (ctx.fn !== fn) { | ||
if (block.fn !== fn) { | ||
updated = 1; | ||
// Unmount previously rendered content | ||
ctx.fn && dispose(injector, block$$1, scope, false); | ||
block.fn && emptyBlockContent(block); | ||
// Mount new block content | ||
ctx.update = fn ? run(injector, block$$1, fn, host, scope) : null; | ||
ctx.fn = fn; | ||
} else if (update) { | ||
block.update = fn && run(block, fn, scope); | ||
block.fn = fn; | ||
} else if (block.update) { | ||
// Update rendered result | ||
updated = run(injector, block$$1, update, host, scope) ? 1 : 0; | ||
updated = run(block, block.update, scope) ? 1 : 0; | ||
} | ||
block.injector.ptr = block.end; | ||
return updated; | ||
@@ -547,6 +624,6 @@ } | ||
/** | ||
* @param {BlockContext} ctx | ||
* @param {FunctionBlock} block | ||
*/ | ||
function unmountBlock(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
function unmountBlock(block) { | ||
disposeBlock(block); | ||
} | ||
@@ -560,19 +637,21 @@ | ||
* @param {Function} body A function that renders item of iterated collection | ||
* @returns {IteratorContext} | ||
* @returns {IteratorBlock} | ||
*/ | ||
function mountIterator(host, injector, get, body) { | ||
/** @type {IteratorContext} */ | ||
const ctx = { | ||
/** @type {IteratorBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
body, | ||
block: block(injector), | ||
scope: getScope(host), | ||
index: 0, | ||
rendered: [], | ||
updated: 0 | ||
}; | ||
updateIterator(ctx); | ||
return ctx; | ||
updated: 0, | ||
start: null, | ||
end: null | ||
}); | ||
updateIterator(block); | ||
return block; | ||
} | ||
@@ -582,21 +661,15 @@ | ||
* Updates iterator block defined in `ctx` | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
* @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; | ||
function updateIterator(block) { | ||
run(block, iteratorHost, block); | ||
return block.updated; | ||
} | ||
/** | ||
* | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
*/ | ||
function unmountIterator(ctx) { | ||
const { rendered, injector } = ctx; | ||
let item; | ||
while (item = rendered.pop()) { | ||
dispose(injector, item[0], item[2], true); | ||
} | ||
function unmountIterator(block) { | ||
disposeBlock(block); | ||
} | ||
@@ -608,18 +681,27 @@ | ||
* @param {Injector} injector | ||
* @param {IteratorContext} ctx | ||
* @param {IteratorBlock} block | ||
*/ | ||
function iteratorHost(host, injector, ctx) { | ||
ctx.index = 0; | ||
ctx.updated = 0; | ||
const collection = ctx.get(host, ctx.scope); | ||
function iteratorHost(host, injector, block) { | ||
block.index = 0; | ||
block.updated = 0; | ||
const collection = block.get(host, block.scope); | ||
if (collection && typeof collection.forEach === 'function') { | ||
collection.forEach(iterator, ctx); | ||
collection.forEach(iterator, block); | ||
} | ||
// 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); | ||
trimIteratorItems(block); | ||
} | ||
/** | ||
* Removes remaining iterator items from current context | ||
* @param {IteratorBlock} block | ||
*/ | ||
function trimIteratorItems(block) { | ||
/** @type {LinkedListItem<IteratorItemBlock>} */ | ||
let item = block.injector.ptr.next, listItem; | ||
while (item.value.owner === block) { | ||
block.updated = 1; | ||
listItem = item.value; | ||
item = listItem.end.next; | ||
disposeBlock(listItem); | ||
} | ||
@@ -629,3 +711,3 @@ } | ||
/** | ||
* @this {IteratorContext} | ||
* @this {IteratorBlock} | ||
* @param {*} value | ||
@@ -635,23 +717,39 @@ * @param {*} key | ||
function iterator(value, key) { | ||
const { host, injector, rendered, index } = this; | ||
const { host, injector, index } = this; | ||
const { ptr } = injector; | ||
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; | ||
/** @type {IteratorItemBlock} */ | ||
let rendered = ptr.next.value; | ||
if (rendered.owner === this) { | ||
// We have rendered item, update it | ||
if (rendered.update) { | ||
setScope(host, assign(rendered.scope, localScope)); | ||
if (run(rendered, rendered.update, rendered.scope)) { | ||
this.updated = 1; | ||
} | ||
exitScope(host); | ||
} | ||
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); | ||
/** @type {IteratorItemBlock} */ | ||
rendered = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: enterScope(host, localScope), | ||
dispose: null, | ||
update: undefined, | ||
owner: this, | ||
start: null, | ||
end: null | ||
}); | ||
rendered.update = run(rendered, this.body, rendered.scope); | ||
exitScope(host); | ||
rendered.push([b, update, scope]); | ||
this.updated = 1; | ||
} | ||
injector.ptr = rendered.end; | ||
this.index++; | ||
@@ -667,21 +765,24 @@ } | ||
* @param {Function} body | ||
* @returns {KeyIteratorContext} | ||
* @returns {KeyIteratorBlock} | ||
*/ | ||
function mountKeyIterator(host, injector, get, keyExpr, body) { | ||
/** @type {KeyIteratorContext} */ | ||
const ctx = { | ||
/** @type {KeyIteratorBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
body, | ||
keyExpr, | ||
body, | ||
get, | ||
index: 0, | ||
updated: 0, | ||
rendered: obj(), | ||
block: block(injector), | ||
scope: getScope(host), | ||
index: 0, | ||
updated: 1, | ||
used: null | ||
}; | ||
updateKeyIterator(ctx); | ||
return ctx; | ||
used: null, | ||
start: null, | ||
end: null | ||
}); | ||
updateKeyIterator(block); | ||
return block; | ||
} | ||
@@ -691,23 +792,15 @@ | ||
* Updates iterator block defined in `ctx` | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} block | ||
* @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; | ||
function updateKeyIterator(block) { | ||
run(block, keyIteratorHost, block); | ||
return block.updated; | ||
} | ||
/** | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} 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); | ||
} | ||
} | ||
disposeBlock(ctx); | ||
} | ||
@@ -719,27 +812,20 @@ | ||
* @param {Injector} injector | ||
* @param {KeyIteratorContext} ctx | ||
* @param {KeyIteratorBlock} block | ||
*/ | ||
function keyIteratorHost(host, injector, ctx) { | ||
ctx.used = obj(); | ||
ctx.index = 0; | ||
ctx.updated = 1; | ||
function keyIteratorHost(host, injector, block) { | ||
block.used = obj(); | ||
block.index = 0; | ||
block.updated = 0; | ||
const collection = ctx.get(host, ctx.scope); | ||
const collection = block.get(host, block.scope); | ||
if (collection && typeof collection.forEach === 'function') { | ||
collection.forEach(iterator$1, ctx); | ||
collection.forEach(iterator$1, block); | ||
} | ||
// 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; | ||
trimIteratorItems(block); | ||
block.rendered = block.used; | ||
} | ||
/** | ||
* @this {KeyIteratorContext} | ||
* @this {KeyIteratorBlock} | ||
* @param {*} value | ||
@@ -749,24 +835,35 @@ * @param {*} key | ||
function iterator$1(value, key) { | ||
const { host, injector, index, used, rendered, keyExpr, body } = this; | ||
const { host, injector, index, rendered, used, body } = this; | ||
const localScope = { index, key, value }; | ||
const id = keyExpr(value, createScope(host, localScope)); | ||
const id = this.keyExpr(value, createScope(host, localScope)); | ||
let entry = id in rendered ? rendered[id].shift() : null; | ||
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; | ||
move(injector, entry, injector.ptr); | ||
if (entry.update) { | ||
setScope(host, assign(entry.scope, localScope)); | ||
if (run(entry, entry.update, entry.scope)) { | ||
this.updated = 1; | ||
} | ||
exitScope(host); | ||
} | ||
exitScope(host); | ||
} else { | ||
// Create & render new block | ||
const b = block(injector); | ||
const scope = enterScope(host, localScope); | ||
const update = run(injector, b, body, host, scope); | ||
/** @type {IteratorItemBlock} */ | ||
entry = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
scope: enterScope(host, localScope), | ||
dispose: null, | ||
update: undefined, | ||
owner: this, | ||
start: null, | ||
end: null | ||
}); | ||
entry.update = run(entry, body, entry.scope); | ||
this.updated = 1; | ||
exitScope(host); | ||
entry = [b, update, scope]; | ||
} | ||
@@ -782,2 +879,3 @@ | ||
injector.ptr = entry.end; | ||
this.index++; | ||
@@ -1015,9 +1113,5 @@ } | ||
const { slots } = host.componentModel; | ||
const injector = createInjector(elem); | ||
/** | ||
* @param {Component} host | ||
* @param {Object} scope | ||
* @param {Injector} injector | ||
*/ | ||
function blockEntry(host, scope, injector) { | ||
function blockEntry() { | ||
ctx.isDefault = !renderSlot(host, injector); | ||
@@ -1027,3 +1121,3 @@ return ctx.isDefault ? ctx.defaultContent : null; | ||
slots[name] = mountBlock(host, createInjector(elem), blockEntry); | ||
slots[name] = mountBlock(host, injector, blockEntry); | ||
@@ -1406,6 +1500,7 @@ return ctx; | ||
finalizeEvents(input); | ||
updateSlots(elem$$1); | ||
if (changes) { | ||
renderNext(elem$$1, changes); | ||
} else { | ||
updateSlots(elem$$1); | ||
} | ||
@@ -1422,3 +1517,3 @@ } | ||
const { componentModel } = elem$$1; | ||
const { slots, input, dispose: dispose$$1 } = componentModel; | ||
const { slots, input, dispose } = componentModel; | ||
const scope = getScope(elem$$1); | ||
@@ -1442,6 +1537,6 @@ | ||
dispose$$1 && dispose$$1(scope); | ||
dispose && dispose(scope); | ||
for (const slotName in slots) { | ||
disposeBlock(slots[slotName].block, scope, true); | ||
disposeBlock(slots[slotName]); | ||
} | ||
@@ -1489,4 +1584,6 @@ | ||
// (for example, if parent node updated component props). | ||
// Check if it’s still queued then render | ||
if (elem$$1.componentModel.queued) { | ||
// Check if it’s still queued then render. | ||
// Also, component can be unmounted after it’s rendering was scheduled | ||
const { componentModel } = elem$$1; | ||
if (componentModel && componentModel.queued) { | ||
renderComponent(elem$$1, changes); | ||
@@ -1526,2 +1623,3 @@ } | ||
runHook(elem$$1, 'didUpdate', args); | ||
updateSlots(elem$$1); | ||
} | ||
@@ -1665,17 +1763,20 @@ | ||
* @param {string} slotName | ||
* @returns {InnerHtmlContext} | ||
* @returns {InnerHtmlBlock} | ||
*/ | ||
function mountInnerHTML(host, injector, get, slotName) { | ||
/** @type {InnerHtmlContext} */ | ||
const ctx = { | ||
/** @type {InnerHtmlBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
scope: getScope(host), | ||
dispose: null, | ||
get, | ||
code: null, | ||
slotName | ||
}; | ||
updateInnerHTML(ctx); | ||
return ctx; | ||
slotName, | ||
start: null, | ||
end: null | ||
}); | ||
updateInnerHTML(block); | ||
return block; | ||
} | ||
@@ -1685,25 +1786,25 @@ | ||
* Updates inner HTML of block, defined in `ctx` | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} block | ||
* @returns {number} Returns `1` if inner HTML was updated, `0` otherwise | ||
*/ | ||
function updateInnerHTML(ctx) { | ||
const { host, injector, block: block$$1, scope } = ctx; | ||
const code = ctx.get(host, injector); | ||
let updated = 0; | ||
function updateInnerHTML(block) { | ||
const code = block.get(block.host, block.scope); | ||
if (code !== ctx.code) { | ||
updated = 1; | ||
ctx.code = code; | ||
dispose(injector, block$$1, scope, false); | ||
isDefined(code) && run(injector, block$$1, renderHTML, host, ctx); | ||
if (code !== block.code) { | ||
emptyBlockContent(block); | ||
if (isDefined(block.code = code)) { | ||
run(block, renderHTML, block); | ||
} | ||
block.injector.ptr = block.end; | ||
return 1; | ||
} | ||
return updated; | ||
return 0; | ||
} | ||
/** | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} ctx | ||
*/ | ||
function unmountInnerHTML(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
disposeBlock(ctx); | ||
} | ||
@@ -1714,3 +1815,3 @@ | ||
* @param {Injector} injector | ||
* @param {InnerHtmlContext} ctx | ||
* @param {InnerHtmlBlock} ctx | ||
*/ | ||
@@ -1720,3 +1821,3 @@ function renderHTML(host, injector, ctx) { | ||
div.innerHTML = ctx.code; | ||
const cssScope$$1 = host.componentModel.definition.cssScope; | ||
const { cssScope: cssScope$$1 } = host.componentModel.definition; | ||
cssScope$$1 && scopeDOM(div, cssScope$$1); | ||
@@ -1750,17 +1851,20 @@ while (div.firstChild) { | ||
* @param {Object} args | ||
* @return {PartialContext} | ||
* @return {PartialBlock} | ||
*/ | ||
function mountPartial(host, injector, partial, args) { | ||
/** @type {PartialContext} */ | ||
const ctx = { | ||
/** @type {PartialBlock} */ | ||
const block = injectBlock(injector, { | ||
$$block: true, | ||
host, | ||
injector, | ||
block: block(injector), | ||
baseScope: getScope(host), | ||
scope: null, | ||
scope: getScope(host), | ||
dispose: null, | ||
childScope: null, | ||
update: null, | ||
partial: null | ||
}; | ||
updatePartial(ctx, partial, args); | ||
return ctx; | ||
partial: null, | ||
start: null, | ||
end: null | ||
}); | ||
updatePartial(block, partial, args); | ||
return block; | ||
} | ||
@@ -1770,3 +1874,3 @@ | ||
* Updates mounted partial | ||
* @param {PartialContext} ctx | ||
* @param {PartialBlock} ctx | ||
* @param {Object} partial | ||
@@ -1777,3 +1881,3 @@ * @param {Object} args | ||
function updatePartial(ctx, partial, args) { | ||
const { host, injector, block: block$$1, baseScope } = ctx; | ||
const { host, injector } = ctx; | ||
let updated = 0; | ||
@@ -1783,8 +1887,8 @@ | ||
// Unmount previously rendered partial | ||
ctx.partial && dispose(injector, block$$1, ctx.scope, false); | ||
ctx.partial && emptyBlockContent(ctx); | ||
// Mount new partial | ||
const scope = ctx.scope = assign(obj(baseScope), partial.defaults, args); | ||
const scope = ctx.childScope = assign(obj(ctx.scope), partial.defaults, args); | ||
setScope(host, scope); | ||
ctx.update = partial ? run(injector, block$$1, partial.body, host, scope) : null; | ||
ctx.update = partial ? run(ctx, partial.body, scope) : null; | ||
ctx.partial = partial; | ||
@@ -1795,4 +1899,4 @@ exitScope(host); | ||
// Update rendered partial | ||
setScope(host, assign(ctx.scope, args)); | ||
if (run(injector, block$$1, ctx.update, host, ctx.scope)) { | ||
const scope = setScope(host, assign(ctx.childScope, args)); | ||
if (run(ctx, ctx.update, scope)) { | ||
updated = 1; | ||
@@ -1803,2 +1907,3 @@ } | ||
injector.ptr = ctx.end; | ||
return updated; | ||
@@ -1808,6 +1913,6 @@ } | ||
/** | ||
* @param {PartialContext} ctx | ||
* @param {PartialBlock} ctx | ||
*/ | ||
function unmountPartial(ctx) { | ||
dispose(ctx.injector, ctx.block, ctx.scope, true); | ||
disposeBlock(ctx); | ||
} | ||
@@ -1982,3 +2087,3 @@ | ||
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 }; | ||
export { get, filter, addDisposeCallback, mountBlock, updateBlock, unmountBlock, mountIterator, updateIterator, unmountIterator, iteratorHost, trimIteratorItems, mountKeyIterator, updateKeyIterator, unmountKeyIterator, createInjector, insert, injectBlock, run, emptyBlockContent, move, 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.12", | ||
"version": "0.1.13", | ||
"description": "EndorphinJS template runtime, embedded with template bundles", | ||
@@ -5,0 +5,0 @@ "main": "./dist/runtime.cjs.js", |
125
types.d.ts
@@ -11,2 +11,6 @@ import { Store } from './lib/store'; | ||
interface BlockDisposeCallback { | ||
(block: BaseBlock): void; | ||
} | ||
interface Component extends Element { | ||
@@ -108,3 +112,3 @@ /** | ||
slots: { | ||
[name: string]: BlockContext | ||
[name: string]: BaseBlock | ||
} | ||
@@ -279,3 +283,3 @@ | ||
*/ | ||
items: InjectorItem[]; | ||
items: LinkedList; | ||
@@ -285,3 +289,3 @@ /** | ||
*/ | ||
ptr: number; | ||
ptr: LinkedListItem<any>; | ||
@@ -291,3 +295,3 @@ /** | ||
*/ | ||
ctx: Block; | ||
ctx: BaseBlock; | ||
@@ -312,29 +316,2 @@ /** | ||
/** | ||
* A structure that holds data about elements owned by given block context | ||
* right below it in `Injector` list | ||
*/ | ||
type Block = { | ||
/** @private */ | ||
'&block': true; | ||
/** | ||
* Number of inserted items in block context | ||
*/ | ||
inserted: number; | ||
/** | ||
* Number of deleted items in block context | ||
*/ | ||
deleted: number; | ||
/** | ||
* Amount of items in current block | ||
*/ | ||
size: number; | ||
/** A function to dispose block contents */ | ||
dispose?: DisposeCallback; | ||
} | ||
interface AttachedEventsMap { | ||
@@ -367,16 +344,49 @@ [event: string]: { | ||
interface BaseContext { | ||
interface SlotContext { | ||
host: Component; | ||
name: string; | ||
isDefault: boolean; | ||
defaultContent: Function; | ||
} | ||
interface StoreUpdateHandler { | ||
(state: object, changes: object): void | ||
} | ||
interface StoreUpdateEntry { | ||
keys?: string[]; | ||
component?: Component; | ||
handler?: StoreUpdateHandler; | ||
} | ||
interface LinkedList { | ||
head: LinkedListItem; | ||
} | ||
interface LinkedListItem<T> { | ||
value: T; | ||
next: LinkedListItem<any> | null; | ||
prev: LinkedListItem<any> | null; | ||
} | ||
interface BaseBlock<T> { | ||
$$block: true; | ||
host: Component; | ||
injector: Injector; | ||
block: Block; | ||
scope: Object; | ||
/** A function to dispose block contents */ | ||
dispose: BlockDisposeCallback | null; | ||
start: LinkedListItem<T>; | ||
end: LinkedListItem<T>; | ||
} | ||
interface BlockContext extends BaseContext { | ||
interface FunctionBlock extends BaseBlock<FunctionBlock> { | ||
get: Function; | ||
fn?: Function, | ||
update?: Function, | ||
fn: Function | undefined; | ||
update: Function | undefined; | ||
} | ||
interface IteratorContext extends BaseContext { | ||
interface IteratorBlock extends BaseBlock<IteratorBlock> { | ||
get: Function; | ||
@@ -386,43 +396,30 @@ body: Function; | ||
updated: number; | ||
rendered: Array<[Block, Function, Object]>; | ||
} | ||
interface KeyIteratorContext extends IteratorContext { | ||
interface KeyIteratorBlock extends IteratorBlock { | ||
keyExpr: Function; | ||
used: { | ||
[key: string]: Array<[Block, Function, Object]> | ||
} | ||
[key: string]: IteratorItemBlock[] | ||
} | null; | ||
rendered: { | ||
[key: string]: Array<[Block, Function, Object]> | ||
} | ||
[key: string]: IteratorItemBlock[] | ||
} | null; | ||
} | ||
interface SlotContext { | ||
host: Component; | ||
name: string; | ||
isDefault: boolean; | ||
defaultContent: Function; | ||
interface IteratorItemBlock extends BaseBlock<IteratorItemBlock> { | ||
update: Function | undefined; | ||
owner: IteratorBlock | KeyIteratorBlock; | ||
} | ||
interface InnerHtmlContext extends BaseContext { | ||
interface InnerHtmlBlock extends BaseBlock<InnerHtmlBlock> { | ||
get: Function; | ||
code?: string; | ||
code: string | null; | ||
slotName: string; | ||
} | ||
interface PartialContext extends BaseContext { | ||
baseScope?: Object; | ||
update?: Function; | ||
partial?: Object; | ||
interface PartialBlock extends BaseBlock<PartialBlock> { | ||
childScope: Object; | ||
update: Function | null; | ||
partial: Object | null; | ||
} | ||
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
348476
10
5734