aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/preact/src/vdom
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-04 11:50:26 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-04 11:50:26 +0200
commit133a8c672c26609e9d56d4e184b779ca26971a8c (patch)
treef3aa4779a75279c95553cd68dd2a3bbe5e5b9dde /thirdparty/preact/src/vdom
parent0697c987c5314232056a86fa128b518c866d2f12 (diff)
parent30b577138dda685f65a8529be1866afa6e321845 (diff)
downloadwallet-core-133a8c672c26609e9d56d4e184b779ca26971a8c.tar.xz
Merge commit '30b577138dda685f65a8529be1866afa6e321845' as 'thirdparty/preact'
Diffstat (limited to 'thirdparty/preact/src/vdom')
-rw-r--r--thirdparty/preact/src/vdom/component-recycler.js32
-rw-r--r--thirdparty/preact/src/vdom/component.js270
-rw-r--r--thirdparty/preact/src/vdom/diff.js247
-rw-r--r--thirdparty/preact/src/vdom/functional-component.js25
-rw-r--r--thirdparty/preact/src/vdom/index.js50
5 files changed, 624 insertions, 0 deletions
diff --git a/thirdparty/preact/src/vdom/component-recycler.js b/thirdparty/preact/src/vdom/component-recycler.js
new file mode 100644
index 000000000..a70f0ece0
--- /dev/null
+++ b/thirdparty/preact/src/vdom/component-recycler.js
@@ -0,0 +1,32 @@
+import { Component } from '../component';
+
+/** Retains a pool of Components for re-use, keyed on component name.
+ * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding.
+ * @private
+ */
+const components = {};
+
+
+export function collectComponent(component) {
+ let name = component.constructor.name,
+ list = components[name];
+ if (list) list.push(component);
+ else components[name] = [component];
+}
+
+
+export function createComponent(Ctor, props, context) {
+ let inst = new Ctor(props, context),
+ list = components[Ctor.name];
+ Component.call(inst, props, context);
+ if (list) {
+ for (let i=list.length; i--; ) {
+ if (list[i].constructor===Ctor) {
+ inst.nextBase = list[i].nextBase;
+ list.splice(i, 1);
+ break;
+ }
+ }
+ }
+ return inst;
+}
diff --git a/thirdparty/preact/src/vdom/component.js b/thirdparty/preact/src/vdom/component.js
new file mode 100644
index 000000000..bb2e4fa5d
--- /dev/null
+++ b/thirdparty/preact/src/vdom/component.js
@@ -0,0 +1,270 @@
+import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants';
+import options from '../options';
+import { isFunction, clone, extend } from '../util';
+import { enqueueRender } from '../render-queue';
+import { getNodeProps } from './index';
+import { diff, mounts, diffLevel, flushMounts, removeOrphanedChildren, recollectNodeTree } from './diff';
+import { isFunctionalComponent, buildFunctionalComponent } from './functional-component';
+import { createComponent, collectComponent } from './component-recycler';
+import { removeNode } from '../dom/index';
+
+
+
+/** Set a component's `props` (generally derived from JSX attributes).
+ * @param {Object} props
+ * @param {Object} [opts]
+ * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.
+ * @param {boolean} [opts.render=true] If `false`, no render will be triggered.
+ */
+export function setComponentProps(component, props, opts, context, mountAll) {
+ if (component._disable) return;
+ component._disable = true;
+
+ if ((component.__ref = props.ref)) delete props.ref;
+ if ((component.__key = props.key)) delete props.key;
+
+ if (!component.base || mountAll) {
+ if (component.componentWillMount) component.componentWillMount();
+ }
+ else if (component.componentWillReceiveProps) {
+ component.componentWillReceiveProps(props, context);
+ }
+
+ if (context && context!==component.context) {
+ if (!component.prevContext) component.prevContext = component.context;
+ component.context = context;
+ }
+
+ if (!component.prevProps) component.prevProps = component.props;
+ component.props = props;
+
+ component._disable = false;
+
+ if (opts!==NO_RENDER) {
+ if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) {
+ renderComponent(component, SYNC_RENDER, mountAll);
+ }
+ else {
+ enqueueRender(component);
+ }
+ }
+
+ if (component.__ref) component.__ref(component);
+}
+
+
+
+/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
+ * @param {Component} component
+ * @param {Object} [opts]
+ * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one.
+ * @private
+ */
+export function renderComponent(component, opts, mountAll, isChild) {
+ if (component._disable) return;
+
+ let skip, rendered,
+ props = component.props,
+ state = component.state,
+ context = component.context,
+ previousProps = component.prevProps || props,
+ previousState = component.prevState || state,
+ previousContext = component.prevContext || context,
+ isUpdate = component.base,
+ nextBase = component.nextBase,
+ initialBase = isUpdate || nextBase,
+ initialChildComponent = component._component,
+ inst, cbase;
+
+ // if updating
+ if (isUpdate) {
+ component.props = previousProps;
+ component.state = previousState;
+ component.context = previousContext;
+ if (opts!==FORCE_RENDER
+ && component.shouldComponentUpdate
+ && component.shouldComponentUpdate(props, state, context) === false) {
+ skip = true;
+ }
+ else if (component.componentWillUpdate) {
+ component.componentWillUpdate(props, state, context);
+ }
+ component.props = props;
+ component.state = state;
+ component.context = context;
+ }
+
+ component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
+ component._dirty = false;
+
+ if (!skip) {
+ if (component.render) rendered = component.render(props, state, context);
+
+ // context to pass to the child, can be updated via (grand-)parent component
+ if (component.getChildContext) {
+ context = extend(clone(context), component.getChildContext());
+ }
+
+ while (isFunctionalComponent(rendered)) {
+ rendered = buildFunctionalComponent(rendered, context);
+ }
+
+ let childComponent = rendered && rendered.nodeName,
+ toUnmount, base;
+
+ if (isFunction(childComponent)) {
+ // set up high order component link
+
+
+ inst = initialChildComponent;
+ let childProps = getNodeProps(rendered);
+
+ if (inst && inst.constructor===childComponent) {
+ setComponentProps(inst, childProps, SYNC_RENDER, context);
+ }
+ else {
+ toUnmount = inst;
+
+ inst = createComponent(childComponent, childProps, context);
+ inst.nextBase = inst.nextBase || nextBase;
+ inst._parentComponent = component;
+ component._component = inst;
+ setComponentProps(inst, childProps, NO_RENDER, context);
+ renderComponent(inst, SYNC_RENDER, mountAll, true);
+ }
+
+ base = inst.base;
+ }
+ else {
+ cbase = initialBase;
+
+ // destroy high order component link
+ toUnmount = initialChildComponent;
+ if (toUnmount) {
+ cbase = component._component = null;
+ }
+
+ if (initialBase || opts===SYNC_RENDER) {
+ if (cbase) cbase._component = null;
+ base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true);
+ }
+ }
+
+ if (initialBase && base!==initialBase && inst!==initialChildComponent) {
+ let baseParent = initialBase.parentNode;
+ if (baseParent && base!==baseParent) {
+ baseParent.replaceChild(base, initialBase);
+ }
+
+ if (!cbase && !toUnmount && component._parentComponent) {
+ initialBase._component = null;
+ recollectNodeTree(initialBase);
+ }
+ }
+
+ if (toUnmount) {
+ unmountComponent(toUnmount, base!==initialBase);
+ }
+
+ component.base = base;
+ if (base && !isChild) {
+ let componentRef = component,
+ t = component;
+ while ((t=t._parentComponent)) { componentRef = t; }
+ base._component = componentRef;
+ base._componentConstructor = componentRef.constructor;
+ }
+ }
+
+ if (!isUpdate || mountAll) {
+ mounts.unshift(component);
+ }
+ else if (!skip && component.componentDidUpdate) {
+ component.componentDidUpdate(previousProps, previousState, previousContext);
+ }
+
+ let cb = component._renderCallbacks, fn;
+ if (cb) while ( (fn = cb.pop()) ) fn.call(component);
+
+ if (!diffLevel && !isChild) flushMounts();
+}
+
+
+
+/** Apply the Component referenced by a VNode to the DOM.
+ * @param {Element} dom The DOM node to mutate
+ * @param {VNode} vnode A Component-referencing VNode
+ * @returns {Element} dom The created/mutated element
+ * @private
+ */
+export function buildComponentFromVNode(dom, vnode, context, mountAll) {
+ let c = dom && dom._component,
+ oldDom = dom,
+ isDirectOwner = c && dom._componentConstructor===vnode.nodeName,
+ isOwner = isDirectOwner,
+ props = getNodeProps(vnode);
+ while (c && !isOwner && (c=c._parentComponent)) {
+ isOwner = c.constructor===vnode.nodeName;
+ }
+
+ if (c && isOwner && (!mountAll || c._component)) {
+ setComponentProps(c, props, ASYNC_RENDER, context, mountAll);
+ dom = c.base;
+ }
+ else {
+ if (c && !isDirectOwner) {
+ unmountComponent(c, true);
+ dom = oldDom = null;
+ }
+
+ c = createComponent(vnode.nodeName, props, context);
+ if (dom && !c.nextBase) c.nextBase = dom;
+ setComponentProps(c, props, SYNC_RENDER, context, mountAll);
+ dom = c.base;
+
+ if (oldDom && dom!==oldDom) {
+ oldDom._component = null;
+ recollectNodeTree(oldDom);
+ }
+ }
+
+ return dom;
+}
+
+
+
+/** Remove a component from the DOM and recycle it.
+ * @param {Element} dom A DOM node from which to unmount the given Component
+ * @param {Component} component The Component instance to unmount
+ * @private
+ */
+export function unmountComponent(component, remove) {
+ // console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`);
+ let base = component.base;
+
+ component._disable = true;
+
+ if (component.componentWillUnmount) component.componentWillUnmount();
+
+ component.base = null;
+
+ // recursively tear down & recollect high-order component children:
+ let inner = component._component;
+ if (inner) {
+ unmountComponent(inner, remove);
+ }
+ else if (base) {
+ if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null);
+
+ component.nextBase = base;
+
+ if (remove) {
+ removeNode(base);
+ collectComponent(component);
+ }
+ removeOrphanedChildren(base.childNodes, !remove);
+ }
+
+ if (component.__ref) component.__ref(null);
+ if (component.componentDidUnmount) component.componentDidUnmount();
+}
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);
+ }
+ }
+ }
+}
diff --git a/thirdparty/preact/src/vdom/functional-component.js b/thirdparty/preact/src/vdom/functional-component.js
new file mode 100644
index 000000000..04bc5a464
--- /dev/null
+++ b/thirdparty/preact/src/vdom/functional-component.js
@@ -0,0 +1,25 @@
+import { EMPTY } from '../constants';
+import { getNodeProps } from './index';
+import { isFunction } from '../util';
+
+
+/** Check if a VNode is a reference to a stateless functional component.
+ * A function component is represented as a VNode whose `nodeName` property is a reference to a function.
+ * If that function is not a Component (ie, has no `.render()` method on a prototype), it is considered a stateless functional component.
+ * @param {VNode} vnode A VNode
+ * @private
+ */
+export function isFunctionalComponent(vnode) {
+ let nodeName = vnode && vnode.nodeName;
+ return nodeName && isFunction(nodeName) && !(nodeName.prototype && nodeName.prototype.render);
+}
+
+
+
+/** Construct a resultant VNode from a VNode referencing a stateless functional component.
+ * @param {VNode} vnode A VNode with a `nodeName` property that is a reference to a function.
+ * @private
+ */
+export function buildFunctionalComponent(vnode, context) {
+ return vnode.nodeName(getNodeProps(vnode), context || EMPTY);
+}
diff --git a/thirdparty/preact/src/vdom/index.js b/thirdparty/preact/src/vdom/index.js
new file mode 100644
index 000000000..50d4ca2b9
--- /dev/null
+++ b/thirdparty/preact/src/vdom/index.js
@@ -0,0 +1,50 @@
+import { clone, isString, isFunction, toLowerCase } from '../util';
+import { isFunctionalComponent } from './functional-component';
+
+
+/** Check if two nodes are equivalent.
+ * @param {Element} node
+ * @param {VNode} vnode
+ * @private
+ */
+export function isSameNodeType(node, vnode) {
+ if (isString(vnode)) {
+ return node instanceof Text;
+ }
+ if (isString(vnode.nodeName)) {
+ return isNamedNode(node, vnode.nodeName);
+ }
+ if (isFunction(vnode.nodeName)) {
+ return node._componentConstructor===vnode.nodeName || isFunctionalComponent(vnode);
+ }
+}
+
+
+export function isNamedNode(node, nodeName) {
+ return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName);
+}
+
+
+/**
+ * Reconstruct Component-style `props` from a VNode.
+ * Ensures default/fallback values from `defaultProps`:
+ * Own-properties of `defaultProps` not present in `vnode.attributes` are added.
+ * @param {VNode} vnode
+ * @returns {Object} props
+ */
+export function getNodeProps(vnode) {
+ let defaultProps = vnode.nodeName.defaultProps,
+ props = clone(vnode.attributes);
+
+ if (defaultProps) {
+ for (let i in defaultProps) {
+ if (props[i]===undefined) {
+ props[i] = defaultProps[i];
+ }
+ }
+ }
+
+ if (vnode.children) props.children = vnode.children;
+
+ return props;
+}