@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
1,775 lines (1,629 loc) • 63.8 kB
JavaScript
/*!
* react-lite.js v0.15.18
* (c) 2016 Jade Gu
* Released under the MIT License.
*/
'use strict';
var HTML_KEY = 'dangerouslySetInnerHTML';
var SVGNamespaceURI = 'http://www.w3.org/2000/svg';
var COMPONENT_ID = 'liteid';
var VELEMENT = 2;
var VSTATELESS = 3;
var VCOMPONENT = 4;
var VCOMMENT = 5;
/**
* current stateful component's refs property
* will attach to every vnode created by calling component.render method
*/
var refs = null;
function createVnode(vtype, type, props, key, ref) {
var vnode = {
vtype: vtype,
type: type,
props: props,
refs: refs,
key: key,
ref: ref
};
if (vtype === VSTATELESS || vtype === VCOMPONENT) {
vnode.uid = getUid();
}
return vnode;
}
function initVnode(vnode, parentContext, namespaceURI) {
var vtype = vnode.vtype;
var node = null;
if (!vtype) {
// init text
node = document.createTextNode(vnode);
} else if (vtype === VELEMENT) {
// init element
node = initVelem(vnode, parentContext, namespaceURI);
} else if (vtype === VCOMPONENT) {
// init stateful component
node = initVcomponent(vnode, parentContext, namespaceURI);
} else if (vtype === VSTATELESS) {
// init stateless component
node = initVstateless(vnode, parentContext, namespaceURI);
} else if (vtype === VCOMMENT) {
// init comment
node = document.createComment('react-text: ' + (vnode.uid || getUid()));
}
return node;
}
function updateVnode(vnode, newVnode, node, parentContext) {
var vtype = vnode.vtype;
if (vtype === VCOMPONENT) {
return updateVcomponent(vnode, newVnode, node, parentContext);
}
if (vtype === VSTATELESS) {
return updateVstateless(vnode, newVnode, node, parentContext);
}
// ignore VCOMMENT and other vtypes
if (vtype !== VELEMENT) {
return node;
}
var oldHtml = vnode.props[HTML_KEY] && vnode.props[HTML_KEY].__html;
if (oldHtml != null) {
updateVelem(vnode, newVnode, node, parentContext);
initVchildren(newVnode, node, parentContext);
} else {
updateVChildren(vnode, newVnode, node, parentContext);
updateVelem(vnode, newVnode, node, parentContext);
}
return node;
}
function updateVChildren(vnode, newVnode, node, parentContext) {
var patches = {
removes: [],
updates: [],
creates: []
};
diffVchildren(patches, vnode, newVnode, node, parentContext);
flatEach(patches.removes, applyDestroy);
flatEach(patches.updates, applyUpdate);
flatEach(patches.creates, applyCreate);
}
function applyUpdate(data) {
if (!data) {
return;
}
var vnode = data.vnode;
var newNode = data.node;
// update
if (!data.shouldIgnore) {
if (!vnode.vtype) {
newNode.replaceData(0, newNode.length, data.newVnode);
// newNode.nodeValue = data.newVnode
} else if (vnode.vtype === VELEMENT) {
updateVelem(vnode, data.newVnode, newNode, data.parentContext);
} else if (vnode.vtype === VSTATELESS) {
newNode = updateVstateless(vnode, data.newVnode, newNode, data.parentContext);
} else if (vnode.vtype === VCOMPONENT) {
newNode = updateVcomponent(vnode, data.newVnode, newNode, data.parentContext);
}
}
// re-order
var currentNode = newNode.parentNode.childNodes[data.index];
if (currentNode !== newNode) {
newNode.parentNode.insertBefore(newNode, currentNode);
}
return newNode;
}
function applyDestroy(data) {
destroyVnode(data.vnode, data.node);
data.node.parentNode.removeChild(data.node);
}
function applyCreate(data) {
var node = initVnode(data.vnode, data.parentContext, data.parentNode.namespaceURI);
data.parentNode.insertBefore(node, data.parentNode.childNodes[data.index]);
}
/**
* Only vnode which has props.children need to call destroy function
* to check whether subTree has component that need to call lify-cycle method and release cache.
*/
function destroyVnode(vnode, node) {
var vtype = vnode.vtype;
if (vtype === VELEMENT) {
// destroy element
destroyVelem(vnode, node);
} else if (vtype === VCOMPONENT) {
// destroy state component
destroyVcomponent(vnode, node);
} else if (vtype === VSTATELESS) {
// destroy stateless component
destroyVstateless(vnode, node);
}
}
function initVelem(velem, parentContext, namespaceURI) {
var type = velem.type;
var props = velem.props;
var node = null;
if (type === 'svg' || namespaceURI === SVGNamespaceURI) {
node = document.createElementNS(SVGNamespaceURI, type);
namespaceURI = SVGNamespaceURI;
} else {
node = document.createElement(type);
}
initVchildren(velem, node, parentContext);
var isCustomComponent = type.indexOf('-') >= 0 || props.is != null;
setProps(node, props, isCustomComponent);
attachRef(velem.refs, velem.ref, node);
return node;
}
function initVchildren(velem, node, parentContext) {
var vchildren = node.vchildren = getFlattenChildren(velem);
var namespaceURI = node.namespaceURI;
for (var i = 0, len = vchildren.length; i < len; i++) {
node.appendChild(initVnode(vchildren[i], parentContext, namespaceURI));
}
}
function getFlattenChildren(vnode) {
var children = vnode.props.children;
var vchildren = [];
if (isArr(children)) {
flatEach(children, collectChild, vchildren);
} else {
collectChild(children, vchildren);
}
return vchildren;
}
function collectChild(child, children) {
if (child != null && typeof child !== 'boolean') {
if (!child.vtype) {
// convert immutablejs data
if (child.toJS) {
child = child.toJS();
if (isArr(child)) {
flatEach(child, collectChild, children);
} else {
collectChild(child, children);
}
return;
}
child = '' + child;
}
children[children.length] = child;
}
}
function diffVchildren(patches, vnode, newVnode, node, parentContext) {
var childNodes = node.childNodes;
var vchildren = node.vchildren;
var newVchildren = node.vchildren = getFlattenChildren(newVnode);
var vchildrenLen = vchildren.length;
var newVchildrenLen = newVchildren.length;
if (vchildrenLen === 0) {
if (newVchildrenLen > 0) {
for (var i = 0; i < newVchildrenLen; i++) {
addItem(patches.creates, {
vnode: newVchildren[i],
parentNode: node,
parentContext: parentContext,
index: i
});
}
}
return;
} else if (newVchildrenLen === 0) {
for (var i = 0; i < vchildrenLen; i++) {
addItem(patches.removes, {
vnode: vchildren[i],
node: childNodes[i]
});
}
return;
}
var updates = Array(newVchildrenLen);
var removes = null;
var creates = null;
// isEqual
for (var i = 0; i < vchildrenLen; i++) {
var _vnode = vchildren[i];
for (var j = 0; j < newVchildrenLen; j++) {
if (updates[j]) {
continue;
}
var _newVnode = newVchildren[j];
if (_vnode === _newVnode) {
var shouldIgnore = true;
if (parentContext) {
if (_vnode.vtype === VCOMPONENT || _vnode.vtype === VSTATELESS) {
if (_vnode.type.contextTypes) {
shouldIgnore = false;
}
}
}
updates[j] = {
shouldIgnore: shouldIgnore,
vnode: _vnode,
newVnode: _newVnode,
node: childNodes[i],
parentContext: parentContext,
index: j
};
vchildren[i] = null;
break;
}
}
}
// isSimilar
for (var i = 0; i < vchildrenLen; i++) {
var _vnode2 = vchildren[i];
if (_vnode2 === null) {
continue;
}
var shouldRemove = true;
for (var j = 0; j < newVchildrenLen; j++) {
if (updates[j]) {
continue;
}
var _newVnode2 = newVchildren[j];
if (_newVnode2.type === _vnode2.type && _newVnode2.key === _vnode2.key && _newVnode2.refs === _vnode2.refs) {
updates[j] = {
vnode: _vnode2,
newVnode: _newVnode2,
node: childNodes[i],
parentContext: parentContext,
index: j
};
shouldRemove = false;
break;
}
}
if (shouldRemove) {
if (!removes) {
removes = [];
}
addItem(removes, {
vnode: _vnode2,
node: childNodes[i]
});
}
}
for (var i = 0; i < newVchildrenLen; i++) {
var item = updates[i];
if (!item) {
if (!creates) {
creates = [];
}
addItem(creates, {
vnode: newVchildren[i],
parentNode: node,
parentContext: parentContext,
index: i
});
} else if (item.vnode.vtype === VELEMENT) {
diffVchildren(patches, item.vnode, item.newVnode, item.node, item.parentContext);
}
}
if (removes) {
addItem(patches.removes, removes);
}
if (creates) {
addItem(patches.creates, creates);
}
addItem(patches.updates, updates);
}
function updateVelem(velem, newVelem, node) {
var isCustomComponent = velem.type.indexOf('-') >= 0 || velem.props.is != null;
patchProps(node, velem.props, newVelem.props, isCustomComponent);
if (velem.ref !== newVelem.ref) {
detachRef(velem.refs, velem.ref);
attachRef(newVelem.refs, newVelem.ref, node);
}
return node;
}
function destroyVelem(velem, node) {
var props = velem.props;
var vchildren = node.vchildren;
var childNodes = node.childNodes;
for (var i = 0, len = vchildren.length; i < len; i++) {
destroyVnode(vchildren[i], childNodes[i]);
}
detachRef(velem.refs, velem.ref);
node.eventStore = node.vchildren = null;
}
function initVstateless(vstateless, parentContext, namespaceURI) {
var vnode = renderVstateless(vstateless, parentContext);
var node = initVnode(vnode, parentContext, namespaceURI);
node.cache = node.cache || {};
node.cache[vstateless.uid] = vnode;
return node;
}
function updateVstateless(vstateless, newVstateless, node, parentContext) {
var uid = vstateless.uid;
var vnode = node.cache[uid];
delete node.cache[uid];
var newVnode = renderVstateless(newVstateless, parentContext);
var newNode = compareTwoVnodes(vnode, newVnode, node, parentContext);
newNode.cache = newNode.cache || {};
newNode.cache[newVstateless.uid] = newVnode;
if (newNode !== node) {
syncCache(newNode.cache, node.cache, newNode);
}
return newNode;
}
function destroyVstateless(vstateless, node) {
var uid = vstateless.uid;
var vnode = node.cache[uid];
delete node.cache[uid];
destroyVnode(vnode, node);
}
function renderVstateless(vstateless, parentContext) {
var factory = vstateless.type;
var props = vstateless.props;
var componentContext = getContextByTypes(parentContext, factory.contextTypes);
var vnode = factory(props, componentContext);
if (vnode && vnode.render) {
vnode = vnode.render();
}
if (vnode === null || vnode === false) {
vnode = createVnode(VCOMMENT);
} else if (!vnode || !vnode.vtype) {
throw new Error('@' + factory.name + '#render:You may have returned undefined, an array or some other invalid object');
}
return vnode;
}
function initVcomponent(vcomponent, parentContext, namespaceURI) {
var Component = vcomponent.type;
var props = vcomponent.props;
var uid = vcomponent.uid;
var componentContext = getContextByTypes(parentContext, Component.contextTypes);
var component = new Component(props, componentContext);
var updater = component.$updater;
var cache = component.$cache;
cache.parentContext = parentContext;
updater.isPending = true;
component.props = component.props || props;
component.context = component.context || componentContext;
if (component.componentWillMount) {
component.componentWillMount();
component.state = updater.getState();
}
var vnode = renderComponent(component);
var node = initVnode(vnode, getChildContext(component, parentContext), namespaceURI);
node.cache = node.cache || {};
node.cache[uid] = component;
cache.vnode = vnode;
cache.node = node;
cache.isMounted = true;
addItem(pendingComponents, component);
attachRef(vcomponent.refs, vcomponent.ref, component);
return node;
}
function updateVcomponent(vcomponent, newVcomponent, node, parentContext) {
var uid = vcomponent.uid;
var component = node.cache[uid];
var updater = component.$updater;
var cache = component.$cache;
var Component = newVcomponent.type;
var nextProps = newVcomponent.props;
var componentContext = getContextByTypes(parentContext, Component.contextTypes);
delete node.cache[uid];
node.cache[newVcomponent.uid] = component;
cache.parentContext = parentContext;
if (component.componentWillReceiveProps) {
updater.isPending = true;
component.componentWillReceiveProps(nextProps, componentContext);
updater.isPending = false;
}
updater.emitUpdate(nextProps, componentContext);
if (vcomponent.ref !== newVcomponent.ref) {
detachRef(vcomponent.refs, vcomponent.ref);
attachRef(newVcomponent.refs, newVcomponent.ref, component);
}
return cache.node;
}
function destroyVcomponent(vcomponent, node) {
var uid = vcomponent.uid;
var component = node.cache[uid];
var cache = component.$cache;
delete node.cache[uid];
detachRef(vcomponent.refs, vcomponent.ref);
component.setState = component.forceUpdate = noop;
if (component.componentWillUnmount) {
component.componentWillUnmount();
}
destroyVnode(cache.vnode, node);
delete component.setState;
cache.isMounted = false;
cache.node = cache.parentContext = cache.vnode = component.refs = component.context = null;
}
function getContextByTypes(curContext, contextTypes) {
var context = {};
if (!contextTypes || !curContext) {
return context;
}
for (var key in contextTypes) {
if (contextTypes.hasOwnProperty(key)) {
context[key] = curContext[key];
}
}
return context;
}
function renderComponent(component, parentContext) {
refs = component.refs;
var vnode = component.render();
if (vnode === null || vnode === false) {
vnode = createVnode(VCOMMENT);
} else if (!vnode || !vnode.vtype) {
throw new Error('@' + component.constructor.name + '#render:You may have returned undefined, an array or some other invalid object');
}
refs = null;
return vnode;
}
function getChildContext(component, parentContext) {
if (component.getChildContext) {
var curContext = component.getChildContext();
if (curContext) {
parentContext = extend(extend({}, parentContext), curContext);
}
}
return parentContext;
}
var pendingComponents = [];
function clearPendingComponents() {
var len = pendingComponents.length;
if (!len) {
return;
}
var components = pendingComponents;
pendingComponents = [];
var i = -1;
while (len--) {
var component = components[++i];
var updater = component.$updater;
if (component.componentDidMount) {
component.componentDidMount();
}
updater.isPending = false;
updater.emitUpdate();
}
}
function compareTwoVnodes(vnode, newVnode, node, parentContext) {
var newNode = node;
if (newVnode == null) {
// remove
destroyVnode(vnode, node);
node.parentNode.removeChild(node);
} else if (vnode.type !== newVnode.type || vnode.key !== newVnode.key) {
// replace
destroyVnode(vnode, node);
newNode = initVnode(newVnode, parentContext, node.namespaceURI);
node.parentNode.replaceChild(newNode, node);
} else if (vnode !== newVnode || parentContext) {
// same type and same key -> update
newNode = updateVnode(vnode, newVnode, node, parentContext);
}
return newNode;
}
function getDOMNode() {
return this;
}
function attachRef(refs, refKey, refValue) {
if (!refs || refKey == null || !refValue) {
return;
}
if (refValue.nodeName && !refValue.getDOMNode) {
// support react v0.13 style: this.refs.myInput.getDOMNode()
refValue.getDOMNode = getDOMNode;
}
if (isFn(refKey)) {
refKey(refValue);
} else {
refs[refKey] = refValue;
}
}
function detachRef(refs, refKey) {
if (!refs || refKey == null) {
return;
}
if (isFn(refKey)) {
refKey(null);
} else {
delete refs[refKey];
}
}
function syncCache(cache, oldCache, node) {
for (var key in oldCache) {
if (!oldCache.hasOwnProperty(key)) {
continue;
}
var value = oldCache[key];
cache[key] = value;
// is component, update component.$cache.node
if (value.forceUpdate) {
value.$cache.node = node;
}
}
}
var updateQueue = {
updaters: [],
isPending: false,
add: function add(updater) {
addItem(this.updaters, updater);
},
batchUpdate: function batchUpdate() {
if (this.isPending) {
return;
}
this.isPending = true;
/*
each updater.update may add new updater to updateQueue
clear them with a loop
event bubbles from bottom-level to top-level
reverse the updater order can merge some props and state and reduce the refresh times
see Updater.update method below to know why
*/
var updaters = this.updaters;
var updater = undefined;
while (updater = updaters.pop()) {
updater.updateComponent();
}
this.isPending = false;
}
};
function Updater(instance) {
this.instance = instance;
this.pendingStates = [];
this.pendingCallbacks = [];
this.isPending = false;
this.nextProps = this.nextContext = null;
this.clearCallbacks = this.clearCallbacks.bind(this);
}
Updater.prototype = {
emitUpdate: function emitUpdate(nextProps, nextContext) {
this.nextProps = nextProps;
this.nextContext = nextContext;
// receive nextProps!! should update immediately
nextProps || !updateQueue.isPending ? this.updateComponent() : updateQueue.add(this);
},
updateComponent: function updateComponent() {
var instance = this.instance;
var pendingStates = this.pendingStates;
var nextProps = this.nextProps;
var nextContext = this.nextContext;
if (nextProps || pendingStates.length > 0) {
nextProps = nextProps || instance.props;
nextContext = nextContext || instance.context;
this.nextProps = this.nextContext = null;
// merge the nextProps and nextState and update by one time
shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks);
}
},
addState: function addState(nextState) {
if (nextState) {
addItem(this.pendingStates, nextState);
if (!this.isPending) {
this.emitUpdate();
}
}
},
replaceState: function replaceState(nextState) {
var pendingStates = this.pendingStates;
pendingStates.pop();
// push special params to point out should replace state
addItem(pendingStates, [nextState]);
},
getState: function getState() {
var instance = this.instance;
var pendingStates = this.pendingStates;
var state = instance.state;
var props = instance.props;
if (pendingStates.length) {
state = extend({}, state);
pendingStates.forEach(function (nextState) {
var isReplace = isArr(nextState);
if (isReplace) {
nextState = nextState[0];
}
if (isFn(nextState)) {
nextState = nextState.call(instance, state, props);
}
// replace state
if (isReplace) {
state = extend({}, nextState[0]);
} else {
extend(state, nextState);
}
});
pendingStates.length = 0;
}
return state;
},
clearCallbacks: function clearCallbacks() {
var pendingCallbacks = this.pendingCallbacks;
var instance = this.instance;
if (pendingCallbacks.length > 0) {
this.pendingCallbacks = [];
pendingCallbacks.forEach(function (callback) {
return callback.call(instance);
});
}
},
addCallback: function addCallback(callback) {
if (isFn(callback)) {
addItem(this.pendingCallbacks, callback);
}
}
};
function Component(props, context) {
this.$updater = new Updater(this);
this.$cache = { isMounted: false };
this.props = props;
this.state = {};
this.refs = {};
this.context = context;
}
Component.prototype = {
constructor: Component,
// getChildContext: _.noop,
// componentWillUpdate: _.noop,
// componentDidUpdate: _.noop,
// componentWillReceiveProps: _.noop,
// componentWillMount: _.noop,
// componentDidMount: _.noop,
// componentWillUnmount: _.noop,
// shouldComponentUpdate(nextProps, nextState) {
// return true
// },
forceUpdate: function forceUpdate(callback) {
var $updater = this.$updater;
var $cache = this.$cache;
var props = this.props;
var state = this.state;
var context = this.context;
if ($updater.isPending || !$cache.isMounted) {
return;
}
var nextProps = $cache.props || props;
var nextState = $cache.state || state;
var nextContext = $cache.context || context;
var parentContext = $cache.parentContext;
var node = $cache.node;
var vnode = $cache.vnode;
$cache.props = $cache.state = $cache.context = null;
$updater.isPending = true;
if (this.componentWillUpdate) {
this.componentWillUpdate(nextProps, nextState, nextContext);
}
this.state = nextState;
this.props = nextProps;
this.context = nextContext;
var newVnode = renderComponent(this);
var newNode = compareTwoVnodes(vnode, newVnode, node, getChildContext(this, parentContext));
if (newNode !== node) {
newNode.cache = newNode.cache || {};
syncCache(newNode.cache, node.cache, newNode);
}
$cache.vnode = newVnode;
$cache.node = newNode;
clearPendingComponents();
if (this.componentDidUpdate) {
this.componentDidUpdate(props, state, context);
}
if (callback) {
callback.call(this);
}
$updater.isPending = false;
$updater.emitUpdate();
},
setState: function setState(nextState, callback) {
var $updater = this.$updater;
$updater.addCallback(callback);
$updater.addState(nextState);
},
replaceState: function replaceState(nextState, callback) {
var $updater = this.$updater;
$updater.addCallback(callback);
$updater.replaceState(nextState);
},
getDOMNode: function getDOMNode() {
var node = this.$cache.node;
return node && node.nodeName === '#comment' ? null : node;
},
isMounted: function isMounted() {
return this.$cache.isMounted;
}
};
function shouldUpdate(component, nextProps, nextState, nextContext, callback) {
var shouldComponentUpdate = true;
if (component.shouldComponentUpdate) {
shouldComponentUpdate = component.shouldComponentUpdate(nextProps, nextState, nextContext);
}
if (shouldComponentUpdate === false) {
component.props = nextProps;
component.state = nextState;
component.context = nextContext || {};
return;
}
var cache = component.$cache;
cache.props = nextProps;
cache.state = nextState;
cache.context = nextContext || {};
component.forceUpdate(callback);
}
// event config
var notBubbleEvents = {
onmouseleave: 1,
onmouseenter: 1,
onload: 1,
onunload: 1,
onscroll: 1,
onfocus: 1,
onblur: 1,
onrowexit: 1,
onbeforeunload: 1,
onstop: 1,
ondragdrop: 1,
ondragenter: 1,
ondragexit: 1,
ondraggesture: 1,
ondragover: 1,
oncontextmenu: 1
};
function getEventName(key) {
key = key === 'onDoubleClick' ? 'ondblclick' : key;
return key.toLowerCase();
}
// Mobile Safari does not fire properly bubble click events on
// non-interactive elements, which means delegated click listeners do not
// fire. The workaround for this bug involves attaching an empty click
// listener on the target node.
var inMobile = ('ontouchstart' in document);
var emptyFunction = function emptyFunction() {};
var ON_CLICK_KEY = 'onclick';
var eventTypes = {};
function addEvent(elem, eventType, listener) {
eventType = getEventName(eventType);
if (notBubbleEvents[eventType] === 1) {
elem[eventType] = listener;
return;
}
var eventStore = elem.eventStore || (elem.eventStore = {});
eventStore[eventType] = listener;
if (!eventTypes[eventType]) {
// onclick -> click
document.addEventListener(eventType.substr(2), dispatchEvent, false);
eventTypes[eventType] = true;
}
if (inMobile && eventType === ON_CLICK_KEY) {
elem.addEventListener('click', emptyFunction, false);
}
var nodeName = elem.nodeName;
if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) {
addEvent(elem, 'oninput', listener);
}
}
function removeEvent(elem, eventType) {
eventType = getEventName(eventType);
if (notBubbleEvents[eventType] === 1) {
elem[eventType] = null;
return;
}
var eventStore = elem.eventStore || (elem.eventStore = {});
delete eventStore[eventType];
if (inMobile && eventType === ON_CLICK_KEY) {
elem.removeEventListener('click', emptyFunction, false);
}
var nodeName = elem.nodeName;
if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) {
delete eventStore['oninput'];
}
}
function dispatchEvent(event) {
var target = event.target;
var type = event.type;
var eventType = 'on' + type;
var syntheticEvent = undefined;
updateQueue.isPending = true;
while (target) {
var _target = target;
var eventStore = _target.eventStore;
var listener = eventStore && eventStore[eventType];
if (!listener) {
target = target.parentNode;
continue;
}
if (!syntheticEvent) {
syntheticEvent = createSyntheticEvent(event);
}
syntheticEvent.currentTarget = target;
listener.call(target, syntheticEvent);
if (syntheticEvent.$cancalBubble) {
break;
}
target = target.parentNode;
}
updateQueue.isPending = false;
updateQueue.batchUpdate();
}
function createSyntheticEvent(nativeEvent) {
var syntheticEvent = {};
var cancalBubble = function cancalBubble() {
return syntheticEvent.$cancalBubble = true;
};
syntheticEvent.nativeEvent = nativeEvent;
syntheticEvent.persist = noop;
for (var key in nativeEvent) {
if (typeof nativeEvent[key] !== 'function') {
syntheticEvent[key] = nativeEvent[key];
} else if (key === 'stopPropagation' || key === 'stopImmediatePropagation') {
syntheticEvent[key] = cancalBubble;
} else {
syntheticEvent[key] = nativeEvent[key].bind(nativeEvent);
}
}
return syntheticEvent;
}
function setStyle(elemStyle, styles) {
for (var styleName in styles) {
if (styles.hasOwnProperty(styleName)) {
setStyleValue(elemStyle, styleName, styles[styleName]);
}
}
}
function removeStyle(elemStyle, styles) {
for (var styleName in styles) {
if (styles.hasOwnProperty(styleName)) {
elemStyle[styleName] = '';
}
}
}
function patchStyle(elemStyle, style, newStyle) {
if (style === newStyle) {
return;
}
if (!newStyle && style) {
removeStyle(elemStyle, style);
return;
} else if (newStyle && !style) {
setStyle(elemStyle, newStyle);
return;
}
for (var key in style) {
if (newStyle.hasOwnProperty(key)) {
if (newStyle[key] !== style[key]) {
setStyleValue(elemStyle, key, newStyle[key]);
}
} else {
elemStyle[key] = '';
}
}
for (var key in newStyle) {
if (!style.hasOwnProperty(key)) {
setStyleValue(elemStyle, key, newStyle[key]);
}
}
}
/**
* CSS properties which accept numbers but are not in units of "px".
*/
var isUnitlessNumber = {
animationIterationCount: 1,
borderImageOutset: 1,
borderImageSlice: 1,
borderImageWidth: 1,
boxFlex: 1,
boxFlexGroup: 1,
boxOrdinalGroup: 1,
columnCount: 1,
flex: 1,
flexGrow: 1,
flexPositive: 1,
flexShrink: 1,
flexNegative: 1,
flexOrder: 1,
gridRow: 1,
gridColumn: 1,
fontWeight: 1,
lineClamp: 1,
lineHeight: 1,
opacity: 1,
order: 1,
orphans: 1,
tabSize: 1,
widows: 1,
zIndex: 1,
zoom: 1,
// SVG-related properties
fillOpacity: 1,
floodOpacity: 1,
stopOpacity: 1,
strokeDasharray: 1,
strokeDashoffset: 1,
strokeMiterlimit: 1,
strokeOpacity: 1,
strokeWidth: 1
};
function prefixKey(prefix, key) {
return prefix + key.charAt(0).toUpperCase() + key.substring(1);
}
var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
Object.keys(isUnitlessNumber).forEach(function (prop) {
prefixes.forEach(function (prefix) {
isUnitlessNumber[prefixKey(prefix, prop)] = 1;
});
});
var RE_NUMBER = /^-?\d+(\.\d+)?$/;
function setStyleValue(elemStyle, styleName, styleValue) {
if (!isUnitlessNumber[styleName] && RE_NUMBER.test(styleValue)) {
elemStyle[styleName] = styleValue + 'px';
return;
}
if (styleName === 'float') {
styleName = 'cssFloat';
}
if (styleValue == null || typeof styleValue === 'boolean') {
styleValue = '';
}
elemStyle[styleName] = styleValue;
}
var ATTRIBUTE_NAME_START_CHAR = ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\uB7\\u0300-\\u036F\\u203F-\\u2040';
var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$');
var isCustomAttribute = RegExp.prototype.test.bind(new RegExp('^(data|aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'));
// will merge some data in properties below
var properties = {};
/**
* Mapping from normalized, camelcased property names to a configuration that
* specifies how the associated DOM property should be accessed or rendered.
*/
var MUST_USE_PROPERTY = 0x1;
var HAS_BOOLEAN_VALUE = 0x4;
var HAS_NUMERIC_VALUE = 0x8;
var HAS_POSITIVE_NUMERIC_VALUE = 0x10 | 0x8;
var HAS_OVERLOADED_BOOLEAN_VALUE = 0x20;
// html config
var HTMLDOMPropertyConfig = {
props: {
/**
* Standard Properties
*/
accept: 0,
acceptCharset: 0,
accessKey: 0,
action: 0,
allowFullScreen: HAS_BOOLEAN_VALUE,
allowTransparency: 0,
alt: 0,
async: HAS_BOOLEAN_VALUE,
autoComplete: 0,
autoFocus: HAS_BOOLEAN_VALUE,
autoPlay: HAS_BOOLEAN_VALUE,
capture: HAS_BOOLEAN_VALUE,
cellPadding: 0,
cellSpacing: 0,
charSet: 0,
challenge: 0,
checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
cite: 0,
classID: 0,
className: 0,
cols: HAS_POSITIVE_NUMERIC_VALUE,
colSpan: 0,
content: 0,
contentEditable: 0,
contextMenu: 0,
controls: HAS_BOOLEAN_VALUE,
coords: 0,
crossOrigin: 0,
data: 0, // For `<object />` acts as `src`.
dateTime: 0,
'default': HAS_BOOLEAN_VALUE,
// not in regular react, they did it in other way
defaultValue: MUST_USE_PROPERTY,
// not in regular react, they did it in other way
defaultChecked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
defer: HAS_BOOLEAN_VALUE,
dir: 0,
disabled: HAS_BOOLEAN_VALUE,
download: HAS_OVERLOADED_BOOLEAN_VALUE,
draggable: 0,
encType: 0,
form: 0,
formAction: 0,
formEncType: 0,
formMethod: 0,
formNoValidate: HAS_BOOLEAN_VALUE,
formTarget: 0,
frameBorder: 0,
headers: 0,
height: 0,
hidden: HAS_BOOLEAN_VALUE,
high: 0,
href: 0,
hrefLang: 0,
htmlFor: 0,
httpEquiv: 0,
icon: 0,
id: 0,
inputMode: 0,
integrity: 0,
is: 0,
keyParams: 0,
keyType: 0,
kind: 0,
label: 0,
lang: 0,
list: 0,
loop: HAS_BOOLEAN_VALUE,
low: 0,
manifest: 0,
marginHeight: 0,
marginWidth: 0,
max: 0,
maxLength: 0,
media: 0,
mediaGroup: 0,
method: 0,
min: 0,
minLength: 0,
// Caution; `option.selected` is not updated if `select.multiple` is
// disabled with `removeAttribute`.
multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
name: 0,
nonce: 0,
noValidate: HAS_BOOLEAN_VALUE,
open: HAS_BOOLEAN_VALUE,
optimum: 0,
pattern: 0,
placeholder: 0,
poster: 0,
preload: 0,
profile: 0,
radioGroup: 0,
readOnly: HAS_BOOLEAN_VALUE,
referrerPolicy: 0,
rel: 0,
required: HAS_BOOLEAN_VALUE,
reversed: HAS_BOOLEAN_VALUE,
role: 0,
rows: HAS_POSITIVE_NUMERIC_VALUE,
rowSpan: HAS_NUMERIC_VALUE,
sandbox: 0,
scope: 0,
scoped: HAS_BOOLEAN_VALUE,
scrolling: 0,
seamless: HAS_BOOLEAN_VALUE,
selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
shape: 0,
size: HAS_POSITIVE_NUMERIC_VALUE,
sizes: 0,
span: HAS_POSITIVE_NUMERIC_VALUE,
spellCheck: 0,
src: 0,
srcDoc: 0,
srcLang: 0,
srcSet: 0,
start: HAS_NUMERIC_VALUE,
step: 0,
style: 0,
summary: 0,
tabIndex: 0,
target: 0,
title: 0,
// Setting .type throws on non-<input> tags
type: 0,
useMap: 0,
value: MUST_USE_PROPERTY,
width: 0,
wmode: 0,
wrap: 0,
/**
* RDFa Properties
*/
about: 0,
datatype: 0,
inlist: 0,
prefix: 0,
// property is also supported for OpenGraph in meta tags.
property: 0,
resource: 0,
'typeof': 0,
vocab: 0,
/**
* Non-standard Properties
*/
// autoCapitalize and autoCorrect are supported in Mobile Safari for
// keyboard hints.
autoCapitalize: 0,
autoCorrect: 0,
// autoSave allows WebKit/Blink to persist values of input fields on page reloads
autoSave: 0,
// color is for Safari mask-icon link
color: 0,
// itemProp, itemScope, itemType are for
// Microdata support. See http://schema.org/docs/gs.html
itemProp: 0,
itemScope: HAS_BOOLEAN_VALUE,
itemType: 0,
// itemID and itemRef are for Microdata support as well but
// only specified in the WHATWG spec document. See
// https://html.spec.whatwg.org/multipage/microdata.html#microdata-dom-api
itemID: 0,
itemRef: 0,
// results show looking glass icon and recent searches on input
// search fields in WebKit/Blink
results: 0,
// IE-only attribute that specifies security restrictions on an iframe
// as an alternative to the sandbox attribute on IE<10
security: 0,
// IE-only attribute that controls focus behavior
unselectable: 0
},
attrNS: {},
domAttrs: {
acceptCharset: 'accept-charset',
className: 'class',
htmlFor: 'for',
httpEquiv: 'http-equiv'
},
domProps: {}
};
// svg config
var xlink = 'http://www.w3.org/1999/xlink';
var xml = 'http://www.w3.org/XML/1998/namespace';
// We use attributes for everything SVG so let's avoid some duplication and run
// code instead.
// The following are all specified in the HTML config already so we exclude here.
// - class (as className)
// - color
// - height
// - id
// - lang
// - max
// - media
// - method
// - min
// - name
// - style
// - target
// - type
// - width
var ATTRS = {
accentHeight: 'accent-height',
accumulate: 0,
additive: 0,
alignmentBaseline: 'alignment-baseline',
allowReorder: 'allowReorder',
alphabetic: 0,
amplitude: 0,
arabicForm: 'arabic-form',
ascent: 0,
attributeName: 'attributeName',
attributeType: 'attributeType',
autoReverse: 'autoReverse',
azimuth: 0,
baseFrequency: 'baseFrequency',
baseProfile: 'baseProfile',
baselineShift: 'baseline-shift',
bbox: 0,
begin: 0,
bias: 0,
by: 0,
calcMode: 'calcMode',
capHeight: 'cap-height',
clip: 0,
clipPath: 'clip-path',
clipRule: 'clip-rule',
clipPathUnits: 'clipPathUnits',
colorInterpolation: 'color-interpolation',
colorInterpolationFilters: 'color-interpolation-filters',
colorProfile: 'color-profile',
colorRendering: 'color-rendering',
contentScriptType: 'contentScriptType',
contentStyleType: 'contentStyleType',
cursor: 0,
cx: 0,
cy: 0,
d: 0,
decelerate: 0,
descent: 0,
diffuseConstant: 'diffuseConstant',
direction: 0,
display: 0,
divisor: 0,
dominantBaseline: 'dominant-baseline',
dur: 0,
dx: 0,
dy: 0,
edgeMode: 'edgeMode',
elevation: 0,
enableBackground: 'enable-background',
end: 0,
exponent: 0,
externalResourcesRequired: 'externalResourcesRequired',
fill: 0,
fillOpacity: 'fill-opacity',
fillRule: 'fill-rule',
filter: 0,
filterRes: 'filterRes',
filterUnits: 'filterUnits',
floodColor: 'flood-color',
floodOpacity: 'flood-opacity',
focusable: 0,
fontFamily: 'font-family',
fontSize: 'font-size',
fontSizeAdjust: 'font-size-adjust',
fontStretch: 'font-stretch',
fontStyle: 'font-style',
fontVariant: 'font-variant',
fontWeight: 'font-weight',
format: 0,
from: 0,
fx: 0,
fy: 0,
g1: 0,
g2: 0,
glyphName: 'glyph-name',
glyphOrientationHorizontal: 'glyph-orientation-horizontal',
glyphOrientationVertical: 'glyph-orientation-vertical',
glyphRef: 'glyphRef',
gradientTransform: 'gradientTransform',
gradientUnits: 'gradientUnits',
hanging: 0,
horizAdvX: 'horiz-adv-x',
horizOriginX: 'horiz-origin-x',
ideographic: 0,
imageRendering: 'image-rendering',
'in': 0,
in2: 0,
intercept: 0,
k: 0,
k1: 0,
k2: 0,
k3: 0,
k4: 0,
kernelMatrix: 'kernelMatrix',
kernelUnitLength: 'kernelUnitLength',
kerning: 0,
keyPoints: 'keyPoints',
keySplines: 'keySplines',
keyTimes: 'keyTimes',
lengthAdjust: 'lengthAdjust',
letterSpacing: 'letter-spacing',
lightingColor: 'lighting-color',
limitingConeAngle: 'limitingConeAngle',
local: 0,
markerEnd: 'marker-end',
markerMid: 'marker-mid',
markerStart: 'marker-start',
markerHeight: 'markerHeight',
markerUnits: 'markerUnits',
markerWidth: 'markerWidth',
mask: 0,
maskContentUnits: 'maskContentUnits',
maskUnits: 'maskUnits',
mathematical: 0,
mode: 0,
numOctaves: 'numOctaves',
offset: 0,
opacity: 0,
operator: 0,
order: 0,
orient: 0,
orientation: 0,
origin: 0,
overflow: 0,
overlinePosition: 'overline-position',
overlineThickness: 'overline-thickness',
paintOrder: 'paint-order',
panose1: 'panose-1',
pathLength: 'pathLength',
patternContentUnits: 'patternContentUnits',
patternTransform: 'patternTransform',
patternUnits: 'patternUnits',
pointerEvents: 'pointer-events',
points: 0,
pointsAtX: 'pointsAtX',
pointsAtY: 'pointsAtY',
pointsAtZ: 'pointsAtZ',
preserveAlpha: 'preserveAlpha',
preserveAspectRatio: 'preserveAspectRatio',
primitiveUnits: 'primitiveUnits',
r: 0,
radius: 0,
refX: 'refX',
refY: 'refY',
renderingIntent: 'rendering-intent',
repeatCount: 'repeatCount',
repeatDur: 'repeatDur',
requiredExtensions: 'requiredExtensions',
requiredFeatures: 'requiredFeatures',
restart: 0,
result: 0,
rotate: 0,
rx: 0,
ry: 0,
scale: 0,
seed: 0,
shapeRendering: 'shape-rendering',
slope: 0,
spacing: 0,
specularConstant: 'specularConstant',
specularExponent: 'specularExponent',
speed: 0,
spreadMethod: 'spreadMethod',
startOffset: 'startOffset',
stdDeviation: 'stdDeviation',
stemh: 0,
stemv: 0,
stitchTiles: 'stitchTiles',
stopColor: 'stop-color',
stopOpacity: 'stop-opacity',
strikethroughPosition: 'strikethrough-position',
strikethroughThickness: 'strikethrough-thickness',
string: 0,
stroke: 0,
strokeDasharray: 'stroke-dasharray',
strokeDashoffset: 'stroke-dashoffset',
strokeLinecap: 'stroke-linecap',
strokeLinejoin: 'stroke-linejoin',
strokeMiterlimit: 'stroke-miterlimit',
strokeOpacity: 'stroke-opacity',
strokeWidth: 'stroke-width',
surfaceScale: 'surfaceScale',
systemLanguage: 'systemLanguage',
tableValues: 'tableValues',
targetX: 'targetX',
targetY: 'targetY',
textAnchor: 'text-anchor',
textDecoration: 'text-decoration',
textRendering: 'text-rendering',
textLength: 'textLength',
to: 0,
transform: 0,
u1: 0,
u2: 0,
underlinePosition: 'underline-position',
underlineThickness: 'underline-thickness',
unicode: 0,
unicodeBidi: 'unicode-bidi',
unicodeRange: 'unicode-range',
unitsPerEm: 'units-per-em',
vAlphabetic: 'v-alphabetic',
vHanging: 'v-hanging',
vIdeographic: 'v-ideographic',
vMathematical: 'v-mathematical',
values: 0,
vectorEffect: 'vector-effect',
version: 0,
vertAdvY: 'vert-adv-y',
vertOriginX: 'vert-origin-x',
vertOriginY: 'vert-origin-y',
viewBox: 'viewBox',
viewTarget: 'viewTarget',
visibility: 0,
widths: 0,
wordSpacing: 'word-spacing',
writingMode: 'writing-mode',
x: 0,
xHeight: 'x-height',
x1: 0,
x2: 0,
xChannelSelector: 'xChannelSelector',
xlinkActuate: 'xlink:actuate',
xlinkArcrole: 'xlink:arcrole',
xlinkHref: 'xlink:href',
xlinkRole: 'xlink:role',
xlinkShow: 'xlink:show',
xlinkTitle: 'xlink:title',
xlinkType: 'xlink:type',
xmlBase: 'xml:base',
xmlns: 0,
xmlnsXlink: 'xmlns:xlink',
xmlLang: 'xml:lang',
xmlSpace: 'xml:space',
y: 0,
y1: 0,
y2: 0,
yChannelSelector: 'yChannelSelector',
z: 0,
zoomAndPan: 'zoomAndPan'
};
var SVGDOMPropertyConfig = {
props: {},
attrNS: {
xlinkActuate: xlink,
xlinkArcrole: xlink,
xlinkHref: xlink,
xlinkRole: xlink,
xlinkShow: xlink,
xlinkTitle: xlink,
xlinkType: xlink,
xmlBase: xml,
xmlLang: xml,
xmlSpace: xml
},
domAttrs: {},
domProps: {}
};
Object.keys(ATTRS).map(function (key) {
SVGDOMPropertyConfig.props[key] = 0;
if (ATTRS[key]) {
SVGDOMPropertyConfig.domAttrs[key] = ATTRS[key];
}
});
// merge html and svg config into properties
mergeConfigToProperties(HTMLDOMPropertyConfig);
mergeConfigToProperties(SVGDOMPropertyConfig);
function mergeConfigToProperties(config) {
var
// all react/react-lite supporting property names in here
props = config.props;
var
// attributes namespace in here
attrNS = config.attrNS;
var
// propName in props which should use to be dom-attribute in here
domAttrs = config.domAttrs;
var
// propName in props which should use to be dom-property in here
domProps = config.domProps;
for (var propName in props) {
if (!props.hasOwnProperty(propName)) {
continue;
}
var propConfig = props[propName];
properties[propName] = {
attributeName: domAttrs.hasOwnProperty(propName) ? domAttrs[propName] : propName.toLowerCase(),
propertyName: domProps.hasOwnProperty(propName) ? domProps[propName] : propName,
attributeNamespace: attrNS.hasOwnProperty(propName) ? attrNS[propName] : null,
mustUseProperty: checkMask(propConfig, MUST_USE_PROPERTY),
hasBooleanValue: checkMask(propConfig, HAS_BOOLEAN_VALUE),
hasNumericValue: checkMask(propConfig, HAS_NUMERIC_VALUE),
hasPositiveNumericValue: checkMask(propConfig, HAS_POSITIVE_NUMERIC_VALUE),
hasOverloadedBooleanValue: checkMask(propConfig, HAS_OVERLOADED_BOOLEAN_VALUE)
};
}
}
function checkMask(value, bitmask) {
return (value & bitmask) === bitmask;
}
/**
* Sets the value for a property on a node.
*
* @param {DOMElement} node
* @param {string} name
* @param {*} value
*/
function setPropValue(node, name, value) {
var propInfo = properties.hasOwnProperty(name) && properties[name];
if (propInfo) {
// should delete value from dom
if (value == null || propInfo.hasBooleanValue && !value || propInfo.hasNumericValue && isNaN(value) || propInfo.hasPositiveNumericValue && value < 1 || propInfo.hasOverloadedBooleanValue && value === false) {
removePropValue(node, name);
} else if (propInfo.mustUseProperty) {
var propName = propInfo.propertyName;
// dom.value has side effect
if (propName !== 'value' || '' + node[propName] !== '' + value) {
node[propName] = value;
}
} else {
var attributeName = propInfo.attributeName;
var namespace = propInfo.attributeNamespace;
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
if (namespace) {
node.setAttributeNS(namespace, attributeName, '' + value);
} else if (propInfo.hasBooleanValue || propInfo.hasOverloadedBooleanValue && value === true) {
node.setAttribute(attributeName, '');
} else {
node.setAttribute(attributeName, '' + value);
}
}
} else if (isCustomAttribute(name) && VALID_ATTRIBUTE_NAME_REGEX.test(name)) {
if (value == null) {
node.removeAttribute(name);
} else {
node.setAttribute(name, '' + value);
}
}
}
/**
* Deletes the value for a property on a node.
*
* @param {DOMElement} node
* @param {string} name
*/
function removePropValue(node, name) {
var propInfo = properties.hasOwnProperty(name) && properties[name];
if (propInfo) {
if (propInfo.mustUseProperty) {
var propName = propInfo.propertyName;
if (propInfo.hasBooleanValue) {
node[propName] = false;
} else {
// dom.value accept string value has side effect
if (propName !== 'value' || '' + node[propName] !== '') {
node[propName] = '';
}
}
} else {
node.removeAttribute(propInfo.attributeName);
}
} else if (isCustomAttribute(name)) {
node.removeAttribute(name);
}
}
function isFn(obj) {
return typeof obj === 'function';
}
var isArr = Array.isArray;
function noop() {}
function identity(obj) {
return obj;
}
function pipe(fn1, fn2) {
return function () {
fn1.apply(this, arguments);
return fn2.apply(this, arguments);
};
}
function addItem(list, item) {
list[list.length] = item;
}
function flatEach(list, iteratee, a) {
var len = list.length;
var i = -1;
while (len--) {
var item = list[++i];
if (isArr(item)) {
flatEach(item, iteratee, a);
} else {
iteratee(item, a);
}
}
}
function extend(to, from) {
if (!from) {
return to;
}
var keys = Object.keys(from);
var i = keys.length;
while (i--) {
to[keys[i]] = from[keys[i]];
}
return to;
}
var uid = 0;
function getUid() {
return ++uid;
}
var EVENT_KEYS = /^on/i;
function setProp(elem, key, value, isCustomComponent) {
if (EVENT_KEYS.test(key)) {
addEvent(elem, key, value);
} else if (key === 'style') {
setStyle(elem.style, value);
} else if (key === HTML_KEY) {
if (value && value.__html != null) {
elem.innerHTML = value.__html;
}
} else if (isCustomComponent) {
if (value == null) {
elem.removeAttribute(key);
} else {
elem.setAttribute(key, '' + value);
}
} else {
setPropValue(elem, key, value);
}
}
function removeProp(elem, key, oldValue, isCustomComponent) {
if (EVENT_KEYS.test(key)) {
removeEvent(elem, key);
} else if (key === 'style') {
removeStyle(elem.style, oldValue);
} else if (key === HTML_KEY) {
elem.innerHTML = '';
} else if (isCustomComponent) {
elem.removeAttribute(key);
} else {
removePropValue(elem, key);
}
}
function patchProp(elem, key, value, oldValue, isCustomComponent) {
if (key === 'value' || key === 'c