UNPKG

soot

Version:
1,296 lines (1,283 loc) 55.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.Soot = {}))); }(this, (function (exports) { 'use strict'; var NO_OP = "$NO_OP"; var ERROR_MSG = "Error"; var isArray = Array.isArray; function isStatefulComponent(o) { return !isUndefined(o.prototype) && !isUndefined(o.prototype.render); } function isStringOrNumber(o) { var type = typeof o; return type === "string" || type === "number"; } function isNullOrUndef(o) { return isUndefined(o) || isNull(o); } function isInvalid(o) { return isNull(o) || o === false || isTrue(o) || isUndefined(o); } function isFunction(o) { return typeof o === "function"; } function isString(o) { return typeof o === "string"; } function isNull(o) { return o === null; } function isTrue(o) { return o === true; } function isUndefined(o) { return o === void 0; } function isObject(o) { return typeof o === "object"; } /** * Throws error * @param {string?} message */ function throwError(message) { if (!message) { message = ERROR_MSG; } throw new Error("Soot Error: " + message); } function warning(message) { // tslint:disable-next-line:no-console console.warn(message); } function combineFrom(first, second) { var out = {}; if (first) { for (var key in first) { out[key] = first[key]; } } if (second) { for (var key in second) { out[key] = second[key]; } } return out; } // tslint:disable-next-line { } /** * Creates virtual node * @param {number} flags * @param {*} type * @param {string|null?} className * @param {Object?} children * @param {Object?} props * @param {*?} key * @param {Object|Function?} ref * @returns {Object} returns new virtual node */ function V(flags, type, className, children, props, key, ref) { if ((flags & 8 /* ComponentUnknown */) > 0) { flags = isStatefulComponent(type) ? 2 /* ComponentClass */ : 4 /* ComponentFunction */; } if ((flags & 14 /* Component */) > 0) { var defaultProps = type.defaultProps; if (!isNullOrUndef(defaultProps)) { if (!props) { props = defaultProps; } else { for (var prop in defaultProps) { if (isUndefined(props[prop])) { props[prop] = defaultProps[prop]; } } } } } else { if (props) { if (className === null) { className = props.hasOwnProperty("className") ? props.className : null; } if (children === null) { children = props.hasOwnProperty("children") ? props.children : null; } } } var vNode = { c: isUndefined(children) ? null : children, cN: isUndefined(className) ? null : className, f: flags, k: isUndefined(key) ? null : key, p: isUndefined(props) ? null : props, r: isUndefined(ref) ? null : ref, t: type }; { } return vNode; } function isVNode(o) { return !!o.f; } /** * Links given data to event as first parameter * @param {*} data data to be linked, it will be available in function as first parameter * @param {Function} event Function to be called when event occurs * @returns {Object} Return null when event is not valid, to avoid creating unnecessary event handlers */ function linkEvent(data, event) { if (isFunction(event)) { return { data: data, event: event }; } return null; } function createIV(input, pos, key) { return { b: null, c: null, d: null, f: 0, i: null, k: key, p: pos, v: input }; } var xlinkNS = "http://www.w3.org/1999/xlink"; var xmlNS = "http://www.w3.org/XML/1998/namespace"; var svgNS = "http://www.w3.org/2000/svg"; var booleanProps = new Set(); booleanProps.add("muted"); booleanProps.add("scoped"); booleanProps.add("loop"); booleanProps.add("open"); booleanProps.add("checked"); booleanProps.add("default"); booleanProps.add("capture"); booleanProps.add("disabled"); booleanProps.add("readOnly"); booleanProps.add("required"); booleanProps.add("autoplay"); booleanProps.add("controls"); booleanProps.add("seamless"); booleanProps.add("reversed"); booleanProps.add("allowfullscreen"); booleanProps.add("novalidate"); booleanProps.add("hidden"); booleanProps.add("autofocus"); booleanProps.add("selected"); booleanProps.add("multiple"); var namespaces = new Map(); namespaces.set("xlink:href", xlinkNS); namespaces.set("xlink:arcrole", xlinkNS); namespaces.set("xlink:actuate", xlinkNS); namespaces.set("xlink:show", xlinkNS); namespaces.set("xlink:role", xlinkNS); namespaces.set("xlink:title", xlinkNS); namespaces.set("xlink:type", xlinkNS); namespaces.set("xml:base", xmlNS); namespaces.set("xml:lang", xmlNS); namespaces.set("xml:space", xmlNS); var skipProps = new Set(); skipProps.add("children"); skipProps.add("ref"); skipProps.add("key"); skipProps.add("className"); var syntheticEvents = new Set(); syntheticEvents.add("onClick"); syntheticEvents.add("onMouseDown"); syntheticEvents.add("onMouseUp"); syntheticEvents.add("onMouseMove"); syntheticEvents.add("onSubmit"); syntheticEvents.add("onDblClick"); syntheticEvents.add("onKeyDown"); syntheticEvents.add("onKeyUp"); syntheticEvents.add("onKeyPress"); function unmount(iv, parentDom) { var input = iv.v; if (isStringOrNumber(input)) { if (!isNull(parentDom)) { removeChild(parentDom, iv.d); } } else { // It's vNode var flags = input.f; var dom = iv.d; var ref = input.r; var props = input.p; var childIVs = iv.c; if ((flags & 241 /* Element */) > 0) { if (isFunction(ref)) { ref(null); } if (!isNull(childIVs)) { if (isArray(childIVs)) { for (var i = 0, len = childIVs.length; i < len; i++) { unmount(childIVs[i], null); } } else { unmount(childIVs, null); } } // Remove synthetic events if (!isNull(props)) { for (var name_1 in props) { if (syntheticEvents.has(name_1)) { handleEvent(name_1, null, dom); } } } } else if ((flags & 14 /* Component */) > 0) { if ((flags & 2 /* ComponentClass */) > 0) { var instance = iv.i; if (isFunction(instance.componentWillUnmount)) { instance.componentWillUnmount(); } if (isFunction(ref)) { ref(null); } instance.__UN = true; iv.i = null; } else { if (!isNullOrUndef(ref)) { if (isFunction(ref.onComponentWillUnmount)) { ref.onComponentWillUnmount(dom, props); } } } iv.b = null; if (!isNull(childIVs) && !isInvalid(childIVs.v)) { unmount(childIVs, null); } } if (!isNull(parentDom) && !isNull(dom)) { removeChild(parentDom, dom); } } } // We need EMPTY_OBJ defined in one place. // Its used for comparison so we cant inline it into shared var EMPTY_OBJ = {}; { Object.freeze(EMPTY_OBJ); } function replaceDOM(iv, parentDom, newDOM) { unmount(iv, null); replaceChild(parentDom, newDOM, iv.d); iv.d = newDOM; } function setTextContent(dom, text) { if (text !== "") { dom.textContent = text; } else { dom.appendChild(document.createTextNode("")); } } function appendChild(parentDom, dom) { parentDom.appendChild(dom); } function insertOrAppend(iv, parentDom, newNode, nextNode) { if (isNull(nextNode)) { appendChild(parentDom, newNode); } else { parentDom.insertBefore(newNode, nextNode); } iv.d = newNode; } function replaceWithNewNode(iv, nextInput, parentDom, lifecycle, isSVG) { var oldNode = iv.d; unmount(iv, null); var newDom = mount(iv, nextInput, parentDom, lifecycle, isSVG, false); if (isNull(newDom)) { removeChild(parentDom, oldNode); } else { replaceChild(parentDom, newDom, oldNode); } iv.d = newDom; } function replaceChild(parentDom, nextDom, lastDom) { parentDom.replaceChild(nextDom, lastDom); } function removeChild(parentDom, dom) { parentDom.removeChild(dom); } function removeAllChildren(parentIV, dom, children) { for (var i = 0, len = children.length; i < len; i++) { unmount(children[i], null); } parentIV.c = null; parentIV.f = 1 /* HasInvalidChildren */; dom.textContent = ""; } function mount(iv, input, parentDom, lifecycle, isSVG, insertIntoDOM) { // Text - Number if (isStringOrNumber(input)) { return mountText(iv, input, parentDom, insertIntoDOM); } else { // VNode var flags = input.f; if ((flags & 241 /* Element */) > 0) { return mountElement(iv, input, parentDom, lifecycle, isSVG, insertIntoDOM); } else if ((flags & 14 /* Component */) > 0) { return mountComponent(iv, input, parentDom, lifecycle, isSVG, (flags & 2 /* ComponentClass */) > 0, insertIntoDOM); } else { { if (typeof input === "object") { throwError("mount() received an object that's not a valid VNode, you should stringify it first. Object: \"" + JSON.stringify(input) + "\"."); } else { throwError("mount() expects a valid VNode, instead it received an object with the type \"" + typeof input + "\"."); } } throwError(); } } } function mountText(iv, text, parentDom, insertIntoDom) { var dom = document.createTextNode(text); if (insertIntoDom) { iv.d = dom; appendChild(parentDom, dom); } iv.f = 8 /* HasTextChildren */; return dom; } function mountElement(iv, vNode, parentDom, lifecycle, isSVG, insertIntoDom) { var dom; var flags = vNode.f; var tag = vNode.t; isSVG = isSVG || (flags & 16 /* SvgElement */) > 0; if (isSVG) { dom = document.createElementNS(svgNS, tag); } else { dom = document.createElement(tag); } var children = vNode.c; var props = vNode.p; var className = vNode.cN; var ref = vNode.r; if (!isInvalid(children)) { if (isStringOrNumber(children)) { // Text setTextContent(dom, children); iv.f = 8 /* HasTextChildren */; } else { var childrenIsSVG = isSVG === true && tag !== "foreignObject"; if (isArray(children)) { // Array mountArrayChildren(iv, children, dom, lifecycle, childrenIsSVG, false); } else { // VNode var childIV = createIV(children, 0, null); iv.c = childIV; iv.f = 16 /* HasBasicChildren */; mount(childIV, children, dom, lifecycle, childrenIsSVG, true); } } } else { iv.f = 1 /* HasInvalidChildren */; } if (!isNull(props)) { for (var prop in props) { patchProp(prop, null, props[prop], dom, isSVG); } } if (!isNull(className)) { if (isSVG) { dom.setAttribute("class", className); } else { dom.className = className; } } if (isFunction(ref)) { lifecycle.push((function () { return ref(dom); })); } if (insertIntoDom) { iv.d = dom; appendChild(parentDom, dom); } return dom; } function mountArrayChildren(iv, children, dom, lifecycle, isSVG, isKeyed) { iv.c = null; iv.f = 1 /* HasInvalidChildren */; // default to invalid for (var i = 0, len = children.length; i < len; i++) { var child = children[i]; if (!isInvalid(child)) { if (iv.c === null) { iv.c = []; isKeyed = isKeyed || isObject(child) ? !isNullOrUndef(child.k) : false; iv.f = isKeyed ? 2 /* HasKeyedChildren */ : 4 /* HasNonKeydChildren */; } var childIV = createIV(child, i, child.k); iv.c.push(childIV); mount(childIV, child, dom, lifecycle, isSVG, true); } } } function mountComponent(iv, vNode, parentDom, lifecycle, isSVG, isClass, insertIntoDom) { var dom = null; var type = vNode.t; var props = vNode.p || EMPTY_OBJ; var childIV; var renderOutput; var instance; var ref = vNode.r; if (isClass) { instance = new type(props); iv.i = instance; instance.__BS = false; instance.__PN = parentDom; if (instance.p === EMPTY_OBJ) { instance.p = props; } instance.__LC = lifecycle; instance.__PSS = true; instance.__SVG = isSVG; if (isFunction(instance.componentWillMount)) { instance.__BR = true; instance.componentWillMount(); instance.__BR = false; } renderOutput = instance.render(props, instance.state); instance.__PSS = false; instance.__IV = iv; } else { renderOutput = type(props); } if (!isInvalid(renderOutput)) { iv.c = childIV = createIV(renderOutput, 0, null); childIV.d = dom = mount(childIV, renderOutput, parentDom, lifecycle, isSVG, false); iv.f = 16 /* HasBasicChildren */; if (isObject(renderOutput)) { if ((renderOutput.f & 14 /* Component */) > 0) { childIV.b = iv; } } } else { iv.f = 1 /* HasInvalidChildren */; } if (isClass) { if (isFunction(ref)) { ref(instance); } if (isFunction(instance.componentDidMount)) { lifecycle.push((function () { return instance.componentDidMount(); })); } } else { if (!isNull(ref)) { if (isFunction(ref.onComponentWillMount)) { ref.onComponentWillMount(props); } if (isFunction(ref.onComponentDidMount)) { lifecycle.push((function () { return ref.onComponentDidMount(dom, props); })); } } } if (insertIntoDom && !isNull(dom)) { iv.d = dom; appendChild(parentDom, dom); } return dom; } var roots = new Map(); var queue = false; { if (document.body === null) { warning('Soot warning: you cannot initialize soot without "document.body". Wait on "DOMContentLoaded" event, add script to bottom of body, or use async/defer attributes on script tag.'); } } function triggerLifecycle(listeners) { for (var i = 0, len = listeners.length; i < len; i++) { listeners[i](); } listeners.length = 0; } var delegatedEvents = new Map(); function handleEvent(name, nextEvent, dom) { var delegatedRoots = delegatedEvents.get(name); if (nextEvent) { if (!delegatedRoots) { delegatedRoots = { items: new Map(), docEvent: null }; delegatedRoots.docEvent = attachEventToDocument(name, delegatedRoots); delegatedEvents.set(name, delegatedRoots); } delegatedRoots.items.set(dom, nextEvent); } else if (delegatedRoots) { var items = delegatedRoots.items; if (items.delete(dom)) { // If any items were deleted, check if listener need to be removed if (items.size === 0) { document.removeEventListener(normalizeEventName(name), delegatedRoots.docEvent); delegatedEvents.delete(name); } } } } function normalizeEventName(name) { return name.substr(2).toLowerCase(); } function stopPropagation() { this.cancelBubble = true; this.stopImmediatePropagation(); } function attachEventToDocument(name, delegatedRoots) { var docEvent = function (event) { var items = delegatedRoots.items; var count = items.size; if (count > 0) { var isClick = event.type === "click"; queue = true; event.stopPropagation = stopPropagation; var dom_1 = event.target; Object.defineProperty(event, "currentTarget", { configurable: true, get: function () { return dom_1; } }); while (count > 0) { var eventsToTrigger = items.get(dom_1); if (!isUndefined(eventsToTrigger)) { count--; if (isFunction(eventsToTrigger)) { eventsToTrigger(event); } else { eventsToTrigger.event(eventsToTrigger.data, event); } if (event.cancelBubble) { break; } } dom_1 = dom_1.parentNode; // Html Nodes can be nested fe: span inside button in that scenario browser does not handle disabled attribute on parent, // because the event listener is on document.body // Don't process clicks on disabled elements if (isNull(dom_1) || (isClick && dom_1.disabled)) { break; } } flushSetStates(); queue = false; } }; document.addEventListener(normalizeEventName(name), docEvent); return docEvent; } /** * Renders virtual node tree into parent node. * @param {VNode | null | string | number} input v to be rendered * @param {*} parentDom DOM node which content will be replaced by virtual node * @param {Function?} callback Callback to be called after rendering has finished * @returns {void} */ function render(input, parentDom, callback) { if (input === NO_OP) { return; } queue = true; var root = roots.get(parentDom); var rootIV; var lifecycle; if (root === undefined) { if (isInvalid(input)) { return; } rootIV = createIV(input, 0, null); lifecycle = []; mount(rootIV, input, parentDom, lifecycle, false, true); roots.set(parentDom, { iv: rootIV, lifeCycle: lifecycle }); } else { rootIV = root.iv; lifecycle = root.lifeCycle; if (isNullOrUndef(input) && !isInvalid(rootIV.v)) { unmount(rootIV, parentDom); roots.delete(parentDom); } else { patch(rootIV, input, parentDom, lifecycle, false); } } triggerLifecycle(lifecycle); if (!isNullOrUndef(callback)) { callback(); } flushSetStates(); queue = false; } function patch(iv, nextInput, parentDom, lifecycle, isSVG) { var lastInput = iv.v; if (lastInput !== nextInput) { if (isStringOrNumber(nextInput)) { if (isStringOrNumber(lastInput)) { iv.d.nodeValue = nextInput; } else { replaceDOM(iv, parentDom, mountText(iv, nextInput, null, false)); } } else if (isStringOrNumber(lastInput)) { replaceDOM(iv, parentDom, mount(iv, nextInput, parentDom, lifecycle, isSVG, false)); } else { var lastFlags = lastInput.f; var nextFlags = nextInput.f; if ((nextFlags & 241 /* Element */) > 0) { if ((lastFlags & 241 /* Element */) > 0) { patchElement(iv, lastInput, nextInput, parentDom, lifecycle, isSVG); } else { replaceDOM(iv, parentDom, mountElement(iv, nextInput, parentDom, lifecycle, isSVG, false)); } } else if ((nextFlags & 14 /* Component */) > 0) { var isClass = (nextFlags & 2 /* ComponentClass */) > 0; if ((lastFlags & 14 /* Component */) > 0) { var lastType = lastInput.t; var nextType = nextInput.t; var lastKey = lastInput.k; var nextKey = nextInput.k; if (lastType !== nextType || lastKey !== nextKey) { replaceWithNewNode(iv, nextInput, parentDom, lifecycle, isSVG); } else { var nextProps = nextInput.p || EMPTY_OBJ; if (isClass) { var instance = iv.i; instance.__IV = iv; if (!instance.__UN) { handleUpdate(instance, instance.state, nextProps, false, isSVG, lifecycle, parentDom); } } else { var shouldUpdate = true; var lastProps = lastInput.p; var nextHooks = nextInput.r; var nextHooksDefined = !isNullOrUndef(nextHooks); if (lastKey !== nextKey) { shouldUpdate = true; } else { if (nextHooksDefined && !isNullOrUndef(nextHooks.onComponentShouldUpdate)) { shouldUpdate = nextHooks.onComponentShouldUpdate(lastProps, nextProps); } } if (shouldUpdate !== false) { if (nextHooksDefined && !isNullOrUndef(nextHooks.onComponentWillUpdate)) { nextHooks.onComponentWillUpdate(lastProps, nextProps); } var renderOutput = nextType(nextProps); if (renderOutput !== NO_OP) { patchChildren(iv, renderOutput, parentDom, lifecycle, isSVG); iv.d = iv.c === null ? null : iv.c.d; if (nextHooksDefined && !isNullOrUndef(nextHooks.onComponentDidUpdate)) { nextHooks.onComponentDidUpdate(lastProps, nextProps); } } } } } } else { replaceDOM(iv, parentDom, mountComponent(iv, nextInput, parentDom, lifecycle, isSVG, isClass, false)); } } } } iv.v = nextInput; } function patchChildren(parentIV, nextInput, parentDOM, lifecycle, childrenIsSVG) { var childFlags = parentIV.f; var childIVs = parentIV.c; if ((childFlags & 8 /* HasTextChildren */) > 0) { if (isInvalid(nextInput)) { removeChild(parentDOM, parentDOM.firstChild); parentIV.f = 1 /* HasInvalidChildren */; } else if (isStringOrNumber(nextInput)) { parentDOM.firstChild.nodeValue = nextInput; } else if (isVNode(nextInput)) { childIVs = createIV(nextInput, 0, null); var newDOM = mount(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG, false); replaceChild(parentDOM, newDOM, parentDOM.firstChild); childIVs.d = newDOM; parentIV.c = childIVs; parentIV.f = 16 /* HasBasicChildren */; } else { parentDOM.removeChild(parentDOM.firstChild); mountArrayChildren(parentIV, nextInput, parentDOM, lifecycle, childrenIsSVG, false); } } else if ((childFlags & 1 /* HasInvalidChildren */) > 0) { if (!isInvalid(nextInput)) { if (isStringOrNumber(nextInput)) { setTextContent(parentDOM, nextInput); parentIV.f = 8 /* HasTextChildren */; } else if (isVNode(nextInput)) { childIVs = createIV(nextInput, 0, null); mount(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG, true); parentIV.c = childIVs; parentIV.f = 16 /* HasBasicChildren */; } else { mountArrayChildren(parentIV, nextInput, parentDOM, lifecycle, childrenIsSVG, false); } } } else if ((childFlags & 16 /* HasBasicChildren */) > 0) { if (isInvalid(nextInput)) { unmount(childIVs, parentDOM); parentIV.c = null; parentIV.f = 1 /* HasInvalidChildren */; } else if (isVNode(nextInput)) { patch(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG); } else if (isStringOrNumber(nextInput)) { replaceWithNewNode(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG); } else { unmount(childIVs, parentDOM); mountArrayChildren(parentIV, nextInput, parentDOM, lifecycle, childrenIsSVG, false); } } else { // Multiple children if (isInvalid(nextInput)) { removeAllChildren(parentIV, parentDOM, childIVs); } else if (isArray(nextInput)) { var lastLength = childIVs === null ? 0 : childIVs.length; var nextLength = nextInput.length; if (lastLength === 0) { if (nextLength > 0) { mountArrayChildren(parentIV, nextInput, parentDOM, lifecycle, childrenIsSVG, false); } } else if (nextLength === 0) { removeAllChildren(parentIV, parentDOM, childIVs); } else if ((childFlags & 2 /* HasKeyedChildren */) > 0 && (nextInput.length > 0 && !isNullOrUndef(nextInput[0]) && !isNullOrUndef(nextInput[0].k))) { patchKeyedChildren(parentIV, childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG, lastLength, nextLength); } else { patchNonKeyedChildren(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG, lastLength, nextLength); } } else if (isStringOrNumber(nextInput)) { removeAllChildren(parentIV, parentDOM, childIVs); setTextContent(parentDOM, nextInput); parentIV.f = 8 /* HasTextChildren */; } else { // vNode removeAllChildren(parentIV, parentDOM, childIVs); childIVs = createIV(nextInput, 0, null); mount(childIVs, nextInput, parentDOM, lifecycle, childrenIsSVG, true); parentIV.c = childIVs; parentIV.f = 16 /* HasBasicChildren */; } } } function patchElement(iv, lastVNode, nextVNode, parentDom, lifecycle, isSVG) { var nextTag = nextVNode.t; var lastTag = lastVNode.t; if (lastTag !== nextTag) { replaceWithNewNode(iv, nextVNode, parentDom, lifecycle, isSVG); } else { var dom_2 = iv.d; var lastProps = lastVNode.p; var nextProps = nextVNode.p; var lastChildren = lastVNode.c; var nextChildren = nextVNode.c; var nextFlags = nextVNode.f; var nextRef_1 = nextVNode.r; var lastClassName = lastVNode.cN; var nextClassName = nextVNode.cN; isSVG = isSVG || (nextFlags & 16 /* SvgElement */) > 0; if (lastChildren !== nextChildren) { var childrenIsSVG = isSVG === true && nextVNode.t !== "foreignObject"; patchChildren(iv, nextChildren, dom_2, lifecycle, childrenIsSVG); } if (lastProps !== nextProps) { var prop = void 0; if (isNull(lastProps)) { if (!isNull(nextProps)) { for (prop in nextProps) { patchProp(prop, null, nextProps[prop], dom_2, isSVG); } } } else { if (isNull(nextProps)) { for (prop in lastProps) { removeProp(prop, dom_2, nextFlags); } } else { for (prop in nextProps) { patchProp(prop, lastProps[prop], nextProps[prop], dom_2, isSVG); } for (prop in lastProps) { if (!nextProps.hasOwnProperty(prop)) { removeProp(prop, dom_2, nextFlags); } } } } } if (lastClassName !== nextClassName) { if (isNullOrUndef(nextClassName)) { dom_2.removeAttribute("class"); } else { if (isSVG) { dom_2.setAttribute("class", nextClassName); } else { dom_2.className = nextClassName; } } } if (lastVNode.r !== nextRef_1) { if (isFunction(nextRef_1)) { lifecycle.push((function () { return nextRef_1(dom_2); })); } } } } function removeProp(prop, dom, nextFlags) { if (prop === "value") { // When removing value of select element, it needs to be set to null instead empty string, because empty string is valid value for option which makes that option selected // MS IE/Edge don't follow html spec for textArea and v elements and we need to set empty string to value in those cases to avoid "null" and "undefined" texts dom.value = (nextFlags & 128 /* SelectElement */) > 0 ? null : ""; } else if (prop === "style") { dom.removeAttribute("style"); } else if (isAttrAnEvent(prop)) { handleEvent(prop, null, dom); } else { dom.removeAttribute(prop); } } function patchNonKeyedChildren(childIVs, nextChildren, parentDOM, lifecycle, isSVG, lastIVsLength, nextChildrenLength) { var nextChild; var iteratedIV = childIVs[0]; var pos = iteratedIV.p; var nextNode = iteratedIV.d; var newChildIVs; var updatedIVs = 0; var addedIVs = 0; for (var j = 0; j < nextChildrenLength; j++) { nextChild = nextChildren[j]; if (pos === j) { if (isInvalid(nextChild)) { unmount(iteratedIV, parentDOM); lastIVsLength--; childIVs.splice(addedIVs + updatedIVs, 1); } else { patch(iteratedIV, nextChild, parentDOM, lifecycle, isSVG); updatedIVs++; } if (updatedIVs < lastIVsLength) { iteratedIV = childIVs[addedIVs + updatedIVs]; pos = iteratedIV.p; nextNode = iteratedIV.d; } else { nextNode = null; } } else if (!isInvalid(nextChild)) { newChildIVs = createIV(nextChild, j, null); insertOrAppend(newChildIVs, parentDOM, mount(newChildIVs, nextChild, parentDOM, lifecycle, isSVG, false), nextNode); childIVs.splice(j, 0, newChildIVs); addedIVs++; } } if (updatedIVs < lastIVsLength) { var firstIndex = updatedIVs; do { unmount(childIVs[addedIVs + updatedIVs++], parentDOM); } while (updatedIVs < lastIVsLength); childIVs.splice(firstIndex, lastIVsLength - firstIndex); // Remove dead IVs } } function patchKeyedChildren(parentIV, a, b, parentDOM, lifecycle, isSVG, aLength, bLength) { var aEnd = aLength - 1; var bEnd = bLength - 1; var aStart = 0; var bStart = 0; var i = -1; var j; var aNode; var bNode; var nextNode = null; var nextPos = 0; var node; var aStartNode = a[aStart]; var bStartNode = b[bStart]; var aEndNode = a[aEnd]; var bEndNode = b[bEnd]; var newChildIVs = new Array(bLength); // Step 1 // tslint:disable-next-line outer: { // Sync nodes with the same key at the beginning. while (aStartNode.k === bStartNode.k) { patch(aStartNode, bStartNode, parentDOM, lifecycle, isSVG); newChildIVs[bStart] = aStartNode; aStart++; bStart++; if (aStart > aEnd || bStart > bEnd) { break outer; } aStartNode = a[aStart]; bStartNode = b[bStart]; } // Sync nodes with the same key at the end. while (aEndNode.k === bEndNode.k) { patch(aEndNode, bEndNode, parentDOM, lifecycle, isSVG); newChildIVs[bEnd] = aEndNode; aEnd--; bEnd--; if (aStart > aEnd || bStart > bEnd) { break outer; } nextNode = aEndNode.d; nextPos = aEnd; aEndNode = a[aEnd]; bEndNode = b[bEnd]; } } if (aStart > aEnd) { if (bStart <= bEnd) { nextPos = bEnd + 1; nextNode = nextPos < bLength ? newChildIVs[nextPos].d : null; while (bStart <= bEnd) { node = b[bStart]; var childIV = createIV(node, bStart, node.k); insertOrAppend(childIV, parentDOM, mount(childIV, node, parentDOM, lifecycle, isSVG, false), nextNode); newChildIVs[bStart] = childIV; bStart++; } } } else if (bStart > bEnd) { for (i = aStart; i <= aEnd; i++) { unmount(a[i], parentDOM); } } else { var sources = new Array(bEnd - bStart + 1).fill(-1); var keyIndex = new Map(); // Mark all nodes as inserted. for (i = bStart; i <= bEnd; i++) { keyIndex.set(b[i].k, i); } var moved = false; var pos = 0; var bUpdated = 0; nextPos = 0; // Try to patch same keys and remove old for (i = aStart; i <= aEnd; i++) { aNode = a[i]; j = keyIndex.get(aNode.k); if (isUndefined(j)) { unmount(aNode, parentDOM); } else { bNode = b[j]; sources[j - bStart] = i; if (pos > j) { moved = true; } else { pos = j; } patch(aNode, bNode, parentDOM, lifecycle, isSVG); newChildIVs[j] = aNode; bUpdated++; } } if (moved) { var seq = LIS(sources); j = seq.length - 1; for (i = bEnd - bStart; i >= 0; i--) { if (sources[i] === -1) { pos = i + bStart; node = b[pos]; nextPos = pos + 1; nextNode = nextPos < bLength ? newChildIVs[nextPos].d : null; var childIV = createIV(node, bStart, node.k); insertOrAppend(childIV, parentDOM, mount(childIV, node, parentDOM, lifecycle, isSVG, false), nextNode); newChildIVs[pos] = childIV; } else { if (j < 0 || i !== seq[j]) { pos = i + bStart; nextPos = pos + 1; nextNode = nextPos < bLength ? newChildIVs[nextPos].d : null; insertOrAppend(newChildIVs[pos], parentDOM, newChildIVs[pos].d, nextNode); } else { j--; } } } } else { for (i = bEnd; i >= bStart && bUpdated !== bLength; i--) { bNode = b[i]; if (isUndefined(newChildIVs[i])) { var iv = createIV(bNode, i, bNode.k); insertOrAppend(iv, parentDOM, mount(iv, bNode, parentDOM, lifecycle, isSVG, false), nextNode); newChildIVs[i] = iv; bUpdated++; } nextNode = newChildIVs[i].d; } } } parentIV.c = newChildIVs; } // Longest Increasing Subsequence algorithm // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function LIS(arr) { var p = arr.slice(0); var result = [0]; var i; var j; var u; var v; var c; var len = arr.length; for (i = 0; i < len; i++) { var arrI = arr[i]; if (arrI !== -1) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = ((u + v) / 2) | 0; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } function isAttrAnEvent(attr) { return attr[0] === "o" && attr[1] === "n"; } function patchProp(prop, lastValue, nextValue, dom, isSVG) { if (lastValue !== nextValue && !skipProps.has(prop)) { if (booleanProps.has(prop)) { dom[prop] = !!nextValue; } else if (isAttrAnEvent(prop)) { patchEvent(prop, lastValue, nextValue, dom); } else if (isNullOrUndef(nextValue)) { dom.removeAttribute(prop); } else if (prop === "style") { patchStyle(lastValue, nextValue, dom); } else { // We optimize for condition being boolean. Its 99.9% time false if (isSVG && namespaces.has(prop)) { // If we end up in this path we can read property again dom.setAttributeNS(namespaces.get(prop), prop, nextValue); } else { dom.setAttribute(prop, nextValue); } } } } function createEventCallback(fn, data) { if (isFunction(fn)) { return function (a1, a2) { queue = true; if (isNull(data)) { fn(a1, a2); } else { fn(data, a1, a2); } flushSetStates(); queue = false; }; } return null; } function patchEvent(name, lastValue, nextValue, dom) { if (lastValue !== nextValue) { if (syntheticEvents.has(name)) { handleEvent(name, nextValue, dom); } else { var nameLowerCase = name.toLowerCase(); dom[nameLowerCase] = !isFunction(nextValue) && !isNullOrUndef(nextValue) ? createEventCallback(nextValue.event, nextValue.data) : createEventCallback(nextValue, null); } } } // We are assuming here that we come from patchProp routine // -nextAttrValue cannot be null or undefined function patchStyle(lastAttrValue, nextAttrValue, dom) { var domStyle = dom.style; var style; var value; if (isString(nextAttrValue)) { domStyle.cssText = nextAttrValue; } else if (!isNullOrUndef(lastAttrValue) && !isString(lastAttrValue)) { for (style in nextAttrValue) { value = nextAttrValue[style]; if (value !== lastAttrValue[style]) { domStyle.setProperty(style, value); } } for (style in lastAttrValue) { if (isNullOrUndef(nextAttrValue[style])) { domStyle.removeProperty(style); } } } else { for (style in nextAttrValue) { domStyle.setProperty(style, nextAttrValue[style]); } } } function queueStateChanges(component, newState, callback) { if (isFunction(newState)) { newState = newState(component.state, component.props); } var pending = component.__PS; if (isNullOrUndef(pending)) { component.__PS = pending = newState; } else { for (var stateKey in newState) { pending[stateKey] = newState[stateKey]; } } if (!component.__PSS && !component.__BR) { queueStateChange(component, callback); } else { var state = component.state; if (state === null) { component.state = pending; } else { for (var key in pending) { state[key] = pending[key]; } } component.__PS = null; if (component.__BR && isFunction(callback)) { component.__LC.push(callback.bind(component)); } } } var componentFlushQueue = []; function handleUpdate(component, nextState, nextProps, fromSetState, isSVG, lifecycle, parentDom) { var hasComponentDidUpdateIsFunction = isFunction(component.componentDidUpdate); // When component has componentDidUpdate hook, we need to clone lastState or will be modified by reference during update var prevState = hasComponentDidUpdateIsFunction ? combineFrom(nextState, null) : component.state; var prevProps = component.props; if (prevProps !== nextProps || nextProps === EMPTY_OBJ) { if (!fromSetState && isFunction(component.componentWillReceiveProps)) { // keep a copy of state before componentWillReceiveProps var beforeState = combineFrom(component.state); component.__BR = true; component.componentWillReceiveProps(nextProps); component.__BR = false; var afterState = component.state; if (beforeState !== afterState) { // if state changed in componentWillReceiveProps, reassign the beforeState component.state = beforeState; // set the afterState as pending state so the change gets picked up below component.__PSS = true; component.__PS = afterState; } } if (component.__PSS) { nextState = combineFrom(nextState, component.__PS); component.__PSS = false;