@bikeshaving/crank
Advanced tools
Comparing version 0.3.6 to 0.3.7
# Changelog | ||
## [0.3.7] - 2020-11-16 | ||
Mostly some changes when trying to get Crank to play nicely with contenteditables | ||
### Fixed | ||
- Fixed TypeScript sourcemaps not being generated in builds (#165). | ||
- Fixed empty arrays or otherwise non-empty element children not clearing out non-Crank DOM mutations (#167). | ||
- Fixed rerenderings updating DOM Text nodes even when the contents have not changed (#169). | ||
## [0.3.6] - 2020-10-13 | ||
### Fixed | ||
- Changed the algorithm of patch slightly so that `setAttribute` is never used with object/function values. | ||
## [0.3.5] - 2020-09-17 | ||
@@ -3,0 +12,0 @@ ### Fixed |
@@ -7,3 +7,3 @@ /** | ||
* elements, and their behavior is determined by the renderer, while elements | ||
* whose tags are components are called “component” elements, and their | ||
* whose tags are functions are called “component” elements, and their | ||
* behavior is determined by the execution of the component. | ||
@@ -30,3 +30,3 @@ */ | ||
* | ||
* The tag is just the empty string, and you can use the empty string in | ||
* This tag is just the empty string, and you can use the empty string in | ||
* createElement calls or transpiler options to avoid having to reference this | ||
@@ -133,3 +133,3 @@ * export directly. | ||
* An object containing the “properties” of an element. These correspond to | ||
* the “attributes” of an element when using JSX syntax. | ||
* the attribute syntax from JSX. | ||
*/ | ||
@@ -139,7 +139,6 @@ props: TagProps<TTag>; | ||
* A value which uniquely identifies an element from its siblings so that it | ||
* can be added/updated/moved/removed by the identity of the key rather than | ||
* its position within the parent. | ||
* can be added/updated/moved/removed by key rather than position. | ||
* | ||
* @remarks | ||
* Passed to the element as the prop "crank-key". | ||
* Passed in createElement() as the prop "crank-key". | ||
*/ | ||
@@ -151,3 +150,3 @@ key: Key; | ||
* @remarks | ||
* Passed to the element as the prop "crank-ref". | ||
* Passed in createElement() as the prop "crank-ref". | ||
*/ | ||
@@ -184,4 +183,4 @@ ref: ((value: unknown) => unknown) | undefined; | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This is mainly important when the nearest | ||
* host is rearranged concurrently. | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
*/ | ||
@@ -188,0 +187,0 @@ _fb: NarrowedChild; |
150
cjs/crank.js
@@ -5,3 +5,2 @@ 'use strict'; | ||
/*** UTILITIES ***/ | ||
const NOOP = () => { }; | ||
@@ -12,11 +11,21 @@ function wrap(value) { | ||
function unwrap(arr) { | ||
return arr.length > 1 ? arr : arr[0]; | ||
return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr; | ||
} | ||
/** | ||
* Ensures a value is an array. | ||
* | ||
* @remarks | ||
* This function pretty much does the same thing as wrap above except it | ||
* handles nulls and iterables, and shallowly clones arrays, so it is | ||
* appropriate for wrapping user-provided children from el.props. | ||
*/ | ||
function arrayify(value) { | ||
return value == null | ||
? [] | ||
: typeof value !== "string" && | ||
typeof value[Symbol.iterator] === "function" | ||
? Array.from(value) | ||
: [value]; | ||
: Array.isArray(value) | ||
? value.slice() | ||
: typeof value === "string" || | ||
typeof value[Symbol.iterator] !== "function" | ||
? [value] | ||
: [...value]; | ||
} | ||
@@ -41,3 +50,3 @@ function isIteratorLike(value) { | ||
* | ||
* The tag is just the empty string, and you can use the empty string in | ||
* This tag is just the empty string, and you can use the empty string in | ||
* createElement calls or transpiler options to avoid having to reference this | ||
@@ -82,3 +91,3 @@ * export directly. | ||
/** | ||
* A flag which is set when the component has been mounted. Used mainly to | ||
* A flag which is set when the element has been mounted. Used mainly to | ||
* detect whether an element is being reused so that we clone it. | ||
@@ -88,3 +97,3 @@ */ | ||
/** | ||
* A flag which is set when the component has committed at least once. | ||
* A flag which is set when the element has committed at least once. | ||
*/ | ||
@@ -96,4 +105,3 @@ const IsCommitted = 1 << 1; | ||
// NOTE: to maximize compatibility between Crank versions, starting with 0.2.0, | ||
// any change to the $$typeof property or public properties will be considered | ||
// a breaking change. | ||
// any change to Element’s properties will be considered a breaking change. | ||
/** | ||
@@ -135,2 +143,5 @@ * Elements are the basic building blocks of Crank applications. They are | ||
// asynchronous components. This may or may not be a good idea. | ||
//this._fb = undefined; | ||
//this._inf = undefined; | ||
//this._onv = undefined; | ||
} | ||
@@ -504,8 +515,8 @@ } | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
// TODO: implement Raw element parse caching | ||
if (oldChild.tag === Portal) { | ||
if (oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
} | ||
} | ||
if (oldChild !== newChild) { | ||
@@ -547,3 +558,3 @@ oldChild.props = newChild.props; | ||
else if (typeof newChild === "string") { | ||
value = renderer.escape(newChild, scope); | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
@@ -573,16 +584,12 @@ return [newChild, value]; | ||
const values = []; | ||
let async = false; | ||
let seen; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
for (let i = 0; i < newChildren.length; i++) { | ||
let child = narrow(newChildren[i]); | ||
if (typeof child === "object" && typeof child.key !== "undefined") { | ||
if (seen === undefined) { | ||
seen = new Set(); | ||
const key = child && child.key; | ||
if (key !== undefined) { | ||
if (seen.has(key)) { | ||
console.error("Duplicate key", key); | ||
} | ||
else { | ||
if (seen.has(child.key)) { | ||
console.error("Duplicate key", child.key); | ||
} | ||
} | ||
seen.add(child.key); | ||
seen.add(key); | ||
} | ||
@@ -594,8 +601,6 @@ let value; | ||
values.push(value); | ||
if (!async && isPromiseLike(value)) { | ||
async = true; | ||
} | ||
isAsync = isAsync || isPromiseLike(value); | ||
} | ||
el._ch = unwrap(newChildren); | ||
if (async) { | ||
if (isAsync) { | ||
let onvalues; | ||
@@ -620,5 +625,4 @@ const values1 = Promise.race([ | ||
function update(renderer, root, host, ctx, scope, el) { | ||
if (typeof el.tag === "function") { | ||
// el._ctx should probably never be undefined here | ||
return el._ctx ? updateCtx(el._ctx) : undefined; | ||
if (el._ctx) { | ||
return updateCtx(el._ctx); | ||
} | ||
@@ -656,4 +660,4 @@ else if (el.tag === Raw) { | ||
let i = 0; | ||
let async = false; | ||
let seen; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
let childrenByKey; | ||
@@ -665,5 +669,5 @@ // TODO: switch to mountChildren if there are no more children | ||
// ALIGNMENT | ||
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined; | ||
let newKey = typeof newChild === "object" ? newChild.key : undefined; | ||
if (seen !== undefined && seen.has(newKey)) { | ||
let oldKey = oldChild && oldChild.key; | ||
let newKey = newChild && newChild.key; | ||
if (newKey !== undefined && seen.has(newKey)) { | ||
console.error("Duplicate key", newKey); | ||
@@ -689,5 +693,2 @@ newKey = undefined; | ||
} | ||
if (!seen) { | ||
seen = new Set(); | ||
} | ||
seen.add(newKey); | ||
@@ -707,5 +708,3 @@ } | ||
newChildren[j] = newChild; | ||
if (!async && isPromiseLike(value)) { | ||
async = true; | ||
} | ||
isAsync = isAsync || isPromiseLike(value); | ||
if (typeof oldChild === "object" && oldChild !== newChild) { | ||
@@ -723,7 +722,6 @@ graveyard.push(oldChild); | ||
} | ||
// TODO: async unmounting | ||
if (childrenByKey !== undefined && childrenByKey.size > 0) { | ||
graveyard.push(...childrenByKey.values()); | ||
} | ||
if (async) { | ||
if (isAsync) { | ||
let values1 = Promise.all(values).finally(() => { | ||
@@ -752,8 +750,12 @@ graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
function commit(renderer, scope, el, values) { | ||
let value = unwrap(values); | ||
if (typeof el.tag === "function") { | ||
if (typeof el._ctx === "object") { | ||
commitCtx(el._ctx, value); | ||
} | ||
if (el._inf) { | ||
el._inf = undefined; | ||
} | ||
if (el._fb) { | ||
el._fb = undefined; | ||
} | ||
let value; | ||
if (el._ctx) { | ||
value = commitCtx(el._ctx, values); | ||
} | ||
else if (el.tag === Portal) { | ||
@@ -765,3 +767,2 @@ if (!(el._f & IsCommitted)) { | ||
renderer.complete(el.props.root); | ||
value = undefined; | ||
} | ||
@@ -777,3 +778,6 @@ else if (el.tag === Raw) { | ||
} | ||
else if (el.tag !== Fragment) { | ||
else if (el.tag === Fragment) { | ||
value = unwrap(values); | ||
} | ||
else { | ||
if (!(el._f & IsCommitted)) { | ||
@@ -790,15 +794,7 @@ el._n = renderer.create(el, scope); | ||
} | ||
if (el._inf) { | ||
el._inf = undefined; | ||
} | ||
if (el._fb) { | ||
el._fb = undefined; | ||
} | ||
return value; | ||
} | ||
function unmount(renderer, host, ctx, el) { | ||
if (typeof el.tag === "function") { | ||
if (typeof el._ctx === "object") { | ||
unmountCtx(el._ctx); | ||
} | ||
if (el._ctx) { | ||
unmountCtx(el._ctx); | ||
ctx = el._ctx; | ||
@@ -1212,5 +1208,4 @@ } | ||
const result1 = result instanceof Promise ? result : Promise.resolve(result); | ||
const block = result1; | ||
const value = result1.then((result) => updateCtxChildren(ctx, result)); | ||
return [block, value]; | ||
return [result1, value]; | ||
} | ||
@@ -1253,3 +1248,2 @@ else { | ||
} | ||
const block = iteration; | ||
const value = iteration.then((iteration) => { | ||
@@ -1277,3 +1271,3 @@ if (!(ctx._f & IsIterating)) { | ||
}); | ||
return [block, value]; | ||
return [iteration, value]; | ||
} | ||
@@ -1327,3 +1321,3 @@ // sync generator component | ||
let [block, value] = stepCtx(ctx); | ||
if (isPromiseLike(block)) { | ||
if (block) { | ||
ctx._ib = block | ||
@@ -1355,2 +1349,3 @@ .catch((err) => { | ||
let resolve; | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
ctx._eb = ctx._ib | ||
@@ -1364,3 +1359,3 @@ .then(() => { | ||
} | ||
if (isPromiseLike(block)) { | ||
if (block) { | ||
return block.catch((err) => { | ||
@@ -1380,3 +1375,2 @@ if (!(ctx._f & IsUpdating)) { | ||
.finally(() => advanceCtx(ctx)); | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
} | ||
@@ -1410,3 +1404,3 @@ return ctx._ev; | ||
} | ||
function commitCtx(ctx, value) { | ||
function commitCtx(ctx, values) { | ||
if (ctx._f & IsUnmounted) { | ||
@@ -1416,6 +1410,6 @@ return; | ||
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) { | ||
for (const child of wrap(value)) { | ||
if (isEventTarget(child)) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
for (const record of ctx._ls) { | ||
child.addEventListener(record.type, record.callback, record.options); | ||
v.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1430,3 +1424,3 @@ } | ||
const record = listeners[i]; | ||
for (const v of wrap(value)) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
@@ -1446,2 +1440,3 @@ v.addEventListener(record.type, record.callback, record.options); | ||
ctx._f &= ~IsUpdating; | ||
const value = unwrap(values); | ||
if (ctx._ss && ctx._ss.size > 0) { | ||
@@ -1458,2 +1453,3 @@ // NOTE: We have to clear the set of callbacks before calling them, because | ||
} | ||
return value; | ||
} | ||
@@ -1460,0 +1456,0 @@ // TODO: async unmounting |
@@ -8,2 +8,4 @@ 'use strict'; | ||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | ||
// TODO: maybe the HasChildren flag can be defined on (Crank) Element flags... | ||
const HasChildren = Symbol.for("crank.HasChildren"); | ||
class DOMRenderer extends crank.Renderer { | ||
@@ -138,27 +140,45 @@ render(children, root, ctx) { | ||
} | ||
if (!("innerHTML" in el.props) && | ||
(children.length !== 0 || node.__cranky)) { | ||
if (("children" in el.props || node[HasChildren]) && | ||
!("innerHTML" in el.props)) { | ||
if (children.length === 0) { | ||
node.textContent = ""; | ||
return; | ||
} | ||
let oldChild = node.firstChild; | ||
let i = 0; | ||
while (oldChild !== null && i < children.length) { | ||
const newChild = children[i]; | ||
if (oldChild === newChild) { | ||
oldChild = oldChild.nextSibling; | ||
i++; | ||
} | ||
else if (typeof newChild === "string") { | ||
if (oldChild.nodeType === Node.TEXT_NODE) { | ||
oldChild.nodeValue = newChild; | ||
else { | ||
let oldChild = node.firstChild; | ||
let i = 0; | ||
while (oldChild !== null && i < children.length) { | ||
const newChild = children[i]; | ||
if (oldChild === newChild) { | ||
oldChild = oldChild.nextSibling; | ||
i++; | ||
} | ||
else if (typeof newChild === "string") { | ||
if (oldChild.nodeType === Node.TEXT_NODE) { | ||
if (oldChild.data !== newChild) { | ||
oldChild.data = newChild; | ||
} | ||
oldChild = oldChild.nextSibling; | ||
} | ||
else { | ||
node.insertBefore(document.createTextNode(newChild), oldChild); | ||
} | ||
i++; | ||
} | ||
else if (oldChild.nodeType === Node.TEXT_NODE) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
else { | ||
node.insertBefore(document.createTextNode(newChild), oldChild); | ||
node.insertBefore(newChild, oldChild); | ||
i++; | ||
// TODO: This is an optimization but we need to think a little more about other cases like prepending. | ||
if (oldChild !== children[i]) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
} | ||
i++; | ||
} | ||
else if (oldChild.nodeType === Node.TEXT_NODE) { | ||
while (oldChild !== null) { | ||
const nextSibling = oldChild.nextSibling; | ||
@@ -168,31 +188,16 @@ node.removeChild(oldChild); | ||
} | ||
else { | ||
node.insertBefore(newChild, oldChild); | ||
i++; | ||
// TODO: this is an optimization for the js frameworks benchmark swap rows, but we need to think a little more about other cases like prepending. | ||
if (oldChild !== children[i]) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
for (; i < children.length; i++) { | ||
const newChild = children[i]; | ||
node.appendChild(typeof newChild === "string" | ||
? document.createTextNode(newChild) | ||
: newChild); | ||
} | ||
} | ||
while (oldChild !== null) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
for (; i < children.length; i++) { | ||
const newChild = children[i]; | ||
node.appendChild(typeof newChild === "string" | ||
? document.createTextNode(newChild) | ||
: newChild); | ||
} | ||
if (children.length > 0) { | ||
node.__cranky = true; | ||
} | ||
else if (node.__cranky) { | ||
node.__cranky = false; | ||
} | ||
} | ||
if (children.length > 0) { | ||
node[HasChildren] = true; | ||
} | ||
else if (node[HasChildren]) { | ||
node[HasChildren] = false; | ||
} | ||
} | ||
@@ -199,0 +204,0 @@ } |
@@ -7,3 +7,3 @@ /** | ||
* elements, and their behavior is determined by the renderer, while elements | ||
* whose tags are components are called “component” elements, and their | ||
* whose tags are functions are called “component” elements, and their | ||
* behavior is determined by the execution of the component. | ||
@@ -30,3 +30,3 @@ */ | ||
* | ||
* The tag is just the empty string, and you can use the empty string in | ||
* This tag is just the empty string, and you can use the empty string in | ||
* createElement calls or transpiler options to avoid having to reference this | ||
@@ -133,3 +133,3 @@ * export directly. | ||
* An object containing the “properties” of an element. These correspond to | ||
* the “attributes” of an element when using JSX syntax. | ||
* the attribute syntax from JSX. | ||
*/ | ||
@@ -139,7 +139,6 @@ props: TagProps<TTag>; | ||
* A value which uniquely identifies an element from its siblings so that it | ||
* can be added/updated/moved/removed by the identity of the key rather than | ||
* its position within the parent. | ||
* can be added/updated/moved/removed by key rather than position. | ||
* | ||
* @remarks | ||
* Passed to the element as the prop "crank-key". | ||
* Passed in createElement() as the prop "crank-key". | ||
*/ | ||
@@ -151,3 +150,3 @@ key: Key; | ||
* @remarks | ||
* Passed to the element as the prop "crank-ref". | ||
* Passed in createElement() as the prop "crank-ref". | ||
*/ | ||
@@ -184,4 +183,4 @@ ref: ((value: unknown) => unknown) | undefined; | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This is mainly important when the nearest | ||
* host is rearranged concurrently. | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
*/ | ||
@@ -188,0 +187,0 @@ _fb: NarrowedChild; |
150
crank.js
/// <reference types="./crank.d.ts" /> | ||
/*** UTILITIES ***/ | ||
const NOOP = () => { }; | ||
@@ -8,11 +7,21 @@ function wrap(value) { | ||
function unwrap(arr) { | ||
return arr.length > 1 ? arr : arr[0]; | ||
return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr; | ||
} | ||
/** | ||
* Ensures a value is an array. | ||
* | ||
* @remarks | ||
* This function pretty much does the same thing as wrap above except it | ||
* handles nulls and iterables, and shallowly clones arrays, so it is | ||
* appropriate for wrapping user-provided children from el.props. | ||
*/ | ||
function arrayify(value) { | ||
return value == null | ||
? [] | ||
: typeof value !== "string" && | ||
typeof value[Symbol.iterator] === "function" | ||
? Array.from(value) | ||
: [value]; | ||
: Array.isArray(value) | ||
? value.slice() | ||
: typeof value === "string" || | ||
typeof value[Symbol.iterator] !== "function" | ||
? [value] | ||
: [...value]; | ||
} | ||
@@ -37,3 +46,3 @@ function isIteratorLike(value) { | ||
* | ||
* The tag is just the empty string, and you can use the empty string in | ||
* This tag is just the empty string, and you can use the empty string in | ||
* createElement calls or transpiler options to avoid having to reference this | ||
@@ -78,3 +87,3 @@ * export directly. | ||
/** | ||
* A flag which is set when the component has been mounted. Used mainly to | ||
* A flag which is set when the element has been mounted. Used mainly to | ||
* detect whether an element is being reused so that we clone it. | ||
@@ -84,3 +93,3 @@ */ | ||
/** | ||
* A flag which is set when the component has committed at least once. | ||
* A flag which is set when the element has committed at least once. | ||
*/ | ||
@@ -92,4 +101,3 @@ const IsCommitted = 1 << 1; | ||
// NOTE: to maximize compatibility between Crank versions, starting with 0.2.0, | ||
// any change to the $$typeof property or public properties will be considered | ||
// a breaking change. | ||
// any change to Element’s properties will be considered a breaking change. | ||
/** | ||
@@ -131,2 +139,5 @@ * Elements are the basic building blocks of Crank applications. They are | ||
// asynchronous components. This may or may not be a good idea. | ||
//this._fb = undefined; | ||
//this._inf = undefined; | ||
//this._onv = undefined; | ||
} | ||
@@ -500,8 +511,8 @@ } | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
// TODO: implement Raw element parse caching | ||
if (oldChild.tag === Portal) { | ||
if (oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
} | ||
} | ||
if (oldChild !== newChild) { | ||
@@ -543,3 +554,3 @@ oldChild.props = newChild.props; | ||
else if (typeof newChild === "string") { | ||
value = renderer.escape(newChild, scope); | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
@@ -569,16 +580,12 @@ return [newChild, value]; | ||
const values = []; | ||
let async = false; | ||
let seen; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
for (let i = 0; i < newChildren.length; i++) { | ||
let child = narrow(newChildren[i]); | ||
if (typeof child === "object" && typeof child.key !== "undefined") { | ||
if (seen === undefined) { | ||
seen = new Set(); | ||
const key = child && child.key; | ||
if (key !== undefined) { | ||
if (seen.has(key)) { | ||
console.error("Duplicate key", key); | ||
} | ||
else { | ||
if (seen.has(child.key)) { | ||
console.error("Duplicate key", child.key); | ||
} | ||
} | ||
seen.add(child.key); | ||
seen.add(key); | ||
} | ||
@@ -590,8 +597,6 @@ let value; | ||
values.push(value); | ||
if (!async && isPromiseLike(value)) { | ||
async = true; | ||
} | ||
isAsync = isAsync || isPromiseLike(value); | ||
} | ||
el._ch = unwrap(newChildren); | ||
if (async) { | ||
if (isAsync) { | ||
let onvalues; | ||
@@ -616,5 +621,4 @@ const values1 = Promise.race([ | ||
function update(renderer, root, host, ctx, scope, el) { | ||
if (typeof el.tag === "function") { | ||
// el._ctx should probably never be undefined here | ||
return el._ctx ? updateCtx(el._ctx) : undefined; | ||
if (el._ctx) { | ||
return updateCtx(el._ctx); | ||
} | ||
@@ -652,4 +656,4 @@ else if (el.tag === Raw) { | ||
let i = 0; | ||
let async = false; | ||
let seen; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
let childrenByKey; | ||
@@ -661,5 +665,5 @@ // TODO: switch to mountChildren if there are no more children | ||
// ALIGNMENT | ||
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined; | ||
let newKey = typeof newChild === "object" ? newChild.key : undefined; | ||
if (seen !== undefined && seen.has(newKey)) { | ||
let oldKey = oldChild && oldChild.key; | ||
let newKey = newChild && newChild.key; | ||
if (newKey !== undefined && seen.has(newKey)) { | ||
console.error("Duplicate key", newKey); | ||
@@ -685,5 +689,2 @@ newKey = undefined; | ||
} | ||
if (!seen) { | ||
seen = new Set(); | ||
} | ||
seen.add(newKey); | ||
@@ -703,5 +704,3 @@ } | ||
newChildren[j] = newChild; | ||
if (!async && isPromiseLike(value)) { | ||
async = true; | ||
} | ||
isAsync = isAsync || isPromiseLike(value); | ||
if (typeof oldChild === "object" && oldChild !== newChild) { | ||
@@ -719,7 +718,6 @@ graveyard.push(oldChild); | ||
} | ||
// TODO: async unmounting | ||
if (childrenByKey !== undefined && childrenByKey.size > 0) { | ||
graveyard.push(...childrenByKey.values()); | ||
} | ||
if (async) { | ||
if (isAsync) { | ||
let values1 = Promise.all(values).finally(() => { | ||
@@ -748,8 +746,12 @@ graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
function commit(renderer, scope, el, values) { | ||
let value = unwrap(values); | ||
if (typeof el.tag === "function") { | ||
if (typeof el._ctx === "object") { | ||
commitCtx(el._ctx, value); | ||
} | ||
if (el._inf) { | ||
el._inf = undefined; | ||
} | ||
if (el._fb) { | ||
el._fb = undefined; | ||
} | ||
let value; | ||
if (el._ctx) { | ||
value = commitCtx(el._ctx, values); | ||
} | ||
else if (el.tag === Portal) { | ||
@@ -761,3 +763,2 @@ if (!(el._f & IsCommitted)) { | ||
renderer.complete(el.props.root); | ||
value = undefined; | ||
} | ||
@@ -773,3 +774,6 @@ else if (el.tag === Raw) { | ||
} | ||
else if (el.tag !== Fragment) { | ||
else if (el.tag === Fragment) { | ||
value = unwrap(values); | ||
} | ||
else { | ||
if (!(el._f & IsCommitted)) { | ||
@@ -786,15 +790,7 @@ el._n = renderer.create(el, scope); | ||
} | ||
if (el._inf) { | ||
el._inf = undefined; | ||
} | ||
if (el._fb) { | ||
el._fb = undefined; | ||
} | ||
return value; | ||
} | ||
function unmount(renderer, host, ctx, el) { | ||
if (typeof el.tag === "function") { | ||
if (typeof el._ctx === "object") { | ||
unmountCtx(el._ctx); | ||
} | ||
if (el._ctx) { | ||
unmountCtx(el._ctx); | ||
ctx = el._ctx; | ||
@@ -1208,5 +1204,4 @@ } | ||
const result1 = result instanceof Promise ? result : Promise.resolve(result); | ||
const block = result1; | ||
const value = result1.then((result) => updateCtxChildren(ctx, result)); | ||
return [block, value]; | ||
return [result1, value]; | ||
} | ||
@@ -1249,3 +1244,2 @@ else { | ||
} | ||
const block = iteration; | ||
const value = iteration.then((iteration) => { | ||
@@ -1273,3 +1267,3 @@ if (!(ctx._f & IsIterating)) { | ||
}); | ||
return [block, value]; | ||
return [iteration, value]; | ||
} | ||
@@ -1323,3 +1317,3 @@ // sync generator component | ||
let [block, value] = stepCtx(ctx); | ||
if (isPromiseLike(block)) { | ||
if (block) { | ||
ctx._ib = block | ||
@@ -1351,2 +1345,3 @@ .catch((err) => { | ||
let resolve; | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
ctx._eb = ctx._ib | ||
@@ -1360,3 +1355,3 @@ .then(() => { | ||
} | ||
if (isPromiseLike(block)) { | ||
if (block) { | ||
return block.catch((err) => { | ||
@@ -1376,3 +1371,2 @@ if (!(ctx._f & IsUpdating)) { | ||
.finally(() => advanceCtx(ctx)); | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
} | ||
@@ -1406,3 +1400,3 @@ return ctx._ev; | ||
} | ||
function commitCtx(ctx, value) { | ||
function commitCtx(ctx, values) { | ||
if (ctx._f & IsUnmounted) { | ||
@@ -1412,6 +1406,6 @@ return; | ||
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) { | ||
for (const child of wrap(value)) { | ||
if (isEventTarget(child)) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
for (const record of ctx._ls) { | ||
child.addEventListener(record.type, record.callback, record.options); | ||
v.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1426,3 +1420,3 @@ } | ||
const record = listeners[i]; | ||
for (const v of wrap(value)) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
@@ -1442,2 +1436,3 @@ v.addEventListener(record.type, record.callback, record.options); | ||
ctx._f &= ~IsUpdating; | ||
const value = unwrap(values); | ||
if (ctx._ss && ctx._ss.size > 0) { | ||
@@ -1454,2 +1449,3 @@ // NOTE: We have to clear the set of callbacks before calling them, because | ||
} | ||
return value; | ||
} | ||
@@ -1456,0 +1452,0 @@ // TODO: async unmounting |
91
dom.js
@@ -5,2 +5,4 @@ /// <reference types="./dom.d.ts" /> | ||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | ||
// TODO: maybe the HasChildren flag can be defined on (Crank) Element flags... | ||
const HasChildren = Symbol.for("crank.HasChildren"); | ||
class DOMRenderer extends Renderer { | ||
@@ -135,27 +137,45 @@ render(children, root, ctx) { | ||
} | ||
if (!("innerHTML" in el.props) && | ||
(children.length !== 0 || node.__cranky)) { | ||
if (("children" in el.props || node[HasChildren]) && | ||
!("innerHTML" in el.props)) { | ||
if (children.length === 0) { | ||
node.textContent = ""; | ||
return; | ||
} | ||
let oldChild = node.firstChild; | ||
let i = 0; | ||
while (oldChild !== null && i < children.length) { | ||
const newChild = children[i]; | ||
if (oldChild === newChild) { | ||
oldChild = oldChild.nextSibling; | ||
i++; | ||
} | ||
else if (typeof newChild === "string") { | ||
if (oldChild.nodeType === Node.TEXT_NODE) { | ||
oldChild.nodeValue = newChild; | ||
else { | ||
let oldChild = node.firstChild; | ||
let i = 0; | ||
while (oldChild !== null && i < children.length) { | ||
const newChild = children[i]; | ||
if (oldChild === newChild) { | ||
oldChild = oldChild.nextSibling; | ||
i++; | ||
} | ||
else if (typeof newChild === "string") { | ||
if (oldChild.nodeType === Node.TEXT_NODE) { | ||
if (oldChild.data !== newChild) { | ||
oldChild.data = newChild; | ||
} | ||
oldChild = oldChild.nextSibling; | ||
} | ||
else { | ||
node.insertBefore(document.createTextNode(newChild), oldChild); | ||
} | ||
i++; | ||
} | ||
else if (oldChild.nodeType === Node.TEXT_NODE) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
else { | ||
node.insertBefore(document.createTextNode(newChild), oldChild); | ||
node.insertBefore(newChild, oldChild); | ||
i++; | ||
// TODO: This is an optimization but we need to think a little more about other cases like prepending. | ||
if (oldChild !== children[i]) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
} | ||
i++; | ||
} | ||
else if (oldChild.nodeType === Node.TEXT_NODE) { | ||
while (oldChild !== null) { | ||
const nextSibling = oldChild.nextSibling; | ||
@@ -165,31 +185,16 @@ node.removeChild(oldChild); | ||
} | ||
else { | ||
node.insertBefore(newChild, oldChild); | ||
i++; | ||
// TODO: this is an optimization for the js frameworks benchmark swap rows, but we need to think a little more about other cases like prepending. | ||
if (oldChild !== children[i]) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
for (; i < children.length; i++) { | ||
const newChild = children[i]; | ||
node.appendChild(typeof newChild === "string" | ||
? document.createTextNode(newChild) | ||
: newChild); | ||
} | ||
} | ||
while (oldChild !== null) { | ||
const nextSibling = oldChild.nextSibling; | ||
node.removeChild(oldChild); | ||
oldChild = nextSibling; | ||
} | ||
for (; i < children.length; i++) { | ||
const newChild = children[i]; | ||
node.appendChild(typeof newChild === "string" | ||
? document.createTextNode(newChild) | ||
: newChild); | ||
} | ||
if (children.length > 0) { | ||
node.__cranky = true; | ||
} | ||
else if (node.__cranky) { | ||
node.__cranky = false; | ||
} | ||
} | ||
if (children.length > 0) { | ||
node[HasChildren] = true; | ||
} | ||
else if (node[HasChildren]) { | ||
node[HasChildren] = false; | ||
} | ||
} | ||
@@ -196,0 +201,0 @@ } |
{ | ||
"name": "@bikeshaving/crank", | ||
"version": "0.3.6", | ||
"version": "0.3.7", | ||
"description": "Write JSX-driven components with functions, promises and generators.", | ||
@@ -122,22 +122,22 @@ "homepage": "https://crank.js.org", | ||
"devDependencies": { | ||
"@types/jest": "^26.0.3", | ||
"@typescript-eslint/eslint-plugin": "^3.5.0", | ||
"@typescript-eslint/parser": "^3.5.0", | ||
"core-js": "^3.6.5", | ||
"eslint": "^7.3.1", | ||
"eslint-config-prettier": "^6.10.1", | ||
"eslint-plugin-jest": "^23.17.1", | ||
"@types/jest": "^26.0.15", | ||
"@typescript-eslint/eslint-plugin": "^4.7.0", | ||
"@typescript-eslint/parser": "^4.7.0", | ||
"core-js": "^3.7.0", | ||
"eslint": "^7.13.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"eslint-plugin-jest": "^24.1.3", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"eslint-plugin-react": "^7.20.3", | ||
"husky": "^4.2.5", | ||
"jest": "^26.1.0", | ||
"lint-staged": "^10.2.11", | ||
"eslint-plugin-react": "^7.21.5", | ||
"husky": "^4.3.0", | ||
"jest": "^26.6.3", | ||
"lint-staged": "^10.5.1", | ||
"magic-string": "^0.25.7", | ||
"prettier": "^2.0.4", | ||
"rollup": "^2.18.1", | ||
"rollup-plugin-typescript2": "^0.27.1", | ||
"shx": "^0.3.2", | ||
"ts-jest": "^26.1.1", | ||
"ts-transform-import-path-rewrite": "^0.2.1", | ||
"typescript": "^3.9.6" | ||
"rollup": "^2.33.1", | ||
"rollup-plugin-typescript2": "^0.29.0", | ||
"shx": "^0.3.3", | ||
"ts-jest": "^26.4.4", | ||
"ts-transform-import-path-rewrite": "^0.3.0", | ||
"typescript": "^4.0.5" | ||
}, | ||
@@ -144,0 +144,0 @@ "publishConfig": { |
@@ -7,3 +7,3 @@ /** | ||
* elements, and their behavior is determined by the renderer, while elements | ||
* whose tags are components are called “component” elements, and their | ||
* whose tags are functions are called “component” elements, and their | ||
* behavior is determined by the execution of the component. | ||
@@ -30,3 +30,3 @@ */ | ||
* | ||
* The tag is just the empty string, and you can use the empty string in | ||
* This tag is just the empty string, and you can use the empty string in | ||
* createElement calls or transpiler options to avoid having to reference this | ||
@@ -133,3 +133,3 @@ * export directly. | ||
* An object containing the “properties” of an element. These correspond to | ||
* the “attributes” of an element when using JSX syntax. | ||
* the attribute syntax from JSX. | ||
*/ | ||
@@ -139,7 +139,6 @@ props: TagProps<TTag>; | ||
* A value which uniquely identifies an element from its siblings so that it | ||
* can be added/updated/moved/removed by the identity of the key rather than | ||
* its position within the parent. | ||
* can be added/updated/moved/removed by key rather than position. | ||
* | ||
* @remarks | ||
* Passed to the element as the prop "crank-key". | ||
* Passed in createElement() as the prop "crank-key". | ||
*/ | ||
@@ -151,3 +150,3 @@ key: Key; | ||
* @remarks | ||
* Passed to the element as the prop "crank-ref". | ||
* Passed in createElement() as the prop "crank-ref". | ||
*/ | ||
@@ -184,4 +183,4 @@ ref: ((value: unknown) => unknown) | undefined; | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This is mainly important when the nearest | ||
* host is rearranged concurrently. | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
*/ | ||
@@ -188,0 +187,0 @@ _fb: NarrowedChild; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
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
653083