aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/preact/src/vdom/diff.js
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/preact/src/vdom/diff.js')
-rw-r--r--thirdparty/preact/src/vdom/diff.js247
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);
+ }
+ }
+ }
+}