@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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
348476
10
5734