preact
Version:
Tiny & fast Component-based virtual DOM framework.
1,058 lines (886 loc) • 33.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.preact = global.preact || {})));
}(this, function (exports) {
function VNode(nodeName, attributes, children) {
/** @type {string|function} */
this.nodeName = nodeName;
/** @type {object<string>|undefined} */
this.attributes = attributes;
/** @type {array<VNode>|undefined} */
this.children = children;
/** Reference to the given key. */
this.key = attributes && attributes.key;
}
var options = {
/** If `true`, `prop` changes trigger synchronous component updates.
* @name syncComponentUpdates
* @type Boolean
* @default true
*/
//syncComponentUpdates: true,
/** Processes all created VNodes.
* @param {VNode} vnode A newly-created VNode to normalize/process
*/
//vnode(vnode) { }
/** Hook invoked after a component is mounted. */
// afterMount(component) { }
/** Hook invoked after the DOM is updated with a component's latest render. */
// afterUpdate(component) { }
/** Hook invoked immediately before a component is unmounted. */
// beforeUnmount(component) { }
};
var stack = [];
/** JSX/hyperscript reviver
* Benchmarks: https://esbench.com/bench/57ee8f8e330ab09900a1a1a0
* @see http://jasonformat.com/wtf-is-jsx
* @public
* @example
* /** @jsx h *\/
* import { render, h } from 'preact';
* render(<span>foo</span>, document.body);
*/
function h(nodeName, attributes) {
var children = [],
lastSimple = undefined,
child = undefined,
simple = undefined,
i = undefined;
for (i = arguments.length; i-- > 2;) {
stack.push(arguments[i]);
}
if (attributes && attributes.children) {
if (!stack.length) stack.push(attributes.children);
delete attributes.children;
}
while (stack.length) {
if ((child = stack.pop()) instanceof Array) {
for (i = child.length; i--;) stack.push(child[i]);
} else if (child != null && child !== false) {
if (typeof child == 'number' || child === true) child = String(child);
simple = typeof child == 'string';
if (simple && lastSimple) {
children[children.length - 1] += child;
} else {
children.push(child);
lastSimple = simple;
}
}
}
var p = new VNode(nodeName, attributes || undefined, children);
// if a "vnode hook" is defined, pass every created VNode to it
if (options.vnode) options.vnode(p);
return p;
}
function extend(obj, props) {
if (props) {
for (var i in props) {
obj[i] = props[i];
}
}
return obj;
}
/** Fast clone. Note: does not filter out non-own properties.
* @see https://esbench.com/bench/56baa34f45df6895002e03b6
*/
function clone(obj) {
return extend({}, obj);
}
/** Get a deep property value from the given object, expressed in dot-notation.
* @private
*/
function delve(obj, key) {
for (var p = key.split('.'), i = 0; i < p.length && obj; i++) {
obj = obj[p[i]];
}
return obj;
}
/** @private is the given object a Function? */
function isFunction(obj) {
return 'function' === typeof obj;
}
/** @private is the given object a String? */
function isString(obj) {
return 'string' === typeof obj;
}
/** Convert a hashmap of CSS classes to a space-delimited className string
* @private
*/
function hashToClassName(c) {
var str = '';
for (var prop in c) {
if (c[prop]) {
if (str) str += ' ';
str += prop;
}
}
return str;
}
/** Just a memoized String#toLowerCase */
var lcCache = {};
var toLowerCase = function toLowerCase(s) {
return lcCache[s] || (lcCache[s] = s.toLowerCase());
};
/** Call a function asynchronously, as soon as possible.
* @param {Function} callback
*/
var resolved = typeof Promise !== 'undefined' && Promise.resolve();
var defer = resolved ? function (f) {
resolved.then(f);
} : setTimeout;
function cloneElement(vnode, props) {
return h(vnode.nodeName, extend(clone(vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children);
}
var NO_RENDER = 0;
var SYNC_RENDER = 1;
var FORCE_RENDER = 2;
var ASYNC_RENDER = 3;
var EMPTY = {};
var ATTR_KEY = typeof Symbol !== 'undefined' ? Symbol['for']('preactattr') : '__preactattr_';
// DOM properties that should NOT have "px" added when numeric
var NON_DIMENSION_PROPS = {
boxFlex: 1, boxFlexGroup: 1, columnCount: 1, fillOpacity: 1, flex: 1, flexGrow: 1,
flexPositive: 1, flexShrink: 1, flexNegative: 1, fontWeight: 1, lineClamp: 1, lineHeight: 1,
opacity: 1, order: 1, orphans: 1, strokeOpacity: 1, widows: 1, zIndex: 1, zoom: 1
};
// DOM event types that do not bubble and should be attached via useCapture
var NON_BUBBLING_EVENTS = { blur: 1, error: 1, focus: 1, load: 1, resize: 1, scroll: 1 };
/** Create an Event handler function that sets a given state property.
* @param {Component} component The component whose state should be updated
* @param {string} key A dot-notated key path to update in the component's state
* @param {string} eventPath A dot-notated key path to the value that should be retrieved from the Event or component
* @returns {function} linkedStateHandler
* @private
*/
function createLinkedState(component, key, eventPath) {
var path = key.split('.');
return function (e) {
var t = e && e.target || this,
state = {},
obj = state,
v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? t.type.match(/^che|rad/) ? t.checked : t.value : e,
i = 0;
for (; i < path.length - 1; i++) {
obj = obj[path[i]] || (obj[path[i]] = !i && component.state[path[i]] || {});
}
obj[path[i]] = v;
component.setState(state);
};
}
/** Managed queue of dirty components to be re-rendered */
// items/itemsOffline swap on each rerender() call (just a simple pool technique)
var items = [];
function enqueueRender(component) {
if (!component._dirty && (component._dirty = true) && items.push(component) == 1) {
(options.debounceRendering || defer)(rerender);
}
}
function rerender() {
var p = undefined,
list = items;
items = [];
while (p = list.pop()) {
if (p._dirty) renderComponent(p);
}
}
/** 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
*/
function isFunctionalComponent(vnode) {
var 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
*/
function buildFunctionalComponent(vnode, context) {
return vnode.nodeName(getNodeProps(vnode), context || EMPTY);
}
/** Check if two nodes are equivalent.
* @param {Element} node
* @param {VNode} vnode
* @private
*/
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);
}
}
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
*/
function getNodeProps(vnode) {
var props = clone(vnode.attributes);
props.children = vnode.children;
var defaultProps = vnode.nodeName.defaultProps;
if (defaultProps) {
for (var i in defaultProps) {
if (props[i] === undefined) {
props[i] = defaultProps[i];
}
}
}
return props;
}
/** Removes a given DOM Node from its parent. */
function removeNode(node) {
var p = node.parentNode;
if (p) p.removeChild(node);
}
/** Set a named attribute on the given Node, with special behavior for some names and event handlers.
* If `value` is `null`, the attribute/handler will be removed.
* @param {Element} node An element to mutate
* @param {string} name The name/key to set, such as an event or attribute name
* @param {any} value An attribute value, such as a function to be used as an event handler
* @param {any} previousValue The last value that was set for this name/node pair
* @private
*/
function setAccessor(node, name, old, value, isSvg) {
if (name === 'className') name = 'class';
if (name === 'class' && value && typeof value === 'object') {
value = hashToClassName(value);
}
if (name === 'key') {
// ignore
} else if (name === 'class' && !isSvg) {
node.className = value || '';
} else if (name === 'style') {
if (!value || isString(value) || isString(old)) {
node.style.cssText = value || '';
}
if (value && typeof value === 'object') {
if (!isString(old)) {
for (var i in old) {
if (!(i in value)) node.style[i] = '';
}
}
for (var i in value) {
node.style[i] = typeof value[i] === 'number' && !NON_DIMENSION_PROPS[i] ? value[i] + 'px' : value[i];
}
}
} else if (name === 'dangerouslySetInnerHTML') {
if (value) node.innerHTML = value.__html;
} else if (name[0] == 'o' && name[1] == 'n') {
var l = node._listeners || (node._listeners = {});
name = toLowerCase(name.substring(2));
// @TODO: this might be worth it later, un-breaks focus/blur bubbling in IE9:
// if (node.attachEvent) name = name=='focus'?'focusin':name=='blur'?'focusout':name;
if (value) {
if (!l[name]) node.addEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]);
} else if (l[name]) {
node.removeEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]);
}
l[name] = value;
} else if (name !== 'list' && name !== 'type' && !isSvg && name in node) {
setProperty(node, name, value == null ? '' : value);
if (value == null || value === false) node.removeAttribute(name);
} else {
var ns = isSvg && name.match(/^xlink\:?(.+)/);
if (value == null || value === false) {
if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1]));else node.removeAttribute(name);
} else if (typeof value !== 'object' && !isFunction(value)) {
if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1]), value);else node.setAttribute(name, value);
}
}
}
/** Attempt to set a DOM property to the given value.
* IE & FF throw for certain property-value combinations.
*/
function setProperty(node, name, value) {
try {
node[name] = value;
} catch (e) {}
}
/** Proxy an event to hooked event handlers
* @private
*/
function eventProxy(e) {
return this._listeners[e.type](options.event && options.event(e) || e);
}
/** DOM node pool, keyed on nodeName. */
var nodes = {};
function collectNode(node) {
removeNode(node);
if (node instanceof Element) {
node._component = node._componentConstructor = null;
var _name = node.normalizedNodeName || toLowerCase(node.nodeName);
(nodes[_name] || (nodes[_name] = [])).push(node);
}
}
function createNode(nodeName, isSvg) {
var name = toLowerCase(nodeName),
node = nodes[name] && nodes[name].pop() || (isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName));
node.normalizedNodeName = name;
return node;
}
/** Diff recursion count, used to track the end of the diff cycle. */
var mounts = [];
/** Diff recursion count, used to track the end of the diff cycle. */
var diffLevel = 0;
var isSvgMode = false;
function flushMounts() {
var c = undefined;
while (c = mounts.pop()) {
if (options.afterMount) options.afterMount(c);
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
*/
function diff(dom, vnode, context, mountAll, parent, componentRoot) {
if (! diffLevel++) isSvgMode = parent instanceof SVGElement;
var 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) {
var 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) {
if (dom.nodeValue != vnode) {
dom.nodeValue = vnode;
}
return dom;
}
recollectNodeTree(dom);
}
return document.createTextNode(vnode);
}
if (isFunction(vnode.nodeName)) {
return buildComponentFromVNode(dom, vnode, context, mountAll);
}
var out = dom,
nodeName = vnode.nodeName,
prevSvgMode = isSvgMode,
vchildren = vnode.children;
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 (vchildren && vchildren.length === 1 && typeof vchildren[0] === 'string' && out.childNodes.length === 1 && out.firstChild instanceof Text) {
if (out.firstChild.nodeValue != vchildren[0]) {
out.firstChild.nodeValue = vchildren[0];
}
} else if (vchildren && vchildren.length || out.firstChild) {
innerDiffNode(out, vchildren, context, mountAll);
}
var props = out[ATTR_KEY];
if (!props) {
out[ATTR_KEY] = props = {};
for (var 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) {
var originalChildren = dom.childNodes,
children = [],
keyed = {},
keyedLen = 0,
min = 0,
len = originalChildren.length,
childrenLen = 0,
vlen = vchildren && vchildren.length,
j = undefined,
c = undefined,
vchild = undefined,
child = undefined;
if (len) {
for (var i = 0; i < len; i++) {
var _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 (var 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
var 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 (var 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 */
function removeOrphanedChildren(children, unmountOnly) {
for (var i = children.length; i--;) {
if (children[i]) {
recollectNodeTree(children[i], unmountOnly);
}
}
}
/** Reclaim an entire tree of nodes, starting at the root. */
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;
var 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 (var _name in old) {
if (!(attrs && _name in attrs) && old[_name] != null) {
setAccessor(dom, _name, old[_name], old[_name] = undefined, isSvgMode);
}
}
// new & updated
if (attrs) {
for (var _name2 in attrs) {
if (_name2 !== 'children' && _name2 !== 'innerHTML' && (!(_name2 in old) || attrs[_name2] !== (_name2 === 'value' || _name2 === 'checked' ? dom[_name2] : old[_name2]))) {
setAccessor(dom, _name2, old[_name2], old[_name2] = attrs[_name2], isSvgMode);
}
}
}
}
/** 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
*/
var components = {};
function collectComponent(component) {
var name = component.constructor.name,
list = components[name];
if (list) list.push(component);else components[name] = [component];
}
function createComponent(Ctor, props, context) {
var inst = new Ctor(props, context),
list = components[Ctor.name];
Component.call(inst, props, context);
if (list) {
for (var i = list.length; i--;) {
if (list[i].constructor === Ctor) {
inst.nextBase = list[i].nextBase;
list.splice(i, 1);
break;
}
}
}
return inst;
}
/** 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.
*/
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
*/
function renderComponent(component, opts, mountAll, isChild) {
if (component._disable) return;
var skip = undefined,
rendered = undefined,
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 = undefined,
cbase = undefined;
// 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);
}
var childComponent = rendered && rendered.nodeName,
toUnmount = undefined,
base = undefined;
if (isFunction(childComponent)) {
// set up high order component link
inst = initialChildComponent;
var 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) {
var baseParent = initialBase.parentNode;
if (baseParent && base !== baseParent) {
baseParent.replaceChild(base, initialBase);
if (!toUnmount) {
initialBase._component = null;
recollectNodeTree(initialBase);
}
}
}
if (toUnmount) {
unmountComponent(toUnmount, base !== initialBase);
}
component.base = base;
if (base && !isChild) {
var componentRef = component,
t = component;
while (t = t._parentComponent) {
(componentRef = t).base = base;
}
base._component = componentRef;
base._componentConstructor = componentRef.constructor;
}
}
if (!isUpdate || mountAll) {
mounts.unshift(component);
} else if (!skip) {
if (component.componentDidUpdate) {
component.componentDidUpdate(previousProps, previousState, previousContext);
}
if (options.afterUpdate) options.afterUpdate(component);
}
var cb = component._renderCallbacks,
fn = undefined;
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
*/
function buildComponentFromVNode(dom, vnode, context, mountAll) {
var 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;
// passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L241:
oldDom = null;
}
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
*/
function unmountComponent(component, remove) {
if (options.beforeUnmount) options.beforeUnmount(component);
// console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`);
var base = component.base;
component._disable = true;
if (component.componentWillUnmount) component.componentWillUnmount();
component.base = null;
// recursively tear down & recollect high-order component children:
var 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();
}
/** Base Component class, for he ES6 Class method of creating Components
* @public
*
* @example
* class MyFoo extends Component {
* render(props, state) {
* return <div />;
* }
* }
*/
function Component(props, context) {
/** @private */
this._dirty = true;
// /** @public */
// this._disableRendering = false;
// /** @public */
// this.prevState = this.prevProps = this.prevContext = this.base = this.nextBase = this._parentComponent = this._component = this.__ref = this.__key = this._linkedStates = this._renderCallbacks = null;
/** @public */
this.context = context;
/** @type {object} */
this.props = props;
/** @type {object} */
if (!this.state) this.state = {};
}
extend(Component.prototype, {
/** Returns a `boolean` value indicating if the component should re-render when receiving the given `props` and `state`.
* @param {object} nextProps
* @param {object} nextState
* @param {object} nextContext
* @returns {Boolean} should the component re-render
* @name shouldComponentUpdate
* @function
*/
// shouldComponentUpdate() {
// return true;
// },
/** Returns a function that sets a state property when called.
* Calling linkState() repeatedly with the same arguments returns a cached link function.
*
* Provides some built-in special cases:
* - Checkboxes and radio buttons link their boolean `checked` value
* - Inputs automatically link their `value` property
* - Event paths fall back to any associated Component if not found on an element
* - If linked value is a function, will invoke it and use the result
*
* @param {string} key The path to set - can be a dot-notated deep key
* @param {string} [eventPath] If set, attempts to find the new state value at a given dot-notated path within the object passed to the linkedState setter.
* @returns {function} linkStateSetter(e)
*
* @example Update a "text" state value when an input changes:
* <input onChange={ this.linkState('text') } />
*
* @example Set a deep state value on click
* <button onClick={ this.linkState('touch.coords', 'touches.0') }>Tap</button
*/
linkState: function linkState(key, eventPath) {
var c = this._linkedStates || (this._linkedStates = {});
return c[key + eventPath] || (c[key + eventPath] = createLinkedState(this, key, eventPath));
},
/** Update component state by copying properties from `state` to `this.state`.
* @param {object} state A hash of state properties to update with new values
*/
setState: function setState(state, callback) {
var s = this.state;
if (!this.prevState) this.prevState = clone(s);
extend(s, isFunction(state) ? state(s, this.props) : state);
if (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback);
enqueueRender(this);
},
/** Immediately perform a synchronous re-render of the component.
* @private
*/
forceUpdate: function forceUpdate() {
renderComponent(this, FORCE_RENDER);
},
/** Accepts `props` and `state`, and returns a new Virtual DOM tree to build.
* Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).
* @param {object} props Props (eg: JSX attributes) received from parent element/component
* @param {object} state The component's current state
* @param {object} context Context object (if a parent component has provided context)
* @returns VNode
*/
render: function render() {}
});
/** Render JSX into a `parent` Element.
* @param {VNode} vnode A (JSX) VNode to render
* @param {Element} parent DOM element to render into
* @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge`
* @public
*
* @example
* // render a div into <body>:
* render(<div id="hello">hello!</div>, document.body);
*
* @example
* // render a "Thing" component into #foo:
* const Thing = ({ name }) => <span>{ name }</span>;
* render(<Thing name="one" />, document.querySelector('#foo'));
*/
function render(vnode, parent, merge) {
return diff(merge, vnode, {}, false, parent);
}
exports.createElement = h;
exports.h = h;
exports.cloneElement = cloneElement;
exports.Component = Component;
exports.render = render;
exports.rerender = rerender;
exports.options = options;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=aliases.js.map