diff options
Diffstat (limited to 'thirdparty/preact/src/vdom/diff.js')
-rw-r--r-- | thirdparty/preact/src/vdom/diff.js | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/thirdparty/preact/src/vdom/diff.js b/thirdparty/preact/src/vdom/diff.js new file mode 100644 index 000000000..691434e98 --- /dev/null +++ b/thirdparty/preact/src/vdom/diff.js @@ -0,0 +1,247 @@ +import { ATTR_KEY } from '../constants'; +import { isString, isFunction } from '../util'; +import { isSameNodeType, isNamedNode } from './index'; +import { isFunctionalComponent, buildFunctionalComponent } from './functional-component'; +import { buildComponentFromVNode } from './component'; +import { setAccessor } from '../dom/index'; +import { createNode, collectNode } from '../dom/recycler'; +import { unmountComponent } from './component'; + + +/** Diff recursion count, used to track the end of the diff cycle. */ +export const mounts = []; + +/** Diff recursion count, used to track the end of the diff cycle. */ +export let diffLevel = 0; + +let isSvgMode = false; + + +export function flushMounts() { + let c; + while ((c=mounts.pop())) { + if (c.componentDidMount) c.componentDidMount(); + } +} + + +/** Apply differences in a given vnode (and it's deep children) to a real DOM Node. + * @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode` + * @param {VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure + * @returns {Element} dom The created/mutated element + * @private + */ +export function diff(dom, vnode, context, mountAll, parent, componentRoot) { + if (!diffLevel++) isSvgMode = parent instanceof SVGElement; + let ret = idiff(dom, vnode, context, mountAll); + if (parent && ret.parentNode!==parent) parent.appendChild(ret); + if (!--diffLevel && !componentRoot) flushMounts(); + return ret; +} + + +function idiff(dom, vnode, context, mountAll) { + let originalAttributes = vnode && vnode.attributes; + + while (isFunctionalComponent(vnode)) { + vnode = buildFunctionalComponent(vnode, context); + } + + if (vnode==null) vnode = ''; + + if (isString(vnode)) { + if (dom) { + if (dom instanceof Text && dom.parentNode) { + dom.nodeValue = vnode; + return dom; + } + recollectNodeTree(dom); + } + return document.createTextNode(vnode); + } + + if (isFunction(vnode.nodeName)) { + return buildComponentFromVNode(dom, vnode, context, mountAll); + } + + let out = dom, + nodeName = vnode.nodeName, + prevSvgMode = isSvgMode; + + if (!isString(nodeName)) { + nodeName = String(nodeName); + } + + isSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode; + + if (!dom) { + out = createNode(nodeName, isSvgMode); + } + else if (!isNamedNode(dom, nodeName)) { + out = createNode(nodeName, isSvgMode); + // move children into the replacement node + while (dom.firstChild) out.appendChild(dom.firstChild); + // reclaim element nodes + recollectNodeTree(dom); + } + + // fast-path for elements containing a single TextNode: + if (vnode.children && vnode.children.length===1 && typeof vnode.children[0]==='string' && out.childNodes.length===1 && out.firstChild instanceof Text) { + out.firstChild.nodeValue = vnode.children[0]; + } + else if (vnode.children || out.firstChild) { + innerDiffNode(out, vnode.children, context, mountAll); + } + + let props = out[ATTR_KEY]; + if (!props) { + out[ATTR_KEY] = props = {}; + for (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value; + } + + diffAttributes(out, vnode.attributes, props); + + if (originalAttributes && typeof originalAttributes.ref==='function') { + (props.ref = originalAttributes.ref)(out); + } + + isSvgMode = prevSvgMode; + + return out; +} + + +/** Apply child and attribute changes between a VNode and a DOM Node to the DOM. */ +function innerDiffNode(dom, vchildren, context, mountAll) { + let originalChildren = dom.childNodes, + children = [], + keyed = {}, + keyedLen = 0, + min = 0, + len = originalChildren.length, + childrenLen = 0, + vlen = vchildren && vchildren.length, + j, c, vchild, child; + + if (len) { + for (let i=0; i<len; i++) { + let child = originalChildren[i], + key = vlen ? ((c = child._component) ? c.__key : (c = child[ATTR_KEY]) ? c.key : null) : null; + if (key || key===0) { + keyedLen++; + keyed[key] = child; + } + else { + children[childrenLen++] = child; + } + } + } + + if (vlen) { + for (let i=0; i<vlen; i++) { + vchild = vchildren[i]; + child = null; + + // if (isFunctionalComponent(vchild)) { + // vchild = buildFunctionalComponent(vchild); + // } + + // attempt to find a node based on key matching + let key = vchild.key; + if (key!=null) { + if (keyedLen && key in keyed) { + child = keyed[key]; + keyed[key] = undefined; + keyedLen--; + } + } + // attempt to pluck a node of the same type from the existing children + else if (!child && min<childrenLen) { + for (j=min; j<childrenLen; j++) { + c = children[j]; + if (c && isSameNodeType(c, vchild)) { + child = c; + children[j] = undefined; + if (j===childrenLen-1) childrenLen--; + if (j===min) min++; + break; + } + } + if (!child && min<childrenLen && isFunction(vchild.nodeName) && mountAll) { + child = children[min]; + children[min++] = undefined; + } + } + + // morph the matched/found/created DOM child to match vchild (deep) + child = idiff(child, vchild, context, mountAll); + + if (child && child!==dom && child!==originalChildren[i]) { + dom.insertBefore(child, originalChildren[i] || null); + } + } + } + + + if (keyedLen) { + for (let i in keyed) if (keyed[i]) recollectNodeTree(keyed[i]); + } + + // remove orphaned children + if (min<childrenLen) { + removeOrphanedChildren(children); + } +} + + +/** Reclaim children that were unreferenced in the desired VTree */ +export function removeOrphanedChildren(children, unmountOnly) { + for (let i=children.length; i--; ) { + if (children[i]) { + recollectNodeTree(children[i], unmountOnly); + } + } +} + + +/** Reclaim an entire tree of nodes, starting at the root. */ +export function recollectNodeTree(node, unmountOnly) { + // @TODO: Need to make a call on whether Preact should remove nodes not created by itself. + // Currently it *does* remove them. Discussion: https://github.com/developit/preact/issues/39 + //if (!node[ATTR_KEY]) return; + + let component = node._component; + if (component) { + unmountComponent(component, !unmountOnly); + } + else { + if (node[ATTR_KEY] && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null); + + if (!unmountOnly) { + collectNode(node); + } + + if (node.childNodes && node.childNodes.length) { + removeOrphanedChildren(node.childNodes, unmountOnly); + } + } +} + + +/** Apply differences in attributes from a VNode to the given DOM Node. */ +function diffAttributes(dom, attrs, old) { + for (let name in old) { + if (!(attrs && name in attrs) && old[name]!=null) { + setAccessor(dom, name, null, old[name], isSvgMode); + } + } + + // new & updated + if (attrs) { + for (let name in attrs) { + if (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name])) { + setAccessor(dom, name, attrs[name], old[name], isSvgMode); + } + } + } +} |