/* Inspire Tree DOM
 * @version 4.0.6
 * https://github.com/helion3/inspire-tree-dom
 * @copyright Copyright 2015 Helion3, and other contributors
 * @license Licensed under MIT
 *          see https://github.com/helion3/inspire-tree-dom/blob/master/LICENSE
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('lodash'), require('inspire-tree')) :
    typeof define === 'function' && define.amd ? define(['lodash', 'inspire-tree'], factory) :
    (global.InspireTreeDOM = factory(global._,global.InspireTree));
}(this, (function (_,InspireTree) { 'use strict';

    InspireTree = InspireTree && InspireTree.hasOwnProperty('default') ? InspireTree['default'] : InspireTree;

    var NO_OP = '$NO_OP';
    var ERROR_MSG = 'a runtime error occured! Use Inferno in development environment to find the error.';
    var isBrowser = !!(typeof window !== 'undefined' && window.document);
    var isArray = Array.isArray;
    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 isNumber(o) {
        return typeof o === 'number';
    }
    function isNull(o) {
        return o === null;
    }
    function isTrue(o) {
        return o === true;
    }
    function isUndefined(o) {
        return o === void 0;
    }
    function throwError(message) {
        if (!message) {
            message = ERROR_MSG;
        }
        throw new Error(("Inferno Error: " + message));
    }
    function combineFrom(first, second) {
        var out = {};
        if (first) {
            for (var key in first) {
                out[key] = first[key];
            }
        }
        if (second) {
            for (var key$1 in second) {
                out[key$1] = second[key$1];
            }
        }
        return out;
    }

    var keyPrefix = '$';
    function getVNode(childFlags, children, className, flags, key, props, ref, type) {
        return {
            childFlags: childFlags,
            children: children,
            className: className,
            dom: null,
            flags: flags,
            key: key === void 0 ? null : key,
            parentVNode: null,
            props: props === void 0 ? null : props,
            ref: ref === void 0 ? null : ref,
            type: type
        };
    }
    function createVNode(flags, type, className, children, childFlags, props, key, ref) {
        var childFlag = childFlags === void 0 ? 1 /* HasInvalidChildren */ : childFlags;
        var vNode = getVNode(childFlag, children, className, flags, key, props, ref, type);
        if (childFlag === 0 /* UnknownChildren */) {
            normalizeChildren(vNode, vNode.children);
        }
        return vNode;
    }
    function createComponentVNode(flags, type, props, key, ref) {
        if ((flags & 2 /* ComponentUnknown */) > 0) {
            flags = type.prototype && isFunction(type.prototype.render) ? 4 /* ComponentClass */ : 8 /* ComponentFunction */;
        }
        // set default props
        var defaultProps = type.defaultProps;
        if (!isNullOrUndef(defaultProps)) {
            if (!props) {
                props = {}; // Props can be referenced and modified at application level so always create new object
            }
            for (var prop in defaultProps) {
                if (isUndefined(props[prop])) {
                    props[prop] = defaultProps[prop];
                }
            }
        }
        if ((flags & 8 /* ComponentFunction */) > 0) {
            var defaultHooks = type.defaultHooks;
            if (!isNullOrUndef(defaultHooks)) {
                if (!ref) {
                    // As ref cannot be referenced from application level, we can use the same refs object
                    ref = defaultHooks;
                }
                else {
                    for (var prop$1 in defaultHooks) {
                        if (isUndefined(ref[prop$1])) {
                            ref[prop$1] = defaultHooks[prop$1];
                        }
                    }
                }
            }
        }
        var vNode = getVNode(1 /* HasInvalidChildren */, null, null, flags, key, props, ref, type);
        var optsVNode = options.createVNode;
        if (isFunction(optsVNode)) {
            optsVNode(vNode);
        }
        return vNode;
    }
    function createTextVNode(text, key) {
        return getVNode(1 /* HasInvalidChildren */, isNullOrUndef(text) ? '' : text, null, 16 /* Text */, key, null, null, null);
    }
    function normalizeProps(vNode) {
        var props = vNode.props;
        if (props) {
            var flags = vNode.flags;
            if (flags & 481 /* Element */) {
                if (props.children !== void 0 && isNullOrUndef(vNode.children)) {
                    normalizeChildren(vNode, props.children);
                }
                if (props.className !== void 0) {
                    vNode.className = props.className || null;
                    props.className = undefined;
                }
            }
            if (props.key !== void 0) {
                vNode.key = props.key;
                props.key = undefined;
            }
            if (props.ref !== void 0) {
                if (flags & 8 /* ComponentFunction */) {
                    vNode.ref = combineFrom(vNode.ref, props.ref);
                }
                else {
                    vNode.ref = props.ref;
                }
                props.ref = undefined;
            }
        }
        return vNode;
    }
    function directClone(vNodeToClone) {
        var newVNode;
        var flags = vNodeToClone.flags;
        if (flags & 14 /* Component */) {
            var props;
            var propsToClone = vNodeToClone.props;
            if (!isNull(propsToClone)) {
                props = {};
                for (var key in propsToClone) {
                    props[key] = propsToClone[key];
                }
            }
            newVNode = createComponentVNode(flags, vNodeToClone.type, props, vNodeToClone.key, vNodeToClone.ref);
        }
        else if (flags & 481 /* Element */) {
            newVNode = createVNode(flags, vNodeToClone.type, vNodeToClone.className, vNodeToClone.children, vNodeToClone.childFlags, vNodeToClone.props, vNodeToClone.key, vNodeToClone.ref);
        }
        else if (flags & 16 /* Text */) {
            newVNode = createTextVNode(vNodeToClone.children, vNodeToClone.key);
        }
        else if (flags & 1024 /* Portal */) {
            newVNode = vNodeToClone;
        }
        return newVNode;
    }
    function createVoidVNode() {
        return createTextVNode('', null);
    }
    function _normalizeVNodes(nodes, result, index, currentKey) {
        for (var len = nodes.length; index < len; index++) {
            var n = nodes[index];
            if (!isInvalid(n)) {
                var newKey = currentKey + keyPrefix + index;
                if (isArray(n)) {
                    _normalizeVNodes(n, result, 0, newKey);
                }
                else {
                    if (isStringOrNumber(n)) {
                        n = createTextVNode(n, newKey);
                    }
                    else {
                        var oldKey = n.key;
                        var isPrefixedKey = isString(oldKey) && oldKey[0] === keyPrefix;
                        if (!isNull(n.dom) || isPrefixedKey) {
                            n = directClone(n);
                        }
                        if (isNull(oldKey) || isPrefixedKey) {
                            n.key = newKey;
                        }
                        else {
                            n.key = currentKey + oldKey;
                        }
                    }
                    result.push(n);
                }
            }
        }
    }
    function normalizeChildren(vNode, children) {
        var newChildren;
        var newChildFlags = 1 /* HasInvalidChildren */;
        // Don't change children to match strict equal (===) true in patching
        if (isInvalid(children)) {
            newChildren = children;
        }
        else if (isString(children)) {
            newChildFlags = 2 /* HasVNodeChildren */;
            newChildren = createTextVNode(children);
        }
        else if (isNumber(children)) {
            newChildFlags = 2 /* HasVNodeChildren */;
            newChildren = createTextVNode(children + '');
        }
        else if (isArray(children)) {
            var len = children.length;
            if (len === 0) {
                newChildren = null;
                newChildFlags = 1 /* HasInvalidChildren */;
            }
            else {
                // we assign $ which basically means we've flagged this array for future note
                // if it comes back again, we need to clone it, as people are using it
                // in an immutable way
                // tslint:disable-next-line
                if (Object.isFrozen(children) || children['$'] === true) {
                    children = children.slice();
                }
                newChildFlags = 8 /* HasKeyedChildren */;
                for (var i = 0; i < len; i++) {
                    var n = children[i];
                    if (isInvalid(n) || isArray(n)) {
                        newChildren = newChildren || children.slice(0, i);
                        _normalizeVNodes(children, newChildren, i, '');
                        break;
                    }
                    else if (isStringOrNumber(n)) {
                        newChildren = newChildren || children.slice(0, i);
                        newChildren.push(createTextVNode(n, keyPrefix + i));
                    }
                    else {
                        var key = n.key;
                        var isNullDom = isNull(n.dom);
                        var isNullKey = isNull(key);
                        var isPrefixed = !isNullKey && key[0] === keyPrefix;
                        if (!isNullDom || isNullKey || isPrefixed) {
                            newChildren = newChildren || children.slice(0, i);
                            if (!isNullDom || isPrefixed) {
                                n = directClone(n);
                            }
                            if (isNullKey || isPrefixed) {
                                n.key = keyPrefix + i;
                            }
                            newChildren.push(n);
                        }
                        else if (newChildren) {
                            newChildren.push(n);
                        }
                    }
                }
                newChildren = newChildren || children;
                newChildren.$ = true;
            }
        }
        else {
            newChildren = children;
            if (!isNull(children.dom)) {
                newChildren = directClone(children);
            }
            newChildFlags = 2 /* HasVNodeChildren */;
        }
        vNode.children = newChildren;
        vNode.childFlags = newChildFlags;
        return vNode;
    }
    var options = {
        afterRender: null,
        beforeRender: null,
        createVNode: null,
        renderComplete: null
    };

    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 namespaces = {
        'xlink:actuate': xlinkNS,
        'xlink:arcrole': xlinkNS,
        'xlink:href': xlinkNS,
        'xlink:role': xlinkNS,
        'xlink:show': xlinkNS,
        'xlink:title': xlinkNS,
        'xlink:type': xlinkNS,
        'xml:base': xmlNS,
        'xml:lang': xmlNS,
        'xml:space': xmlNS
    };

    // We need EMPTY_OBJ defined in one place.
    // Its used for comparison so we cant inline it into shared
    var EMPTY_OBJ = {};
    var LIFECYCLE = [];
    function appendChild(parentDom, dom) {
        parentDom.appendChild(dom);
    }
    function insertOrAppend(parentDom, newNode, nextNode) {
        if (isNullOrUndef(nextNode)) {
            appendChild(parentDom, newNode);
        }
        else {
            parentDom.insertBefore(newNode, nextNode);
        }
    }
    function documentCreateElement(tag, isSVG) {
        if (isSVG === true) {
            return document.createElementNS(svgNS, tag);
        }
        return document.createElement(tag);
    }
    function replaceChild(parentDom, newDom, lastDom) {
        parentDom.replaceChild(newDom, lastDom);
    }
    function removeChild(parentDom, dom) {
        parentDom.removeChild(dom);
    }
    function callAll(arrayFn) {
        var listener;
        while ((listener = arrayFn.shift()) !== undefined) {
            listener();
        }
    }

    var attachedEventCounts = {};
    var attachedEvents = {};
    function handleEvent(name, nextEvent, dom) {
        var eventsLeft = attachedEventCounts[name];
        var eventsObject = dom.$EV;
        if (nextEvent) {
            if (!eventsLeft) {
                attachedEvents[name] = attachEventToDocument(name);
                attachedEventCounts[name] = 0;
            }
            if (!eventsObject) {
                eventsObject = dom.$EV = {};
            }
            if (!eventsObject[name]) {
                attachedEventCounts[name]++;
            }
            eventsObject[name] = nextEvent;
        }
        else if (eventsObject && eventsObject[name]) {
            attachedEventCounts[name]--;
            if (eventsLeft === 1) {
                document.removeEventListener(normalizeEventName(name), attachedEvents[name]);
                attachedEvents[name] = null;
            }
            eventsObject[name] = nextEvent;
        }
    }
    function dispatchEvents(event, target, isClick, name, eventData) {
        var dom = target;
        while (!isNull(dom)) {
            // 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 (isClick && dom.disabled) {
                return;
            }
            var eventsObject = dom.$EV;
            if (eventsObject) {
                var currentEvent = eventsObject[name];
                if (currentEvent) {
                    // linkEvent object
                    eventData.dom = dom;
                    if (currentEvent.event) {
                        currentEvent.event(currentEvent.data, event);
                    }
                    else {
                        currentEvent(event);
                    }
                    if (event.cancelBubble) {
                        return;
                    }
                }
            }
            dom = dom.parentNode;
        }
    }
    function normalizeEventName(name) {
        return name.substr(2).toLowerCase();
    }
    function stopPropagation() {
        this.cancelBubble = true;
        if (!this.immediatePropagationStopped) {
            this.stopImmediatePropagation();
        }
    }
    function attachEventToDocument(name) {
        var docEvent = function (event) {
            var type = event.type;
            var isClick = type === 'click' || type === 'dblclick';
            if (isClick && event.button !== 0) {
                // Firefox incorrectly triggers click event for mid/right mouse buttons.
                // This bug has been active for 12 years.
                // https://bugzilla.mozilla.org/show_bug.cgi?id=184051
                event.stopPropagation();
                return false;
            }
            event.stopPropagation = stopPropagation;
            // Event data needs to be object to save reference to currentTarget getter
            var eventData = {
                dom: document
            };
            Object.defineProperty(event, 'currentTarget', {
                configurable: true,
                get: function get() {
                    return eventData.dom;
                }
            });
            dispatchEvents(event, event.target, isClick, name, eventData);
            return;
        };
        document.addEventListener(normalizeEventName(name), docEvent);
        return docEvent;
    }

    function isSameInnerHTML(dom, innerHTML) {
        var tempdom = document.createElement('i');
        tempdom.innerHTML = innerHTML;
        return tempdom.innerHTML === dom.innerHTML;
    }
    function isSamePropsInnerHTML(dom, props) {
        return Boolean(props && props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html && isSameInnerHTML(dom, props.dangerouslySetInnerHTML.__html));
    }

    function triggerEventListener(props, methodName, e) {
        if (props[methodName]) {
            var listener = props[methodName];
            if (listener.event) {
                listener.event(listener.data, e);
            }
            else {
                listener(e);
            }
        }
        else {
            var nativeListenerName = methodName.toLowerCase();
            if (props[nativeListenerName]) {
                props[nativeListenerName](e);
            }
        }
    }
    function createWrappedFunction(methodName, applyValue) {
        var fnMethod = function (e) {
            e.stopPropagation();
            var vNode = this.$V;
            // If vNode is gone by the time event fires, no-op
            if (!vNode) {
                return;
            }
            var props = vNode.props || EMPTY_OBJ;
            var dom = vNode.dom;
            if (isString(methodName)) {
                triggerEventListener(props, methodName, e);
            }
            else {
                for (var i = 0; i < methodName.length; i++) {
                    triggerEventListener(props, methodName[i], e);
                }
            }
            if (isFunction(applyValue)) {
                var newVNode = this.$V;
                var newProps = newVNode.props || EMPTY_OBJ;
                applyValue(newProps, dom, false, newVNode);
            }
        };
        Object.defineProperty(fnMethod, 'wrapped', {
            configurable: false,
            enumerable: false,
            value: true,
            writable: false
        });
        return fnMethod;
    }

    function isCheckedType(type) {
        return type === 'checkbox' || type === 'radio';
    }
    var onTextInputChange = createWrappedFunction('onInput', applyValueInput);
    var wrappedOnChange = createWrappedFunction(['onClick', 'onChange'], applyValueInput);
    /* tslint:disable-next-line:no-empty */
    function emptywrapper(event) {
        event.stopPropagation();
    }
    emptywrapper.wrapped = true;
    function inputEvents(dom, nextPropsOrEmpty) {
        if (isCheckedType(nextPropsOrEmpty.type)) {
            dom.onchange = wrappedOnChange;
            dom.onclick = emptywrapper;
        }
        else {
            dom.oninput = onTextInputChange;
        }
    }
    function applyValueInput(nextPropsOrEmpty, dom) {
        var type = nextPropsOrEmpty.type;
        var value = nextPropsOrEmpty.value;
        var checked = nextPropsOrEmpty.checked;
        var multiple = nextPropsOrEmpty.multiple;
        var defaultValue = nextPropsOrEmpty.defaultValue;
        var hasValue = !isNullOrUndef(value);
        if (type && type !== dom.type) {
            dom.setAttribute('type', type);
        }
        if (!isNullOrUndef(multiple) && multiple !== dom.multiple) {
            dom.multiple = multiple;
        }
        if (!isNullOrUndef(defaultValue) && !hasValue) {
            dom.defaultValue = defaultValue + '';
        }
        if (isCheckedType(type)) {
            if (hasValue) {
                dom.value = value;
            }
            if (!isNullOrUndef(checked)) {
                dom.checked = checked;
            }
        }
        else {
            if (hasValue && dom.value !== value) {
                dom.defaultValue = value;
                dom.value = value;
            }
            else if (!isNullOrUndef(checked)) {
                dom.checked = checked;
            }
        }
    }

    function updateChildOptionGroup(vNode, value) {
        var type = vNode.type;
        if (type === 'optgroup') {
            var children = vNode.children;
            var childFlags = vNode.childFlags;
            if (childFlags & 12 /* MultipleChildren */) {
                for (var i = 0, len = children.length; i < len; i++) {
                    updateChildOption(children[i], value);
                }
            }
            else if (childFlags === 2 /* HasVNodeChildren */) {
                updateChildOption(children, value);
            }
        }
        else {
            updateChildOption(vNode, value);
        }
    }
    function updateChildOption(vNode, value) {
        var props = vNode.props || EMPTY_OBJ;
        var dom = vNode.dom;
        // we do this as multiple may have changed
        dom.value = props.value;
        if ((isArray(value) && value.indexOf(props.value) !== -1) || props.value === value) {
            dom.selected = true;
        }
        else if (!isNullOrUndef(value) || !isNullOrUndef(props.selected)) {
            dom.selected = props.selected || false;
        }
    }
    var onSelectChange = createWrappedFunction('onChange', applyValueSelect);
    function selectEvents(dom) {
        dom.onchange = onSelectChange;
    }
    function applyValueSelect(nextPropsOrEmpty, dom, mounting, vNode) {
        var multiplePropInBoolean = Boolean(nextPropsOrEmpty.multiple);
        if (!isNullOrUndef(nextPropsOrEmpty.multiple) && multiplePropInBoolean !== dom.multiple) {
            dom.multiple = multiplePropInBoolean;
        }
        var childFlags = vNode.childFlags;
        if ((childFlags & 1 /* HasInvalidChildren */) === 0) {
            var children = vNode.children;
            var value = nextPropsOrEmpty.value;
            if (mounting && isNullOrUndef(value)) {
                value = nextPropsOrEmpty.defaultValue;
            }
            if (childFlags & 12 /* MultipleChildren */) {
                for (var i = 0, len = children.length; i < len; i++) {
                    updateChildOptionGroup(children[i], value);
                }
            }
            else if (childFlags === 2 /* HasVNodeChildren */) {
                updateChildOptionGroup(children, value);
            }
        }
    }

    var onTextareaInputChange = createWrappedFunction('onInput', applyValueTextArea);
    var wrappedOnChange$1 = createWrappedFunction('onChange');
    function textAreaEvents(dom, nextPropsOrEmpty) {
        dom.oninput = onTextareaInputChange;
        if (nextPropsOrEmpty.onChange) {
            dom.onchange = wrappedOnChange$1;
        }
    }
    function applyValueTextArea(nextPropsOrEmpty, dom, mounting) {
        var value = nextPropsOrEmpty.value;
        var domValue = dom.value;
        if (isNullOrUndef(value)) {
            if (mounting) {
                var defaultValue = nextPropsOrEmpty.defaultValue;
                if (!isNullOrUndef(defaultValue) && defaultValue !== domValue) {
                    dom.defaultValue = defaultValue;
                    dom.value = defaultValue;
                }
            }
        }
        else if (domValue !== value) {
            /* There is value so keep it controlled */
            dom.defaultValue = value;
            dom.value = value;
        }
    }

    /**
     * There is currently no support for switching same input between controlled and nonControlled
     * If that ever becomes a real issue, then re design controlled elements
     * Currently user must choose either controlled or non-controlled and stick with that
     */
    function processElement(flags, vNode, dom, nextPropsOrEmpty, mounting, isControlled) {
        if (flags & 64 /* InputElement */) {
            applyValueInput(nextPropsOrEmpty, dom);
        }
        else if (flags & 256 /* SelectElement */) {
            applyValueSelect(nextPropsOrEmpty, dom, mounting, vNode);
        }
        else if (flags & 128 /* TextareaElement */) {
            applyValueTextArea(nextPropsOrEmpty, dom, mounting);
        }
        if (isControlled) {
            dom.$V = vNode;
        }
    }
    function addFormElementEventHandlers(flags, dom, nextPropsOrEmpty) {
        if (flags & 64 /* InputElement */) {
            inputEvents(dom, nextPropsOrEmpty);
        }
        else if (flags & 256 /* SelectElement */) {
            selectEvents(dom);
        }
        else if (flags & 128 /* TextareaElement */) {
            textAreaEvents(dom, nextPropsOrEmpty);
        }
    }
    function isControlledFormElement(nextPropsOrEmpty) {
        return nextPropsOrEmpty.type && isCheckedType(nextPropsOrEmpty.type) ? !isNullOrUndef(nextPropsOrEmpty.checked) : !isNullOrUndef(nextPropsOrEmpty.value);
    }

    function remove(vNode, parentDom) {
        unmount(vNode);
        if (parentDom && vNode.dom) {
            removeChild(parentDom, vNode.dom);
            // Let carbage collector free memory
            vNode.dom = null;
        }
    }
    function unmount(vNode) {
        var flags = vNode.flags;
        if (flags & 481 /* Element */) {
            var ref = vNode.ref;
            var props = vNode.props;
            if (isFunction(ref)) {
                ref(null);
            }
            var children = vNode.children;
            var childFlags = vNode.childFlags;
            if (childFlags & 12 /* MultipleChildren */) {
                unmountAllChildren(children);
            }
            else if (childFlags === 2 /* HasVNodeChildren */) {
                unmount(children);
            }
            if (!isNull(props)) {
                for (var name in props) {
                    switch (name) {
                        case 'onClick':
                        case 'onDblClick':
                        case 'onFocusIn':
                        case 'onFocusOut':
                        case 'onKeyDown':
                        case 'onKeyPress':
                        case 'onKeyUp':
                        case 'onMouseDown':
                        case 'onMouseMove':
                        case 'onMouseUp':
                        case 'onSubmit':
                        case 'onTouchEnd':
                        case 'onTouchMove':
                        case 'onTouchStart':
                            handleEvent(name, null, vNode.dom);
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        else {
            var children$1 = vNode.children;
            // Safe guard for crashed VNode
            if (children$1) {
                if (flags & 14 /* Component */) {
                    var ref$1 = vNode.ref;
                    if (flags & 4 /* ComponentClass */) {
                        if (isFunction(children$1.componentWillUnmount)) {
                            children$1.componentWillUnmount();
                        }
                        if (isFunction(ref$1)) {
                            ref$1(null);
                        }
                        children$1.$UN = true;
                        if (children$1.$LI) {
                            unmount(children$1.$LI);
                        }
                    }
                    else {
                        if (!isNullOrUndef(ref$1) && isFunction(ref$1.onComponentWillUnmount)) {
                            ref$1.onComponentWillUnmount(vNode.dom, vNode.props || EMPTY_OBJ);
                        }
                        unmount(children$1);
                    }
                }
                else if (flags & 1024 /* Portal */) {
                    remove(children$1, vNode.type);
                }
            }
        }
    }
    function unmountAllChildren(children) {
        for (var i = 0, len = children.length; i < len; i++) {
            unmount(children[i]);
        }
    }
    function removeAllChildren(dom, children) {
        unmountAllChildren(children);
        dom.textContent = '';
    }

    function createLinkEvent(linkEvent, nextValue) {
        return function (e) {
            linkEvent(nextValue.data, e);
        };
    }
    function patchEvent(name, lastValue, nextValue, dom) {
        var nameLowerCase = name.toLowerCase();
        if (!isFunction(nextValue) && !isNullOrUndef(nextValue)) {
            var linkEvent = nextValue.event;
            if (linkEvent && isFunction(linkEvent)) {
                dom[nameLowerCase] = createLinkEvent(linkEvent, nextValue);
            }
        }
        else {
            var domEvent = dom[nameLowerCase];
            // if the function is wrapped, that means it's been controlled by a wrapper
            if (!domEvent || !domEvent.wrapped) {
                dom[nameLowerCase] = nextValue;
            }
        }
    }
    function getNumberStyleValue(style, value) {
        switch (style) {
            case 'animationIterationCount':
            case 'borderImageOutset':
            case 'borderImageSlice':
            case 'borderImageWidth':
            case 'boxFlex':
            case 'boxFlexGroup':
            case 'boxOrdinalGroup':
            case 'columnCount':
            case 'fillOpacity':
            case 'flex':
            case 'flexGrow':
            case 'flexNegative':
            case 'flexOrder':
            case 'flexPositive':
            case 'flexShrink':
            case 'floodOpacity':
            case 'fontWeight':
            case 'gridColumn':
            case 'gridRow':
            case 'lineClamp':
            case 'lineHeight':
            case 'opacity':
            case 'order':
            case 'orphans':
            case 'stopOpacity':
            case 'strokeDasharray':
            case 'strokeDashoffset':
            case 'strokeMiterlimit':
            case 'strokeOpacity':
            case 'strokeWidth':
            case 'tabSize':
            case 'widows':
            case 'zIndex':
            case 'zoom':
                return value;
            default:
                return value + 'px';
        }
    }
    // 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;
            return;
        }
        if (!isNullOrUndef(lastAttrValue) && !isString(lastAttrValue)) {
            for (style in nextAttrValue) {
                // do not add a hasOwnProperty check here, it affects performance
                value = nextAttrValue[style];
                if (value !== lastAttrValue[style]) {
                    domStyle[style] = isNumber(value) ? getNumberStyleValue(style, value) : value;
                }
            }
            for (style in lastAttrValue) {
                if (isNullOrUndef(nextAttrValue[style])) {
                    domStyle[style] = '';
                }
            }
        }
        else {
            for (style in nextAttrValue) {
                value = nextAttrValue[style];
                domStyle[style] = isNumber(value) ? getNumberStyleValue(style, value) : value;
            }
        }
    }
    function patchProp(prop, lastValue, nextValue, dom, isSVG, hasControlledValue, lastVNode) {
        switch (prop) {
            case 'onClick':
            case 'onDblClick':
            case 'onFocusIn':
            case 'onFocusOut':
            case 'onKeyDown':
            case 'onKeyPress':
            case 'onKeyUp':
            case 'onMouseDown':
            case 'onMouseMove':
            case 'onMouseUp':
            case 'onSubmit':
            case 'onTouchEnd':
            case 'onTouchMove':
            case 'onTouchStart':
                handleEvent(prop, nextValue, dom);
                break;
            case 'children':
            case 'childrenType':
            case 'className':
            case 'defaultValue':
            case 'key':
            case 'multiple':
            case 'ref':
                break;
            case 'autoFocus':
                dom.autofocus = !!nextValue;
                break;
            case 'allowfullscreen':
            case 'autoplay':
            case 'capture':
            case 'checked':
            case 'controls':
            case 'default':
            case 'disabled':
            case 'hidden':
            case 'indeterminate':
            case 'loop':
            case 'muted':
            case 'novalidate':
            case 'open':
            case 'readOnly':
            case 'required':
            case 'reversed':
            case 'scoped':
            case 'seamless':
            case 'selected':
                dom[prop] = !!nextValue;
                break;
            case 'defaultChecked':
            case 'value':
            case 'volume':
                if (hasControlledValue && prop === 'value') {
                    return;
                }
                var value = isNullOrUndef(nextValue) ? '' : nextValue;
                if (dom[prop] !== value) {
                    dom[prop] = value;
                }
                break;
            case 'dangerouslySetInnerHTML':
                var lastHtml = (lastValue && lastValue.__html) || '';
                var nextHtml = (nextValue && nextValue.__html) || '';
                if (lastHtml !== nextHtml) {
                    if (!isNullOrUndef(nextHtml) && !isSameInnerHTML(dom, nextHtml)) {
                        if (!isNull(lastVNode)) {
                            if (lastVNode.childFlags & 12 /* MultipleChildren */) {
                                unmountAllChildren(lastVNode.children);
                            }
                            else if (lastVNode.childFlags === 2 /* HasVNodeChildren */) {
                                unmount(lastVNode.children);
                            }
                            lastVNode.children = null;
                            lastVNode.childFlags = 1 /* HasInvalidChildren */;
                        }
                        dom.innerHTML = nextHtml;
                    }
                }
                break;
            default:
                if (prop[0] === 'o' && prop[1] === 'n') {
                    patchEvent(prop, lastValue, nextValue, dom);
                }
                else if (isNullOrUndef(nextValue)) {
                    dom.removeAttribute(prop);
                }
                else if (prop === 'style') {
                    patchStyle(lastValue, nextValue, dom);
                }
                else if (isSVG && namespaces[prop]) {
                    // We optimize for isSVG being false
                    // If we end up in this path we can read property again
                    dom.setAttributeNS(namespaces[prop], prop, nextValue);
                }
                else {
                    dom.setAttribute(prop, nextValue);
                }
                break;
        }
    }
    function mountProps(vNode, flags, props, dom, isSVG) {
        var hasControlledValue = false;
        var isFormElement = (flags & 448 /* FormElement */) > 0;
        if (isFormElement) {
            hasControlledValue = isControlledFormElement(props);
            if (hasControlledValue) {
                addFormElementEventHandlers(flags, dom, props);
            }
        }
        for (var prop in props) {
            // do not add a hasOwnProperty check here, it affects performance
            patchProp(prop, null, props[prop], dom, isSVG, hasControlledValue, null);
        }
        if (isFormElement) {
            processElement(flags, vNode, dom, props, true, hasControlledValue);
        }
    }

    function createClassComponentInstance(vNode, Component, props, context) {
        var instance = new Component(props, context);
        vNode.children = instance;
        instance.$V = vNode;
        instance.$BS = false;
        instance.context = context;
        if (instance.props === EMPTY_OBJ) {
            instance.props = props;
        }
        instance.$UN = false;
        if (isFunction(instance.componentWillMount)) {
            instance.$BR = true;
            instance.componentWillMount();
            if (instance.$PSS) {
                var state = instance.state;
                var pending = instance.$PS;
                if (isNull(state)) {
                    instance.state = pending;
                }
                else {
                    for (var key in pending) {
                        state[key] = pending[key];
                    }
                }
                instance.$PSS = false;
                instance.$PS = null;
            }
            instance.$BR = false;
        }
        if (isFunction(options.beforeRender)) {
            options.beforeRender(instance);
        }
        var input = handleComponentInput(instance.render(props, instance.state, context), vNode);
        var childContext;
        if (isFunction(instance.getChildContext)) {
            childContext = instance.getChildContext();
        }
        if (isNullOrUndef(childContext)) {
            instance.$CX = context;
        }
        else {
            instance.$CX = combineFrom(context, childContext);
        }
        if (isFunction(options.afterRender)) {
            options.afterRender(instance);
        }
        instance.$LI = input;
        return instance;
    }
    function handleComponentInput(input, componentVNode) {
        if (isInvalid(input)) {
            input = createVoidVNode();
        }
        else if (isStringOrNumber(input)) {
            input = createTextVNode(input, null);
        }
        else {
            if (input.dom) {
                input = directClone(input);
            }
            if (input.flags & 14 /* Component */) {
                // if we have an input that is also a component, we run into a tricky situation
                // where the root vNode needs to always have the correct DOM entry
                // we can optimise this in the future, but this gets us out of a lot of issues
                input.parentVNode = componentVNode;
            }
        }
        return input;
    }

    function mount(vNode, parentDom, context, isSVG) {
        var flags = vNode.flags;
        if (flags & 481 /* Element */) {
            return mountElement(vNode, parentDom, context, isSVG);
        }
        if (flags & 14 /* Component */) {
            return mountComponent(vNode, parentDom, context, isSVG, (flags & 4 /* ComponentClass */) > 0);
        }
        if (flags & 512 /* Void */ || flags & 16 /* Text */) {
            return mountText(vNode, parentDom);
        }
        if (flags & 1024 /* Portal */) {
            mount(vNode.children, vNode.type, context, false);
            return (vNode.dom = mountText(createVoidVNode(), parentDom));
        }
    }
    function mountText(vNode, parentDom) {
        var dom = (vNode.dom = document.createTextNode(vNode.children));
        if (!isNull(parentDom)) {
            appendChild(parentDom, dom);
        }
        return dom;
    }
    function mountElement(vNode, parentDom, context, isSVG) {
        var flags = vNode.flags;
        var children = vNode.children;
        var props = vNode.props;
        var className = vNode.className;
        var ref = vNode.ref;
        var childFlags = vNode.childFlags;
        isSVG = isSVG || (flags & 32 /* SvgElement */) > 0;
        var dom = documentCreateElement(vNode.type, isSVG);
        vNode.dom = dom;
        if (!isNullOrUndef(className) && className !== '') {
            if (isSVG) {
                dom.setAttribute('class', className);
            }
            else {
                dom.className = className;
            }
        }
        if (!isNull(parentDom)) {
            appendChild(parentDom, dom);
        }
        if ((childFlags & 1 /* HasInvalidChildren */) === 0) {
            var childrenIsSVG = isSVG === true && vNode.type !== 'foreignObject';
            if (childFlags === 2 /* HasVNodeChildren */) {
                mount(children, dom, context, childrenIsSVG);
            }
            else if (childFlags & 12 /* MultipleChildren */) {
                mountArrayChildren(children, dom, context, childrenIsSVG);
            }
        }
        if (!isNull(props)) {
            mountProps(vNode, flags, props, dom, isSVG);
        }
        if (isFunction(ref)) {
            mountRef(dom, ref);
        }
        return dom;
    }
    function mountArrayChildren(children, dom, context, isSVG) {
        for (var i = 0, len = children.length; i < len; i++) {
            var child = children[i];
            if (!isNull(child.dom)) {
                children[i] = child = directClone(child);
            }
            mount(child, dom, context, isSVG);
        }
    }
    function mountComponent(vNode, parentDom, context, isSVG, isClass) {
        var dom;
        var type = vNode.type;
        var props = vNode.props || EMPTY_OBJ;
        var ref = vNode.ref;
        if (isClass) {
            var instance = createClassComponentInstance(vNode, type, props, context);
            vNode.dom = dom = mount(instance.$LI, null, instance.$CX, isSVG);
            mountClassComponentCallbacks(vNode, ref, instance);
            instance.$UPD = false;
        }
        else {
            var input = handleComponentInput(type(props, context), vNode);
            vNode.children = input;
            vNode.dom = dom = mount(input, null, context, isSVG);
            mountFunctionalComponentCallbacks(props, ref, dom);
        }
        if (!isNull(parentDom)) {
            appendChild(parentDom, dom);
        }
        return dom;
    }
    function createClassMountCallback(instance) {
        return function () {
            instance.componentDidMount();
        };
    }
    function mountClassComponentCallbacks(vNode, ref, instance) {
        if (isFunction(ref)) {
            ref(instance);
        }
        if (isFunction(instance.componentDidMount)) {
            LIFECYCLE.push(createClassMountCallback(instance));
        }
    }
    function createOnMountCallback(ref, dom, props) {
        return function () { return ref.onComponentDidMount(dom, props); };
    }
    function mountFunctionalComponentCallbacks(props, ref, dom) {
        if (!isNullOrUndef(ref)) {
            if (isFunction(ref.onComponentWillMount)) {
                ref.onComponentWillMount(props);
            }
            if (isFunction(ref.onComponentDidMount)) {
                LIFECYCLE.push(createOnMountCallback(ref, dom, props));
            }
        }
    }
    function mountRef(dom, value) {
        LIFECYCLE.push(function () { return value(dom); });
    }

    function hydrateComponent(vNode, dom, context, isSVG, isClass) {
        var type = vNode.type;
        var ref = vNode.ref;
        var props = vNode.props || EMPTY_OBJ;
        if (isClass) {
            var instance = createClassComponentInstance(vNode, type, props, context);
            var input = instance.$LI;
            hydrateVNode(input, dom, instance.$CX, isSVG);
            vNode.dom = input.dom;
            mountClassComponentCallbacks(vNode, ref, instance);
            instance.$UPD = false; // Mount finished allow going sync
        }
        else {
            var input$1 = handleComponentInput(type(props, context), vNode);
            hydrateVNode(input$1, dom, context, isSVG);
            vNode.children = input$1;
            vNode.dom = input$1.dom;
            mountFunctionalComponentCallbacks(props, ref, dom);
        }
    }
    function hydrateElement(vNode, dom, context, isSVG) {
        var children = vNode.children;
        var props = vNode.props;
        var className = vNode.className;
        var flags = vNode.flags;
        var ref = vNode.ref;
        isSVG = isSVG || (flags & 32 /* SvgElement */) > 0;
        if (dom.nodeType !== 1 || dom.tagName.toLowerCase() !== vNode.type) {
            var newDom = mountElement(vNode, null, context, isSVG);
            vNode.dom = newDom;
            replaceChild(dom.parentNode, newDom, dom);
        }
        else {
            vNode.dom = dom;
            var childNode = dom.firstChild;
            var childFlags = vNode.childFlags;
            if ((childFlags & 1 /* HasInvalidChildren */) === 0) {
                var nextSibling = null;
                while (childNode) {
                    nextSibling = childNode.nextSibling;
                    if (childNode.nodeType === 8) {
                        if (childNode.data === '!') {
                            dom.replaceChild(document.createTextNode(''), childNode);
                        }
                        else {
                            dom.removeChild(childNode);
                        }
                    }
                    childNode = nextSibling;
                }
                childNode = dom.firstChild;
                if (childFlags === 2 /* HasVNodeChildren */) {
                    if (isNull(childNode)) {
                        mount(children, dom, context, isSVG);
                    }
                    else {
                        nextSibling = childNode.nextSibling;
                        hydrateVNode(children, childNode, context, isSVG);
                        childNode = nextSibling;
                    }
                }
                else if (childFlags & 12 /* MultipleChildren */) {
                    for (var i = 0, len = children.length; i < len; i++) {
                        var child = children[i];
                        if (isNull(childNode)) {
                            mount(child, dom, context, isSVG);
                        }
                        else {
                            nextSibling = childNode.nextSibling;
                            hydrateVNode(child, childNode, context, isSVG);
                            childNode = nextSibling;
                        }
                    }
                }
                // clear any other DOM nodes, there should be only a single entry for the root
                while (childNode) {
                    nextSibling = childNode.nextSibling;
                    dom.removeChild(childNode);
                    childNode = nextSibling;
                }
            }
            else if (!isNull(dom.firstChild) && !isSamePropsInnerHTML(dom, props)) {
                dom.textContent = ''; // dom has content, but VNode has no children remove everything from DOM
                if (flags & 448 /* FormElement */) {
                    // If element is form element, we need to clear defaultValue also
                    dom.defaultValue = '';
                }
            }
            if (!isNull(props)) {
                mountProps(vNode, flags, props, dom, isSVG);
            }
            if (isNullOrUndef(className)) {
                if (dom.className !== '') {
                    dom.removeAttribute('class');
                }
            }
            else if (isSVG) {
                dom.setAttribute('class', className);
            }
            else {
                dom.className = className;
            }
            if (isFunction(ref)) {
                mountRef(dom, ref);
            }
        }
    }
    function hydrateText(vNode, dom) {
        if (dom.nodeType !== 3) {
            var newDom = mountText(vNode, null);
            vNode.dom = newDom;
            replaceChild(dom.parentNode, newDom, dom);
        }
        else {
            var text = vNode.children;
            if (dom.nodeValue !== text) {
                dom.nodeValue = text;
            }
            vNode.dom = dom;
        }
    }
    function hydrateVNode(vNode, dom, context, isSVG) {
        var flags = vNode.flags;
        if (flags & 14 /* Component */) {
            hydrateComponent(vNode, dom, context, isSVG, (flags & 4 /* ComponentClass */) > 0);
        }
        else if (flags & 481 /* Element */) {
            hydrateElement(vNode, dom, context, isSVG);
        }
        else if (flags & 16 /* Text */) {
            hydrateText(vNode, dom);
        }
        else if (flags & 512 /* Void */) {
            vNode.dom = dom;
        }
        else {
            throwError();
        }
    }
    function hydrate(input, parentDom, callback) {
        var dom = parentDom.firstChild;
        if (!isNull(dom)) {
            if (!isInvalid(input)) {
                hydrateVNode(input, dom, EMPTY_OBJ, false);
            }
            dom = parentDom.firstChild;
            // clear any other DOM nodes, there should be only a single entry for the root
            while ((dom = dom.nextSibling)) {
                parentDom.removeChild(dom);
            }
        }
        if (LIFECYCLE.length > 0) {
            callAll(LIFECYCLE);
        }
        parentDom.$V = input;
        if (isFunction(callback)) {
            callback();
        }
    }

    function replaceWithNewNode(lastNode, nextNode, parentDom, context, isSVG) {
        unmount(lastNode);
        replaceChild(parentDom, mount(nextNode, null, context, isSVG), lastNode.dom);
    }
    function patch(lastVNode, nextVNode, parentDom, context, isSVG) {
        if (lastVNode !== nextVNode) {
            var nextFlags = nextVNode.flags | 0;
            if (lastVNode.flags !== nextFlags || nextFlags & 2048 /* ReCreate */) {
                replaceWithNewNode(lastVNode, nextVNode, parentDom, context, isSVG);
            }
            else if (nextFlags & 481 /* Element */) {
                patchElement(lastVNode, nextVNode, parentDom, context, isSVG);
            }
            else if (nextFlags & 14 /* Component */) {
                patchComponent(lastVNode, nextVNode, parentDom, context, isSVG, (nextFlags & 4 /* ComponentClass */) > 0);
            }
            else if (nextFlags & 16 /* Text */) {
                patchText(lastVNode, nextVNode, parentDom);
            }
            else if (nextFlags & 512 /* Void */) {
                nextVNode.dom = lastVNode.dom;
            }
            else {
                // Portal
                patchPortal(lastVNode, nextVNode, context);
            }
        }
    }
    function patchPortal(lastVNode, nextVNode, context) {
        var lastContainer = lastVNode.type;
        var nextContainer = nextVNode.type;
        var nextChildren = nextVNode.children;
        patchChildren(lastVNode.childFlags, nextVNode.childFlags, lastVNode.children, nextChildren, lastContainer, context, false);
        nextVNode.dom = lastVNode.dom;
        if (lastContainer !== nextContainer && !isInvalid(nextChildren)) {
            var node = nextChildren.dom;
            lastContainer.removeChild(node);
            nextContainer.appendChild(node);
        }
    }
    function patchElement(lastVNode, nextVNode, parentDom, context, isSVG) {
        var nextTag = nextVNode.type;
        if (lastVNode.type !== nextTag) {
            replaceWithNewNode(lastVNode, nextVNode, parentDom, context, isSVG);
        }
        else {
            var dom = lastVNode.dom;
            var nextFlags = nextVNode.flags;
            var lastProps = lastVNode.props;
            var nextProps = nextVNode.props;
            var isFormElement = false;
            var hasControlledValue = false;
            var nextPropsOrEmpty;
            nextVNode.dom = dom;
            isSVG = isSVG || (nextFlags & 32 /* SvgElement */) > 0;
            // inlined patchProps  -- starts --
            if (lastProps !== nextProps) {
                var lastPropsOrEmpty = lastProps || EMPTY_OBJ;
                nextPropsOrEmpty = nextProps || EMPTY_OBJ;
                if (nextPropsOrEmpty !== EMPTY_OBJ) {
                    isFormElement = (nextFlags & 448 /* FormElement */) > 0;
                    if (isFormElement) {
                        hasControlledValue = isControlledFormElement(nextPropsOrEmpty);
                    }
                    for (var prop in nextPropsOrEmpty) {
                        var lastValue = lastPropsOrEmpty[prop];
                        var nextValue = nextPropsOrEmpty[prop];
                        if (lastValue !== nextValue) {
                            patchProp(prop, lastValue, nextValue, dom, isSVG, hasControlledValue, lastVNode);
                        }
                    }
                }
                if (lastPropsOrEmpty !== EMPTY_OBJ) {
                    for (var prop$1 in lastPropsOrEmpty) {
                        // do not add a hasOwnProperty check here, it affects performance
                        if (!nextPropsOrEmpty.hasOwnProperty(prop$1) && !isNullOrUndef(lastPropsOrEmpty[prop$1])) {
                            patchProp(prop$1, lastPropsOrEmpty[prop$1], null, dom, isSVG, hasControlledValue, lastVNode);
                        }
                    }
                }
            }
            var lastChildren = lastVNode.children;
            var nextChildren = nextVNode.children;
            var nextRef = nextVNode.ref;
            var lastClassName = lastVNode.className;
            var nextClassName = nextVNode.className;
            if (lastChildren !== nextChildren) {
                patchChildren(lastVNode.childFlags, nextVNode.childFlags, lastChildren, nextChildren, dom, context, isSVG && nextTag !== 'foreignObject');
            }
            if (isFormElement) {
                processElement(nextFlags, nextVNode, dom, nextPropsOrEmpty, false, hasControlledValue);
            }
            // inlined patchProps  -- ends --
            if (lastClassName !== nextClassName) {
                if (isNullOrUndef(nextClassName)) {
                    dom.removeAttribute('class');
                }
                else if (isSVG) {
                    dom.setAttribute('class', nextClassName);
                }
                else {
                    dom.className = nextClassName;
                }
            }
            if (isFunction(nextRef) && lastVNode.ref !== nextRef) {
                mountRef(dom, nextRef);
            }
        }
    }
    function patchChildren(lastChildFlags, nextChildFlags, lastChildren, nextChildren, parentDOM, context, isSVG) {
        switch (lastChildFlags) {
            case 2 /* HasVNodeChildren */:
                switch (nextChildFlags) {
                    case 2 /* HasVNodeChildren */:
                        patch(lastChildren, nextChildren, parentDOM, context, isSVG);
                        break;
                    case 1 /* HasInvalidChildren */:
                        remove(lastChildren, parentDOM);
                        break;
                    default:
                        remove(lastChildren, parentDOM);
                        mountArrayChildren(nextChildren, parentDOM, context, isSVG);
                        break;
                }
                break;
            case 1 /* HasInvalidChildren */:
                switch (nextChildFlags) {
                    case 2 /* HasVNodeChildren */:
                        mount(nextChildren, parentDOM, context, isSVG);
                        break;
                    case 1 /* HasInvalidChildren */:
                        break;
                    default:
                        mountArrayChildren(nextChildren, parentDOM, context, isSVG);
                        break;
                }
                break;
            default:
                if (nextChildFlags & 12 /* MultipleChildren */) {
                    var lastLength = lastChildren.length;
                    var nextLength = nextChildren.length;
                    // Fast path's for both algorithms
                    if (lastLength === 0) {
                        if (nextLength > 0) {
                            mountArrayChildren(nextChildren, parentDOM, context, isSVG);
                        }
                    }
                    else if (nextLength === 0) {
                        removeAllChildren(parentDOM, lastChildren);
                    }
                    else if (nextChildFlags === 8 /* HasKeyedChildren */ && lastChildFlags === 8 /* HasKeyedChildren */) {
                        patchKeyedChildren(lastChildren, nextChildren, parentDOM, context, isSVG, lastLength, nextLength);
                    }
                    else {
                        patchNonKeyedChildren(lastChildren, nextChildren, parentDOM, context, isSVG, lastLength, nextLength);
                    }
                }
                else if (nextChildFlags === 1 /* HasInvalidChildren */) {
                    removeAllChildren(parentDOM, lastChildren);
                }
                else {
                    removeAllChildren(parentDOM, lastChildren);
                    mount(nextChildren, parentDOM, context, isSVG);
                }
                break;
        }
    }
    function updateClassComponent(instance, nextState, nextVNode, nextProps, parentDom, context, isSVG, force, fromSetState) {
        var lastState = instance.state;
        var lastProps = instance.props;
        nextVNode.children = instance;
        var renderOutput;
        if (instance.$UN) {
            return;
        }
        if (lastProps !== nextProps || nextProps === EMPTY_OBJ) {
            if (!fromSetState && isFunction(instance.componentWillReceiveProps)) {
                instance.$BR = true;
                instance.componentWillReceiveProps(nextProps, context);
                // If instance component was removed during its own update do nothing...
                if (instance.$UN) {
                    return;
                }
                instance.$BR = false;
            }
            if (instance.$PSS) {
                nextState = combineFrom(nextState, instance.$PS);
                instance.$PSS = false;
                instance.$PS = null;
            }
        }
        /* Update if scu is not defined, or it returns truthy value or force */
        var hasSCU = Boolean(instance.shouldComponentUpdate);
        if (force || !hasSCU || (hasSCU && instance.shouldComponentUpdate(nextProps, nextState, context))) {
            if (isFunction(instance.componentWillUpdate)) {
                instance.$BS = true;
                instance.componentWillUpdate(nextProps, nextState, context);
                instance.$BS = false;
            }
            instance.props = nextProps;
            instance.state = nextState;
            instance.context = context;
            if (isFunction(options.beforeRender)) {
                options.beforeRender(instance);
            }
            renderOutput = instance.render(nextProps, nextState, context);
            if (isFunction(options.afterRender)) {
                options.afterRender(instance);
            }
            var didUpdate = renderOutput !== NO_OP;
            var childContext;
            if (isFunction(instance.getChildContext)) {
                childContext = instance.getChildContext();
            }
            if (isNullOrUndef(childContext)) {
                childContext = context;
            }
            else {
                childContext = combineFrom(context, childContext);
            }
            instance.$CX = childContext;
            if (didUpdate) {
                var lastInput = instance.$LI;
                var nextInput = (instance.$LI = handleComponentInput(renderOutput, nextVNode));
                patch(lastInput, nextInput, parentDom, childContext, isSVG);
                if (isFunction(instance.componentDidUpdate)) {
                    instance.componentDidUpdate(lastProps, lastState);
                }
            }
        }
        else {
            instance.props = nextProps;
            instance.state = nextState;
            instance.context = context;
        }
        nextVNode.dom = instance.$LI.dom;
    }
    function patchComponent(lastVNode, nextVNode, parentDom, context, isSVG, isClass) {
        var nextType = nextVNode.type;
        var lastKey = lastVNode.key;
        var nextKey = nextVNode.key;
        if (lastVNode.type !== nextType || lastKey !== nextKey) {
            replaceWithNewNode(lastVNode, nextVNode, parentDom, context, isSVG);
        }
        else {
            var nextProps = nextVNode.props || EMPTY_OBJ;
            if (isClass) {
                var instance = lastVNode.children;
                if (instance) {
                instance.$UPD = true;
                instance.$V = nextVNode;
                updateClassComponent(instance, instance.state, nextVNode, nextProps, parentDom, context, isSVG, false, false);
                instance.$UPD = false;
                }
            }
            else {
                var shouldUpdate = true;
                var lastProps = lastVNode.props;
                var nextHooks = nextVNode.ref;
                var nextHooksDefined = !isNullOrUndef(nextHooks);
                var lastInput = lastVNode.children;
                nextVNode.dom = lastVNode.dom;
                nextVNode.children = lastInput;
                if (nextHooksDefined && isFunction(nextHooks.onComponentShouldUpdate)) {
                    shouldUpdate = nextHooks.onComponentShouldUpdate(lastProps, nextProps);
                }
                if (shouldUpdate !== false) {
                    if (nextHooksDefined && isFunction(nextHooks.onComponentWillUpdate)) {
                        nextHooks.onComponentWillUpdate(lastProps, nextProps);
                    }
                    var nextInput = nextType(nextProps, context);
                    if (nextInput !== NO_OP) {
                        nextInput = handleComponentInput(nextInput, nextVNode);
                        patch(lastInput, nextInput, parentDom, context, isSVG);
                        nextVNode.children = nextInput;
                        nextVNode.dom = nextInput.dom;
                        if (nextHooksDefined && isFunction(nextHooks.onComponentDidUpdate)) {
                            nextHooks.onComponentDidUpdate(lastProps, nextProps);
                        }
                    }
                }
                else if (lastInput.flags & 14 /* Component */) {
                    lastInput.parentVNode = nextVNode;
                }
            }
        }
    }
    function patchText(lastVNode, nextVNode, parentDom) {
        var nextText = nextVNode.children;
        var textNode = parentDom.firstChild;
        var dom;
        // Guard against external change on DOM node.
        if (isNull(textNode)) {
            parentDom.textContent = nextText;
            dom = parentDom.firstChild;
        }
        else {
            dom = lastVNode.dom;
            if (nextText !== lastVNode.children) {
                dom.nodeValue = nextText;
            }
        }
        nextVNode.dom = dom;
    }
    function patchNonKeyedChildren(lastChildren, nextChildren, dom, context, isSVG, lastChildrenLength, nextChildrenLength) {
        var commonLength = lastChildrenLength > nextChildrenLength ? nextChildrenLength : lastChildrenLength;
        var i = 0;
        var nextChild;
        var lastChild;
        for (; i < commonLength; i++) {
            nextChild = nextChildren[i];
            lastChild = lastChildren[i];
            if (nextChild.dom) {
                nextChild = nextChildren[i] = directClone(nextChild);
            }
            patch(lastChild, nextChild, dom, context, isSVG);
            lastChildren[i] = nextChild;
        }
        if (lastChildrenLength < nextChildrenLength) {
            for (i = commonLength; i < nextChildrenLength; i++) {
                nextChild = nextChildren[i];
                if (nextChild.dom) {
                    nextChild = nextChildren[i] = directClone(nextChild);
                }
                mount(nextChild, dom, context, isSVG);
            }
        }
        else if (lastChildrenLength > nextChildrenLength) {
            for (i = commonLength; i < lastChildrenLength; i++) {
                remove(lastChildren[i], dom);
            }
        }
    }
    function patchKeyedChildren(a, b, dom, context, isSVG, aLength, bLength) {
        var aEnd = aLength - 1;
        var bEnd = bLength - 1;
        var i;
        var j = 0;
        var aNode = a[j];
        var bNode = b[j];
        var nextPos;
        // Step 1
        // tslint:disable-next-line
        outer: {
            // Sync nodes with the same key at the beginning.
            while (aNode.key === bNode.key) {
                if (bNode.dom) {
                    b[j] = bNode = directClone(bNode);
                }
                patch(aNode, bNode, dom, context, isSVG);
                a[j] = bNode;
                j++;
                if (j > aEnd || j > bEnd) {
                    break outer;
                }
                aNode = a[j];
                bNode = b[j];
            }
            aNode = a[aEnd];
            bNode = b[bEnd];
            // Sync nodes with the same key at the end.
            while (aNode.key === bNode.key) {
                if (bNode.dom) {
                    b[bEnd] = bNode = directClone(bNode);
                }
                patch(aNode, bNode, dom, context, isSVG);
                a[aEnd] = bNode;
                aEnd--;
                bEnd--;
                if (j > aEnd || j > bEnd) {
                    break outer;
                }
                aNode = a[aEnd];
                bNode = b[bEnd];
            }
        }
        if (j > aEnd) {
            if (j <= bEnd) {
                nextPos = bEnd + 1;
                var nextNode = nextPos < bLength ? b[nextPos].dom : null;
                while (j <= bEnd) {
                    bNode = b[j];
                    if (bNode.dom) {
                        b[j] = bNode = directClone(bNode);
                    }
                    j++;
                    insertOrAppend(dom, mount(bNode, null, context, isSVG), nextNode);
                }
            }
        }
        else if (j > bEnd) {
            while (j <= aEnd) {
                remove(a[j++], dom);
            }
        }
        else {
            var aStart = j;
            var bStart = j;
            var aLeft = aEnd - j + 1;
            var bLeft = bEnd - j + 1;
            var sources = [];
            for (i = 0; i < bLeft; i++) {
                sources.push(0);
            }
            // Keep track if its possible to remove whole DOM using textContent = '';
            var canRemoveWholeContent = aLeft === aLength;
            var moved = false;
            var pos = 0;
            var patched = 0;
            // When sizes are small, just loop them through
            if (bLength < 4 || (aLeft | bLeft) < 32) {
                for (i = aStart; i <= aEnd; i++) {
                    aNode = a[i];
                    if (patched < bLeft) {
                        for (j = bStart; j <= bEnd; j++) {
                            bNode = b[j];
                            if (aNode.key === bNode.key) {
                                sources[j - bStart] = i + 1;
                                if (canRemoveWholeContent) {
                                    canRemoveWholeContent = false;
                                    while (i > aStart) {
                                        remove(a[aStart++], dom);
                                    }
                                }
                                if (pos > j) {
                                    moved = true;
                                }
                                else {
                                    pos = j;
                                }
                                if (bNode.dom) {
                                    b[j] = bNode = directClone(bNode);
                                }
                                patch(aNode, bNode, dom, context, isSVG);
                                patched++;
                                break;
                            }
                        }
                        if (!canRemoveWholeContent && j > bEnd) {
                            remove(aNode, dom);
                        }
                    }
                    else if (!canRemoveWholeContent) {
                        remove(aNode, dom);
                    }
                }
            }
            else {
                var keyIndex = {};
                // Map keys by their index
                for (i = bStart; i <= bEnd; i++) {
                    keyIndex[b[i].key] = i;
                }
                // Try to patch same keys
                for (i = aStart; i <= aEnd; i++) {
                    aNode = a[i];
                    if (patched < bLeft) {
                        j = keyIndex[aNode.key];
                        if (j !== void 0) {
                            if (canRemoveWholeContent) {
                                canRemoveWholeContent = false;
                                while (i > aStart) {
                                    remove(a[aStart++], dom);
                                }
                            }
                            bNode = b[j];
                            sources[j - bStart] = i + 1;
                            if (pos > j) {
                                moved = true;
                            }
                            else {
                                pos = j;
                            }
                            if (bNode.dom) {
                                b[j] = bNode = directClone(bNode);
                            }
                            patch(aNode, bNode, dom, context, isSVG);
                            patched++;
                        }
                        else if (!canRemoveWholeContent) {
                            remove(aNode, dom);
                        }
                    }
                    else if (!canRemoveWholeContent) {
                        remove(aNode, dom);
                    }
                }
            }
            // fast-path: if nothing patched remove all old and add all new
            if (canRemoveWholeContent) {
                removeAllChildren(dom, a);
                mountArrayChildren(b, dom, context, isSVG);
            }
            else {
                if (moved) {
                    var seq = lis_algorithm(sources);
                    j = seq.length - 1;
                    for (i = bLeft - 1; i >= 0; i--) {
                        if (sources[i] === 0) {
                            pos = i + bStart;
                            bNode = b[pos];
                            if (bNode.dom) {
                                b[pos] = bNode = directClone(bNode);
                            }
                            nextPos = pos + 1;
                            insertOrAppend(dom, mount(bNode, null, context, isSVG), nextPos < bLength ? b[nextPos].dom : null);
                        }
                        else if (j < 0 || i !== seq[j]) {
                            pos = i + bStart;
                            bNode = b[pos];
                            nextPos = pos + 1;
                            insertOrAppend(dom, bNode.dom, nextPos < bLength ? b[nextPos].dom : null);
                        }
                        else {
                            j--;
                        }
                    }
                }
                else if (patched !== bLeft) {
                    // when patched count doesn't match b length we need to insert those new ones
                    // loop backwards so we can use insertBefore
                    for (i = bLeft - 1; i >= 0; i--) {
                        if (sources[i] === 0) {
                            pos = i + bStart;
                            bNode = b[pos];
                            if (bNode.dom) {
                                b[pos] = bNode = directClone(bNode);
                            }
                            nextPos = pos + 1;
                            insertOrAppend(dom, mount(bNode, null, context, isSVG), nextPos < bLength ? b[nextPos].dom : null);
                        }
                    }
                }
            }
        }
    }
    // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
    function lis_algorithm(arr) {
        var p = arr.slice();
        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 !== 0) {
                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;
    }

    var documentBody = isBrowser ? document.body : null;
    function render(input, parentDom, callback) {
        if (input === NO_OP) {
            return;
        }
        var rootInput = parentDom.$V;
        if (isNullOrUndef(rootInput)) {
            if (!isInvalid(input)) {
                if (input.dom) {
                    input = directClone(input);
                }
                if (isNull(parentDom.firstChild)) {
                    mount(input, parentDom, EMPTY_OBJ, false);
                    parentDom.$V = input;
                }
                else {
                    hydrate(input, parentDom);
                }
                rootInput = input;
            }
        }
        else {
            if (isNullOrUndef(input)) {
                remove(rootInput, parentDom);
                parentDom.$V = null;
            }
            else {
                if (input.dom) {
                    input = directClone(input);
                }
                patch(rootInput, input, parentDom, EMPTY_OBJ, false);
                rootInput = parentDom.$V = input;
            }
        }
        if (LIFECYCLE.length > 0) {
            callAll(LIFECYCLE);
        }
        if (isFunction(callback)) {
            callback();
        }
        if (isFunction(options.renderComplete)) {
            options.renderComplete(rootInput);
        }
        if (rootInput && rootInput.flags & 14 /* Component */) {
            return rootInput.children;
        }
    }

    var resolvedPromise = typeof Promise === 'undefined' ? null : Promise.resolve();
    // raf.bind(window) is needed to work around bug in IE10-IE11 strict mode (TypeError: Invalid calling object)
    var fallbackMethod = typeof requestAnimationFrame === 'undefined' ? setTimeout : requestAnimationFrame.bind(window);
    function nextTick(fn) {
        if (resolvedPromise) {
            return resolvedPromise.then(fn);
        }
        return fallbackMethod(fn);
    }
    function queueStateChanges(component, newState, callback, force) {
        if (isFunction(newState)) {
            newState = newState(component.state, component.props, component.context);
        }
        var pending = component.$PS;
        if (isNullOrUndef(pending)) {
            component.$PS = newState;
        }
        else {
            for (var stateKey in newState) {
                pending[stateKey] = newState[stateKey];
            }
        }
        if (!component.$PSS && !component.$BR) {
            if (!component.$UPD) {
                component.$PSS = true;
                component.$UPD = true;
                applyState(component, force, callback);
                component.$UPD = false;
            }
            else {
                // Async
                var queue = component.$QU;
                if (isNull(queue)) {
                    queue = component.$QU = [];
                    nextTick(promiseCallback(component, queue));
                }
                if (isFunction(callback)) {
                    queue.push(callback);
                }
            }
        }
        else {
            component.$PSS = true;
            if (component.$BR && isFunction(callback)) {
                LIFECYCLE.push(callback.bind(component));
            }
        }
    }
    function promiseCallback(component, queue) {
        return function () {
            component.$QU = null;
            component.$UPD = true;
            applyState(component, false, function () {
                for (var i = 0, len = queue.length; i < len; i++) {
                    queue[i].call(component);
                }
            });
            component.$UPD = false;
        };
    }
    function applyState(component, force, callback) {
        if (component.$UN) {
            return;
        }
        if (force || !component.$BR) {
            component.$PSS = false;
            var pendingState = component.$PS;
            var prevState = component.state;
            var nextState = combineFrom(prevState, pendingState);
            var props = component.props;
            var context = component.context;
            component.$PS = null;
            var vNode = component.$V;
            var lastInput = component.$LI;
            var parentDom = lastInput.dom && lastInput.dom.parentNode;
            updateClassComponent(component, nextState, vNode, props, parentDom, context, (vNode.flags & 32 /* SvgElement */) > 0, force, true);
            if (component.$UN) {
                return;
            }
            if ((component.$LI.flags & 1024 /* Portal */) === 0) {
                var dom = component.$LI.dom;
                while (!isNull((vNode = vNode.parentVNode))) {
                    if ((vNode.flags & 14 /* Component */) > 0) {
                        vNode.dom = dom;
                    }
                }
            }
            if (LIFECYCLE.length > 0) {
                callAll(LIFECYCLE);
            }
        }
        else {
            component.state = component.$PS;
            component.$PS = null;
        }
        if (isFunction(callback)) {
            callback.call(component);
        }
    }
    var Component = function Component(props, context) {
        this.state = null;
        // Internal properties
        this.$BR = false; // BLOCK RENDER
        this.$BS = true; // BLOCK STATE
        this.$PSS = false; // PENDING SET STATE
        this.$PS = null; // PENDING STATE (PARTIAL or FULL)
        this.$LI = null; // LAST INPUT
        this.$V = null; // VNODE
        this.$UN = false; // UNMOUNTED
        this.$CX = null; // CHILDCONTEXT
        this.$UPD = true; // UPDATING
        this.$QU = null; // QUEUE
        /** @type {object} */
        this.props = props || EMPTY_OBJ;
        /** @type {object} */
        this.context = context || EMPTY_OBJ; // context should not be mutable
    };
    Component.prototype.forceUpdate = function forceUpdate (callback) {
        if (this.$UN) {
            return;
        }
        // Do not allow double render during force update
        queueStateChanges(this, {}, callback, true);
    };
    Component.prototype.setState = function setState (newState, callback) {
        if (this.$UN) {
            return;
        }
        if (!this.$BS) {
            queueStateChanges(this, newState, callback, false);
        }
        else {
            return;
        }
    };
    // tslint:disable-next-line:no-empty
    Component.prototype.render = function render (nextProps, nextState, nextContext) { };



    var JSX = /*#__PURE__*/Object.freeze({

    });

    var ENTER = 12;
    var LEFT_ARROW = 37;
    var UP_ARROW = 38;
    var RIGHT_ARROW = 39;
    var DOWN_ARROW = 40;

    /**
     * Helper method to create an object for a new node.
     *
     * @private
     * @return {void}
     */
    function blankNode() {
        return {
            text: 'New Node',
            itree: {
                state: {
                    editing: true,
                    focused: true
                }
            }
        };
    }

    var classCallCheck = function (instance, Constructor) {
      if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    };

    var createClass = function () {
      function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
          var descriptor = props[i];
          descriptor.enumerable = descriptor.enumerable || false;
          descriptor.configurable = true;
          if ("value" in descriptor) descriptor.writable = true;
          Object.defineProperty(target, descriptor.key, descriptor);
        }
      }

      return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
      };
    }();

    var _extends = Object.assign || function (target) {
      for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];

        for (var key in source) {
          if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
          }
        }
      }

      return target;
    };

    var inherits = function (subClass, superClass) {
      if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
      }

      subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
          value: subClass,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
      if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    };

    var possibleConstructorReturn = function (self, call) {
      if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
      }

      return call && (typeof call === "object" || typeof call === "function") ? call : self;
    };

    var Checkbox = function (_Component) {
        inherits(Checkbox, _Component);

        function Checkbox() {
            classCallCheck(this, Checkbox);
            return possibleConstructorReturn(this, (Checkbox.__proto__ || Object.getPrototypeOf(Checkbox)).apply(this, arguments));
        }

        createClass(Checkbox, [{
            key: 'click',
            value: function click(event) {
                var _this2 = this;

                // Define our default handler
                var handler = function handler() {
                    _this2.props.node.toggleCheck();
                };

                // Emit an event with our forwarded MouseEvent, node, and default handler
                this.props.dom._tree.emit('node.click', event, this.props.node, handler);

                // Unless default is prevented, auto call our default handler
                if (!event.treeDefaultPrevented) {
                    handler();
                }
            }
        }, {
            key: 'render',
            value: function render$$1() {
                return createVNode(64, 'input', null, null, 1, {
                    'checked': this.props.checked,
                    'indeterminate': this.props.indeterminate,
                    'onClick': this.click.bind(this),
                    'type': 'checkbox'
                });
            }
        }]);
        return Checkbox;
    }(Component);

    /**
     * Utility method for parsing and merging custom class names.
     *
     * @private
     * @param {TreeNode} node Node object.
     * @param {string} type 'li' or 'a' attribute object type.
     * @return {Array} Processed class names
     */
    var classlist = (function (node) {
        var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'li';

        var nodeAttrs = node.itree[type].attributes;
        var classNames = [];

        // Append any custom class names
        var customClasses = nodeAttrs.class || nodeAttrs.className;

        // Support callbacks
        if (_.isFunction(customClasses)) {
            customClasses = customClasses(node);
        }

        // Convert custom classes into an array and concat
        if (!_.isEmpty(customClasses)) {
            if (_.isString(customClasses)) {
                // Support periods for backwards compat with hyperscript-formatted classes
                classNames = classNames.concat(customClasses.split(/[\s\.]+/));
            } else if (_.isArray(customClasses)) {
                classNames = classNames.concat(customClasses);
            }
        }

        return classNames;
    });

    var EditToolbar = function (_Component) {
        inherits(EditToolbar, _Component);

        function EditToolbar() {
            classCallCheck(this, EditToolbar);
            return possibleConstructorReturn(this, (EditToolbar.__proto__ || Object.getPrototypeOf(EditToolbar)).apply(this, arguments));
        }

        createClass(EditToolbar, [{
            key: 'shouldComponentUpdate',
            value: function shouldComponentUpdate() {
                return false;
            }
        }, {
            key: 'add',
            value: function add(event) {
                event.stopPropagation();

                this.props.node.addChild(blankNode());
                this.props.node.expand();
            }
        }, {
            key: 'edit',
            value: function edit(event) {
                event.stopPropagation();

                this.props.node.toggleEditing();
            }
        }, {
            key: 'remove',
            value: function remove(event) {
                event.stopPropagation();

                this.props.node.remove();
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var buttons = [];

                if (this.props.dom._tree.config.editing.edit) {
                    buttons.push(createVNode(1, 'a', 'btn icon icon-pencil', null, 1, {
                        'onclick': this.edit.bind(this),
                        'title': 'Edit this node'
                    }));
                }

                if (this.props.dom._tree.config.editing.add) {
                    buttons.push(createVNode(1, 'a', 'btn icon icon-plus', null, 1, {
                        'onclick': this.add.bind(this),
                        'title': 'Add a child node'
                    }));
                }

                if (this.props.dom._tree.config.editing.remove) {
                    buttons.push(createVNode(1, 'a', 'btn icon icon-minus', null, 1, {
                        'onclick': this.remove.bind(this),
                        'title': 'Remove this node'
                    }));
                }

                return createVNode(1, 'span', 'btn-group', buttons, 0);
            }
        }]);
        return EditToolbar;
    }(Component);

    var EmptyList = function (_Component) {
        inherits(EmptyList, _Component);

        function EmptyList() {
            classCallCheck(this, EmptyList);
            return possibleConstructorReturn(this, (EmptyList.__proto__ || Object.getPrototypeOf(EmptyList)).apply(this, arguments));
        }

        createClass(EmptyList, [{
            key: 'render',
            value: function render$$1() {
                return createVNode(1, 'ol', null, createVNode(1, 'li', 'leaf', createVNode(1, 'span', 'title icon icon-file-empty empty', this.props.text, 0), 2), 2);
            }
        }]);
        return EmptyList;
    }(Component);

    /**
     * Compares all keys on the given state. Returns true if any difference exists.
     *
     * @private
     * @category DOM
     * @param {object} previousState Previous state.
     * @param {object} currentState  Current state.
     * @return {boolean} Difference was found.
     */
    function stateComparator(previousState, currentState) {
        // Always treat dirty flag as a state difference
        var isDirty = currentState.dirty || false;

        if (!isDirty) {
            _.each(Object.keys(currentState), function (key) {
                if (key !== 'dirty' && currentState[key] !== previousState[key]) {
                    isDirty = true;
                    return false;
                }
            });
        }

        return isDirty;
    }

    var EditForm = function (_Component) {
        inherits(EditForm, _Component);

        function EditForm(props) {
            classCallCheck(this, EditForm);

            var _this = possibleConstructorReturn(this, (EditForm.__proto__ || Object.getPrototypeOf(EditForm)).call(this, props));

            _this.state = _this.getStateFromNodes(props.node);
            return _this;
        }

        createClass(EditForm, [{
            key: 'getStateFromNodes',
            value: function getStateFromNodes(node) {
                return {
                    text: node.text
                };
            }
        }, {
            key: 'componentWillReceiveProps',
            value: function componentWillReceiveProps(data) {
                this.setState(this.getStateFromNodes(data.node));
            }
        }, {
            key: 'shouldComponentUpdate',
            value: function shouldComponentUpdate(nextProps, nextState) {
                return stateComparator(this.state, nextState);
            }
        }, {
            key: 'click',
            value: function click(event) {
                var _this2 = this;

                // Define our default handler
                var handler = function handler() {
                    _this2.props.node.toggleCheck();
                };

                // Emit an event with our forwarded MouseEvent, node, and default handler
                this.props.dom._tree.emit('node.click', event, this.props.node, handler);

                // Unless default is prevented, auto call our default handler
                if (!event.treeDefaultPrevented) {
                    handler();
                }
            }
        }, {
            key: 'keypress',
            value: function keypress(event) {
                if (event.which === ENTER) {
                    return this.save();
                }
            }
        }, {
            key: 'input',
            value: function input(event) {
                this.setState({
                    text: event.target.value
                });
            }
        }, {
            key: 'cancel',
            value: function cancel(event) {
                if (event) {
                    event.stopPropagation();
                }

                this.props.node.toggleEditing();
            }
        }, {
            key: 'save',
            value: function save(event) {
                if (event) {
                    event.stopPropagation();
                }

                // Cache current text
                var originalText = this.props.node.text;
                var newText = this.ref.value;

                // Update the text
                this.props.node.set('text', newText);

                // Disable editing and update
                this.props.node.state('editing', false);
                this.props.node.markDirty();
                this.props.dom._tree.applyChanges();

                if (originalText !== newText) {
                    this.props.dom._tree.emit('node.edited', this.props.node, originalText, newText);
                }
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var _this3 = this;

                return createVNode(1, 'form', null, [createVNode(64, 'input', null, null, 1, {
                    'onClick': function onClick(event) {
                        return event.stopPropagation;
                    },
                    'onInput': this.input.bind(this),
                    'onKeyPress': this.keypress.bind(this),
                    'value': this.state.text
                }, null, function (elem) {
                    return _this3.ref = elem;
                }), createVNode(1, 'span', 'btn-group', [createVNode(1, 'button', 'btn icon icon-check', null, 1, {
                    'onClick': this.save.bind(this),
                    'title': 'Save',
                    'type': 'button'
                }), createVNode(1, 'button', 'btn icon icon-cross', null, 1, {
                    'onClick': this.cancel.bind(this),
                    'title': 'Cancel',
                    'type': 'button'
                })], 4)], 4, {
                    'onsubmit': function onsubmit(event) {
                        return event.preventDefault;
                    }
                });
            }
        }]);
        return EditForm;
    }(Component);

    var NodeAnchor = function (_Component) {
        inherits(NodeAnchor, _Component);

        function NodeAnchor() {
            classCallCheck(this, NodeAnchor);
            return possibleConstructorReturn(this, (NodeAnchor.__proto__ || Object.getPrototypeOf(NodeAnchor)).apply(this, arguments));
        }

        createClass(NodeAnchor, [{
            key: 'blur',
            value: function blur() {
                this.props.node.blur();
            }
        }, {
            key: 'click',
            value: function click(event) {
                var _this2 = this;

                var _props = this.props,
                    node = _props.node,
                    dom = _props.dom;

                // Define our default handler

                var handler = function handler() {
                    event.preventDefault();

                    if (_this2.props.editing) {
                        return;
                    }

                    if (event.metaKey || event.ctrlKey || event.shiftKey) {
                        dom._tree.disableDeselection();
                    }

                    if (event.shiftKey) {
                        dom.clearSelection();

                        var selected = dom._tree.lastSelectedNode();
                        if (selected) {
                            dom._tree.selectBetween.apply(dom._tree, dom._tree.boundingNodes(selected, node));
                        }
                    }

                    if (node.selected()) {
                        if (!dom._tree.config.selection.disableDirectDeselection) {
                            node.deselect();
                        }
                    } else {
                        node.select();
                    }

                    dom._tree.enableDeselection();
                };

                // Emit an event with our forwarded MouseEvent, node, and default handler
                dom._tree.emit('node.click', event, node, handler);

                // Unless default is prevented, auto call our default handler
                if (!event.treeDefaultPrevented) {
                    handler();
                }
            }
        }, {
            key: 'contextMenu',
            value: function contextMenu(event) {
                var _props2 = this.props,
                    node = _props2.node,
                    dom = _props2.dom;


                dom._tree.emit('node.contextmenu', event, node);
            }
        }, {
            key: 'dblclick',
            value: function dblclick(event) {
                var _props3 = this.props,
                    node = _props3.node,
                    dom = _props3.dom;

                // Define our default handler

                var handler = function handler() {
                    // Clear text selection which occurs on double click
                    dom.clearSelection();

                    node.toggleCollapse();
                };

                // Emit an event with our forwarded MouseEvent, node, and default handler
                dom._tree.emit('node.dblclick', event, node, handler);

                // Unless default is prevented, auto call our default handler
                if (!event.treeDefaultPrevented) {
                    handler();
                }
            }
        }, {
            key: 'focus',
            value: function focus(event) {
                this.props.node.focus(event);
            }
        }, {
            key: 'mousedown',
            value: function mousedown() {
                if (this.props.dom.isDragDropEnabled) {
                    this.props.dom.isMouseHeld = true;
                }
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var node = this.props.node;
                var attributes = _.clone(node.itree.a.attributes) || {};
                attributes.tabindex = 1;
                attributes.unselectable = 'on';

                // Build and set classnames
                var classNames = classlist(node, 'a').concat(['title', 'icon']);

                if (!this.props.dom.config.showCheckboxes) {
                    var folder = this.props.expanded ? 'icon-folder-open' : 'icon-folder';
                    classNames.push(node.itree.icon || (this.props.hasOrWillHaveChildren ? folder : 'icon-file-empty'));
                }

                attributes.class = attributes.className = classNames.join(' ');

                var content = node.text;
                if (node.editing()) {
                    content = createComponentVNode(2, EditForm, {
                        'dom': this.props.dom,
                        'node': this.props.node
                    });
                }

                return normalizeProps(createVNode(1, 'a', null, content, 0, _extends({
                    'data-uid': node.id,
                    'onBlur': this.blur.bind(this),
                    'onClick': this.click.bind(this),
                    'onContextMenu': this.contextMenu.bind(this),
                    'onDblClick': this.dblclick.bind(this),
                    'onFocus': this.focus.bind(this),
                    'onMouseDown': this.mousedown.bind(this)
                }, attributes)));
            }
        }]);
        return NodeAnchor;
    }(Component);

    var ToggleAnchor = function (_Component) {
        inherits(ToggleAnchor, _Component);

        function ToggleAnchor() {
            classCallCheck(this, ToggleAnchor);
            return possibleConstructorReturn(this, (ToggleAnchor.__proto__ || Object.getPrototypeOf(ToggleAnchor)).apply(this, arguments));
        }

        createClass(ToggleAnchor, [{
            key: 'className',
            value: function className() {
                return 'toggle icon ' + (this.props.collapsed ? 'icon-expand' : 'icon-collapse');
            }
        }, {
            key: 'render',
            value: function render$$1() {
                return createVNode(1, 'a', this.className(), null, 1, {
                    'onClick': this.props.node.toggleCollapse.bind(this.props.node)
                });
            }
        }]);
        return ToggleAnchor;
    }(Component);

    var ListItem = function (_Component) {
        inherits(ListItem, _Component);

        function ListItem(props) {
            classCallCheck(this, ListItem);

            var _this = possibleConstructorReturn(this, (ListItem.__proto__ || Object.getPrototypeOf(ListItem)).call(this, props));

            _this.state = _this.stateFromNode(props.node);
            return _this;
        }

        createClass(ListItem, [{
            key: 'stateFromNode',
            value: function stateFromNode(node) {
                return {
                    dirty: node.itree.dirty
                };
            }
        }, {
            key: 'componentWillReceiveProps',
            value: function componentWillReceiveProps(data) {
                this.setState(this.stateFromNode(data.node));
            }
        }, {
            key: 'shouldComponentUpdate',
            value: function shouldComponentUpdate(nextProps, nextState) {
                return nextState.dirty;
            }
        }, {
            key: 'getAttributes',
            value: function getAttributes() {
                var node = this.props.node;
                var attributes = _.clone(node.itree.li.attributes) || {};
                attributes.class = attributes.className = this.getClassNames();

                // Force internal-use attributes
                attributes['data-uid'] = node.id;

                // Allow drag and drop?
                if (this.props.dom.config.dragAndDrop.enabled) {
                    attributes.draggable = node.state('draggable');
                    attributes.onDragEnd = this.onDragEnd.bind(this);
                    attributes.onDragEnter = this.onDragEnter.bind(this);
                    attributes.onDragLeave = this.onDragLeave.bind(this);
                    attributes.onDragStart = this.onDragStart.bind(this);

                    // Are we a valid drop target?
                    if (node.state('drop-target')) {
                        attributes.onDragOver = this.onDragOver.bind(this);
                        attributes.onDrop = this.onDrop.bind(this);
                    } else {
                        // Setting to null forces removal of prior listeners
                        attributes.onDragOver = null;
                        attributes.onDrop = null;
                    }
                }

                return attributes;
            }
        }, {
            key: 'getClassNames',
            value: function getClassNames() {
                var node = this.props.node;
                var state = node.itree.state;

                // Set state classnames
                var classNames = classlist(node);

                // https://jsperf.com/object-keys-vs-each
                _.each(Object.keys(state), function (key) {
                    if (state[key]) {
                        classNames.push(key);
                    }
                });

                // Inverse and additional classes
                if (!node.hidden() && node.removed()) {
                    classNames.push('hidden');
                }

                if (node.expanded()) {
                    classNames.push('expanded');
                }

                classNames.push(node.hasOrWillHaveChildren() ? 'folder' : 'leaf');

                return classNames.join(' ');
            }
        }, {
            key: 'getTargetDirection',
            value: function getTargetDirection(event, elem) {
                var clientY = event.clientY;
                var targetRect = elem.getBoundingClientRect();

                var yThresholdForAbove = targetRect.top + targetRect.height / 3;
                var yThresholdForBelow = targetRect.bottom - targetRect.height / 3;

                var dir = 0;

                if (clientY <= yThresholdForAbove) {
                    dir = -1;
                } else if (clientY >= yThresholdForBelow) {
                    dir = 1;
                }

                return dir;
            }
        }, {
            key: 'onDragStart',
            value: function onDragStart(event) {
                event.stopPropagation();

                event.dataTransfer.effectAllowed = 'move';
                event.dataTransfer.dropEffect = 'move';

                var node = this.props.node;

                // Due to "protected" mode we can't access any DataTransfer data
                // during the dragover event, yet we still need to validate this node with the target
                this.props.dom._activeDragNode = node;

                event.dataTransfer.setData('treeId', node.tree().id);
                event.dataTransfer.setData('nodeId', node.id);

                // Disable self and children as drop targets
                node.state('drop-target', false);

                if (node.hasChildren()) {
                    node.children.stateDeep('drop-target', false);
                }

                // If we should validate all nodes as potential drop targets on drag start
                if (this.props.dom.config.dragAndDrop.validateOn === 'dragstart') {
                    var validator = this.props.dom.config.dragAndDrop.validate;
                    var validateCallable = _.isFunction(validator);

                    // Validate with a custom recursor because a return of "false"
                    // should mean "do not descend" rather than "stop iterating"
                    var recursor = function recursor(obj, iteratee) {
                        if (InspireTree.isTreeNodes(obj)) {
                            _.each(obj, function (n) {
                                recursor(n, iteratee);
                            });
                        } else if (InspireTree.isTreeNode(obj)) {
                            if (iteratee(obj) !== false && obj.hasChildren()) {
                                recursor(obj.children, iteratee);
                            }
                        }
                    };

                    this.props.dom._tree.batch();

                    recursor(this.props.dom._tree.model, function (n) {
                        var valid = n.id !== node.id;

                        // Ensure target node isn't a descendant
                        if (valid) {
                            valid = !n.hasAncestor(node);
                        }

                        // If still valid and user has additional validation...
                        if (valid && validateCallable) {
                            valid = validator(node, n);
                        }

                        // Set state
                        n.state('drop-target', valid);

                        return valid;
                    });

                    this.props.dom._tree.end();
                }

                this.props.dom._tree.emit('node.dragstart', event);
            }
        }, {
            key: 'onDragEnd',
            value: function onDragEnd(event) {
                event.preventDefault();
                event.stopPropagation();

                this.unhighlightTarget();

                this.props.dom._tree.emit('node.dragend', event);
            }
        }, {
            key: 'onDragEnter',
            value: function onDragEnter(event) {
                event.preventDefault();
                event.stopPropagation();

                // Nodes already within parents don't trigger enter/leave events on their ancestors
                this.props.node.recurseUp(this.unhighlightTarget);

                // Set drag target state
                this.props.node.state('drag-targeting', true);

                this.props.dom._tree.emit('node.dragenter', event);
            }
        }, {
            key: 'onDragLeave',
            value: function onDragLeave(event) {
                event.preventDefault();
                event.stopPropagation();

                this.unhighlightTarget();

                this.props.dom._tree.emit('node.dragleave', event);
            }
        }, {
            key: 'onDragOver',
            value: function onDragOver(event) {
                event.preventDefault();
                event.stopPropagation();

                var dragNode = this.props.dom._activeDragNode;
                var node = this.props.node;

                // Event.target doesn't always match the element we need to calculate
                var dir = this.getTargetDirection(event, node.itree.ref.querySelector('a'));

                if (this.props.dom.config.dragAndDrop.validateOn === 'dragover') {
                    // Validate drop target
                    var validator = this.props.dom.config.dragAndDrop.validate;
                    var validateCallable = _.isFunction(validator);

                    var valid = dragNode.id !== node.id;

                    if (valid) {
                        valid = !node.hasAncestor(dragNode);
                    }

                    if (valid && validateCallable) {
                        valid = validator(dragNode, node, dir);
                    }

                    // Set state
                    node.state('drop-target', valid);
                    this.props.dom._tree.applyChanges();

                    if (!valid) {
                        return;
                    }
                }

                // Set drag target states
                this.props.dom._tree.batch();
                node.state('drag-targeting', true);
                node.state('drag-targeting-above', dir === -1);
                node.state('drag-targeting-below', dir === 1);
                node.state('drag-targeting-insert', dir === 0);
                this.props.dom._tree.end();

                this.props.dom._tree.emit('node.dragover', event, dir);
            }
        }, {
            key: 'onDrop',
            value: function onDrop(event) {
                event.preventDefault();
                event.stopPropagation();

                // Always unhighlight target
                this.unhighlightTarget();

                // Get the data from our transfer
                var treeId = event.dataTransfer.getData('treeId');
                var nodeId = event.dataTransfer.getData('nodeId');

                // Find the drop target
                var targetNode = this.props.node;

                // Clear cache
                this.props.dom._activeDragNode = null;

                // Determine the insert direction (calc before removing source node, which modifies the DOM)
                var dir = this.getTargetDirection(event, event.target);

                var sourceTree = void 0;
                if (treeId === this.props.dom._tree.id) {
                    sourceTree = this.props.dom._tree;
                } else if (treeId) {
                    sourceTree = document.querySelector('[data-uid="' + treeId + '"]').inspireTree;
                }

                // Only source/handle node if it's a node that was dropped
                var newNode = void 0,
                    newIndex = void 0;
                if (sourceTree) {
                    var node = sourceTree.node(nodeId);
                    node.state('drop-target', true);

                    var exported = node.remove(true);

                    // Get the index of the target node
                    var targetIndex = targetNode.context().indexOf(targetNode);

                    if (dir === 0) {
                        // Add as a child
                        newNode = targetNode.addChild(exported);

                        // Cache the new index
                        newIndex = targetNode.children.indexOf(newNode);

                        // Auto-expand
                        targetNode.expand();
                    } else {
                        // Determine the new index
                        newIndex = dir === 1 ? ++targetIndex : targetIndex;

                        // Insert and cache the node
                        newNode = targetNode.context().insertAt(newIndex, exported);
                    }
                }

                this.props.dom._tree.emit('node.drop', event, newNode, targetNode, newIndex);
            }
        }, {
            key: 'unhighlightTarget',
            value: function unhighlightTarget(node) {
                (node || this.props.node).states(['drag-targeting', 'drag-targeting-above', 'drag-targeting-below', 'drag-targeting-insert'], false);
            }
        }, {
            key: 'renderCheckbox',
            value: function renderCheckbox() {
                var node = this.props.node;

                if (this.props.dom.config.showCheckboxes) {
                    return createComponentVNode(2, Checkbox, {
                        'checked': node.checked(),
                        'dom': this.props.dom,
                        'indeterminate': node.indeterminate(),
                        'node': node
                    });
                }
            }
        }, {
            key: 'renderChildren',
            value: function renderChildren() {
                var _props = this.props,
                    node = _props.node,
                    dom = _props.dom;


                if (node.hasChildren()) {
                    var nodes = node.children;
                    var loading = dom.loading;
                    var pagination = nodes.pagination();

                    return createComponentVNode(2, List, {
                        'context': node,
                        'dom': dom,
                        'limit': pagination.limit,
                        'loading': loading,
                        'nodes': nodes,
                        'total': pagination.total
                    });
                } else if (this.props.dom.isDynamic && node.children) {
                    if (!node.hasLoadedChildren()) {
                        return createComponentVNode(2, EmptyList, {
                            'text': 'Loading...'
                        });
                    } else {
                        return createComponentVNode(2, EmptyList, {
                            'text': 'No Results'
                        });
                    }
                }
            }
        }, {
            key: 'renderEditToolbar',
            value: function renderEditToolbar() {
                // @todo fix this boolean
                if (this.props.dom._tree.config.editing.edit && !this.props.node.editing()) {
                    return createComponentVNode(2, EditToolbar, {
                        'dom': this.props.dom,
                        'node': this.props.node
                    });
                }
            }
        }, {
            key: 'renderToggle',
            value: function renderToggle() {
                var node = this.props.node;
                var hasVisibleChildren = !this.props.dom.isDynamic ? node.hasVisibleChildren() : Boolean(node.children);

                if (hasVisibleChildren) {
                    return createComponentVNode(2, ToggleAnchor, {
                        'collapsed': node.collapsed(),
                        'node': node
                    });
                }
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var _this2 = this;

                var node = this.props.node;

                var li = normalizeProps(createVNode(1, 'li', null, [this.renderEditToolbar(), createVNode(1, 'div', 'title-wrap', [this.renderToggle(), this.renderCheckbox(), createComponentVNode(2, NodeAnchor, {
                    'dom': this.props.dom,
                    'editing': node.editing(),
                    'expanded': node.expanded(),
                    'hasOrWillHaveChildren': node.hasOrWillHaveChildren(),
                    'node': node,
                    'text': node.text
                })], 0), createVNode(1, 'div', 'wholerow'), this.renderChildren()], 0, _extends({}, this.getAttributes()), null, function (elem) {
                    return _this2.node = _this2.props.node.itree.ref = elem;
                }));

                // Clear dirty bool only after everything has been generated (and states set)
                this.props.node.state('rendered', true);
                this.props.node.itree.dirty = false;

                return li;
            }
        }]);
        return ListItem;
    }(Component);

    var List = function (_Component) {
        inherits(List, _Component);

        function List() {
            classCallCheck(this, List);
            return possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).apply(this, arguments));
        }

        createClass(List, [{
            key: 'shouldComponentUpdate',
            value: function shouldComponentUpdate(nextProps) {
                return _.find(nextProps.nodes, 'itree.dirty') || stateComparator(this.props, nextProps);
            }
        }, {
            key: 'isDeferred',
            value: function isDeferred() {
                return this.props.dom.config.deferredRendering || this.props.dom._tree.config.deferredLoading;
            }
        }, {
            key: 'loadMore',
            value: function loadMore(event) {
                event.preventDefault();
                if (this.props.context) {
                    this.props.context.loadMore(event);
                } else {
                    this.props.dom._tree.loadMore(event);
                }
            }
        }, {
            key: 'renderLoadMoreNode',
            value: function renderLoadMoreNode() {
                return createVNode(1, 'li', 'leaf detached', createVNode(1, 'a', 'title icon icon-more load-more', createTextVNode('Load More'), 2, {
                    'onClick': this.loadMore.bind(this)
                }), 2);
            }
        }, {
            key: 'renderLoadingTextNode',
            value: function renderLoadingTextNode() {
                return createVNode(1, 'li', 'leaf', createVNode(1, 'span', 'title icon icon-more', createTextVNode('Loading...'), 2), 2);
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var _this2 = this;

                var renderNodes = this.props.nodes;
                var pagination = renderNodes.pagination();

                // If rendering deferred, chunk the nodes client-side
                if (this.props.dom.config.deferredRendering) {
                    // Filter non-hidden/removed nodes and limit by this context's pagination
                    var count = 0;
                    renderNodes = this.props.nodes.filter(function (n) {
                        var matches = !(n.hidden() || n.removed());

                        if (matches) {
                            count++;
                        }

                        return count <= pagination.limit && matches;
                    });
                }

                // Render nodes as list items
                var items = _.map(renderNodes, function (node) {
                    return createComponentVNode(2, ListItem, {
                        'dom': _this2.props.dom,
                        'node': node
                    }, node.id);
                });

                if (this.isDeferred() && pagination.limit < pagination.total) {
                    if (!this.props.loading) {
                        items.push(this.renderLoadMoreNode());
                    } else {
                        items.push(this.renderLoadingTextNode());
                    }
                }

                return createVNode(1, 'ol', null, [items, this.props.children], 0);
            }
        }]);
        return List;
    }(Component);

    var Tree = function (_Component) {
        inherits(Tree, _Component);

        function Tree() {
            classCallCheck(this, Tree);
            return possibleConstructorReturn(this, (Tree.__proto__ || Object.getPrototypeOf(Tree)).apply(this, arguments));
        }

        createClass(Tree, [{
            key: 'add',
            value: function add() {
                this.props.dom._tree.focused().blur();

                this.props.dom._tree.addNode(blankNode());
            }
        }, {
            key: 'renderAddLink',
            value: function renderAddLink() {
                if (this.props.dom._tree.config.editing.add) {
                    return createVNode(1, 'li', null, createVNode(1, 'a', 'btn icon icon-plus', null, 1, {
                        'onClick': this.add.bind(this),
                        'title': 'Add a new root node'
                    }), 2);
                }
            }
        }, {
            key: 'render',
            value: function render$$1() {
                var _props = this.props,
                    dom = _props.dom,
                    nodes = _props.nodes;

                var loading = dom.loading;
                var pagination = nodes.pagination();

                return createComponentVNode(2, List, {
                    'dom': dom,
                    'limit': pagination.limit,
                    'loading': loading,
                    'nodes': nodes,
                    'total': pagination.total,
                    children: this.renderAddLink()
                });
            }
        }]);
        return Tree;
    }(Component);

    /**
     * Default InspireTree rendering logic.
     *
     * @category DOM
     * @return {InspireDOM} Default renderer.
     */

    var InspireDOM = function () {
        function InspireDOM(tree, opts) {
            var _this = this;

            classCallCheck(this, InspireDOM);

            if (!(tree instanceof InspireTree)) {
                throw new TypeError('Tree argument is not an InspireTree instance.');
            }

            // Init properties
            this._tree = tree;
            this.batching = 0;
            this.dropTargets = [];
            this.$scrollLayer;

            if (!opts.target) {
                throw new TypeError('Invalid `target` property - must be a selector, HTMLElement, or jQuery element.');
            }

            // Let InspireTree know we're in control of a node's `rendered` state
            tree.usesNativeDOM = true;

            var dndDefaults = {
                enabled: false,
                validateOn: 'dragstart',
                validate: null
            };

            // Assign defaults
            this.config = _.defaultsDeep({}, opts, {
                autoLoadMore: true,
                deferredRendering: false,
                dragAndDrop: dndDefaults,
                nodeHeight: 25,
                showCheckboxes: false,
                tabindex: -1,
                target: false
            });

            if (opts.dragAndDrop === true) {
                this.config.dragAndDrop = dndDefaults;
                this.config.dragAndDrop.enabled = true;
            }

            // If user didn't specify showCheckboxes,
            // but is using checkbox selection mode,
            // enable it automatically.
            if (tree.config.selection.mode === 'checkbox' && !_.isBoolean(_.get(opts, 'showCheckboxes'))) {
                this.config.showCheckboxes = true;
            }

            // Cache because we use in loops
            this.isDynamic = _.isFunction(this._tree.config.data);

            // Connect to our target DOM element
            this.attach(this.config.target);

            var initialRender = true;

            // Apply changes
            tree.on('changes.applied', function () {
                _this.renderNodes();

                if (initialRender) {
                    _this.scrollSelectedIntoView();

                    initialRender = false;
                }
            });

            // Immediately render, just in case any already exists
            this.renderNodes();
        }

        /**
         * Attaches to the DOM element for rendering.
         *
         * @category DOM
         * @private
         * @param {HTMLElement} target Element, selector, or jQuery-like object.
         * @return {void}
         */


        createClass(InspireDOM, [{
            key: 'attach',
            value: function attach(target) {
                this.$target = this.getElement(target);
                this.$scrollLayer = this.getScrollableAncestor(this.$target);

                if (!this.$target) {
                    throw new Error('No valid element to attach to.');
                }

                this.$target.setAttribute('data-uid', this._tree.id);

                // Set classnames
                var classNames = this.$target.className.split(' ');
                classNames.push('inspire-tree');

                if (this._tree.config.editable) {
                    classNames.push('editable');

                    _.each(_.pickBy(this._tree.config.editing, _.identity), function (v, key) {
                        classNames.push('editable-' + key);
                    });
                }

                this.$target.className = classNames.join(' ');
                this.$target.setAttribute('tabindex', this.config.tabindex || 0);

                // Handle keyboard interaction
                this.$target.addEventListener('keydown', this.keyboardListener.bind(this));

                // Drag and drop listeners
                if (this.config.dragAndDrop.enabled) {
                    this.$target.addEventListener('dragenter', this.onDragEnter.bind(this), false);
                    this.$target.addEventListener('dragleave', this.onDragLeave.bind(this), false);
                    this.$target.addEventListener('dragover', this.onDragOver.bind(this), false);
                    this.$target.addEventListener('drop', this.onDrop.bind(this), false);

                    this.$target.classList.add('drag-and-drop');
                }

                // Sync browser focus to focus state
                this._tree.on('node.focused', function (node) {
                    var elem = node.itree.ref.querySelector('.title');
                    if (elem !== document.activeElement) {
                        elem.focus();
                    }
                });

                if (this.config.deferredRendering || this._tree.config.deferredLoading) {
                    // Force valid pagination limit based on viewport
                    var limit = this._tree.config.pagination.limit;
                    this._tree.config.pagination.limit = limit > 0 ? limit : _.ceil(this.$scrollLayer.clientHeight / this.config.nodeHeight);

                    // Listen for scrolls for automatic loading
                    if (this.config.autoLoadMore) {
                        this.$target.addEventListener('scroll', _.throttle(this.scrollListener.bind(this), 20));
                    }
                }

                this.$target.inspireTree = this._tree;
            }

            /**
             * Clear page text selection, primarily after a click event which
             * natively selects a range of text.
             *
             * @category DOM
             * @private
             * @return {void}
             */

        }, {
            key: 'clearSelection',
            value: function clearSelection() {
                if (document.selection && document.selection.empty) {
                    document.selection.empty();
                } else if (window.getSelection) {
                    window.getSelection().removeAllRanges();
                }
            }

            /**
             * Get an HTMLElement through various means:
             * An element, jquery object, or a selector.
             *
             * @category DOM
             * @private
             * @param {mixed} target Element, jQuery selector, selector.
             * @return {HTMLElement} Matching element.
             */

        }, {
            key: 'getElement',
            value: function getElement(target) {
                var $element = void 0;

                if (target instanceof HTMLElement) {
                    $element = target;
                } else if (_.isObject(target) && _.isObject(target[0])) {
                    $element = target[0];
                } else if (_.isString(target)) {
                    var match = document.querySelector(target);
                    if (match) {
                        $element = match;
                    }
                }

                return $element;
            }

            /**
             * Helper method to find a scrollable ancestor element.
             *
             * @category DOM
             * @private
             * @param {HTMLElement} $element Starting element.
             * @return {HTMLElement} Scrollable element.
             */

        }, {
            key: 'getScrollableAncestor',
            value: function getScrollableAncestor($element) {
                if ($element instanceof Element) {
                    var style = getComputedStyle($element);
                    if (style.overflow !== 'auto' && $element.parentNode) {
                        $element = this.getScrollableAncestor($element.parentNode);
                    }
                }

                return $element;
            }

            /**
             * Get a tree instance based on an ID.
             *
             * @category DOM
             * @param {string} id Tree ID.
             * @return {InspireTree} Tree instance.
             */

        }, {
            key: 'keyboardListener',


            /**
             * Listen to keyboard event for navigation.
             *
             * @category DOM
             * @private
             * @param {Event} event Keyboard event.
             * @return {void}
             */
            value: function keyboardListener(event) {
                event.stopPropagation();

                // Ignore keys we won't care for.
                // For example, this avoids trampling cmd+reload
                if ([DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, UP_ARROW].indexOf(event.which) < 0) {
                    return;
                }

                // Navigation
                var focusedNodes = this._tree.focused();
                if (focusedNodes.length) {
                    event.preventDefault();

                    switch (event.which) {
                        case DOWN_ARROW:
                            this.moveFocusDownFrom(focusedNodes[0]);
                            break;
                        case ENTER:
                            focusedNodes[0].toggleSelect();
                            break;
                        case LEFT_ARROW:
                            focusedNodes[0].collapse();
                            break;
                        case RIGHT_ARROW:
                            focusedNodes[0].expand();
                            break;
                        case UP_ARROW:
                            this.moveFocusUpFrom(focusedNodes[0]);
                            break;
                        default:
                    }
                }
            }

            /**
             * Move select down the visible tree from a starting node.
             *
             * @category DOM
             * @private
             * @param {object} startingNode Node object.
             * @return {void}
             */

        }, {
            key: 'moveFocusDownFrom',
            value: function moveFocusDownFrom(startingNode) {
                var next = startingNode.nextVisibleNode();
                if (next) {
                    next.focus();
                }
            }

            /**
             * Move select up the visible tree from a starting node.
             *
             * @category DOM
             * @private
             * @param {object} startingNode Node object.
             * @return {void}
             */

        }, {
            key: 'moveFocusUpFrom',
            value: function moveFocusUpFrom(startingNode) {
                var prev = startingNode.previousVisibleNode();
                if (prev) {
                    prev.focus();
                }
            }

            /**
             * Helper method for obtaining the data-uid from a DOM element.
             *
             * @category DOM
             * @private
             * @param {HTMLElement} element HTML Element.
             * @return {object} Node object
             */

        }, {
            key: 'nodeFromTitleDOMElement',
            value: function nodeFromTitleDOMElement(element) {
                var uid = element.parentNode.parentNode.getAttribute('data-uid');
                return this._tree.node(uid);
            }

            /**
             * Drag enter listener.
             *
             * @category DOM
             * @private
             * @param {DragEvent} event Drag enter.
             * @return {void}
             */

        }, {
            key: 'onDragEnter',
            value: function onDragEnter(event) {
                event.preventDefault();

                event.target.classList.add('drag-targeting', 'drag-targeting-insert');
            }

            /**
             * Drag leave listener.
             *
             * @category DOM
             * @private
             * @param {DragEvent} event Drag leave.
             * @return {void}
             */

        }, {
            key: 'onDragLeave',
            value: function onDragLeave(event) {
                event.preventDefault();

                this.unhighlightTarget(event.target);
            }

            /**
             * Drag over listener.
             *
             * @category DOM
             * @private
             * @param {DragEvent} event Drag over.
             * @return {void}
             */

        }, {
            key: 'onDragOver',
            value: function onDragOver(event) {
                event.preventDefault();
            }

            /**
             * Drop listener.
             *
             * @category DOM
             * @private
             * @param {DragEvent} event Dropped.
             * @return {void}
             */

        }, {
            key: 'onDrop',
            value: function onDrop(event) {
                event.preventDefault();

                this.unhighlightTarget(event.target);

                var treeId = event.dataTransfer.getData('treeId');
                var nodeId = event.dataTransfer.getData('nodeId');

                // Find the tree
                var tree = InspireDOM.getTreeById(treeId);
                var node = tree.node(nodeId);

                node.state('drop-target', true);

                // Remove the node from its previous context
                var exported = node.remove(true);

                // Add the node to this tree/level
                var newNode = this._tree.addNode(exported);
                var newIndex = this._tree.indexOf(newNode);

                this._tree.emit('node.drop', event, newNode, null, newIndex);
            }

            /**
             * Triggers rendering for the given node array.
             *
             * @category DOM
             * @private
             * @param {array} nodes Array of node objects.
             * @return {void}
             */

        }, {
            key: 'renderNodes',
            value: function renderNodes(nodes) {
                render(createComponentVNode(2, Tree, {
                    'dom': this,
                    'nodes': nodes || this._tree.nodes()
                }), this.$target);
            }
        }, {
            key: 'scrollListener',


            /**
             * Listens for scroll events, to automatically trigger
             * Load More links when they're scrolled into view.
             *
             * @category DOM
             * @private
             * @param {Event} event Scroll event.
             * @return {void}
             */
            value: function scrollListener(event) {
                var _this2 = this;

                if (!this.rendering && !this.loading) {
                    // Get the bounding rect of the scroll layer
                    var rect = this.$scrollLayer.getBoundingClientRect();

                    // Find all load-more links
                    var links = document.querySelectorAll('.load-more');
                    _.each(links, function (link) {
                        // Look for load-more links which overlap our "viewport"
                        var r = link.getBoundingClientRect();
                        var overlap = !(rect.right < r.left || rect.left > r.right || rect.bottom < r.top || rect.top > r.bottom);

                        if (overlap) {
                            // Auto-trigger Load More links
                            var context = void 0;

                            var $parent = link.parentNode.parentNode.parentNode;
                            if ($parent.tagName === 'LI') {
                                context = _this2._tree.node($parent.getAttribute('data-uid'));
                            }

                            _this2._tree.loadMore(context, event);
                        }
                    });
                }
            }

            /**
             * Scroll the first selected node into view.
             *
             * @category DOM
             * @private
             * @return {void}
             */

        }, {
            key: 'scrollSelectedIntoView',
            value: function scrollSelectedIntoView() {
                var $selected = this.$target.querySelector('.selected');

                if ($selected && this.$scrollLayer) {
                    this.$scrollLayer.scrollTop = $selected.offsetTop;
                }
            }

            /**
             * Remove highlight class.
             *
             * @category DOM
             * @private
             * @param {HTMLElement} element Target element.
             * @return {void}
             */

        }, {
            key: 'unhighlightTarget',
            value: function unhighlightTarget(element) {
                if (element) {
                    element.classList.remove('drag-targeting', 'drag-targeting-insert');
                }
            }
        }], [{
            key: 'getTreeById',
            value: function getTreeById(id) {
                var element = document.querySelector('[data-uid="' + id + '"]');
                if (element) {
                    return element.inspireTree;
                }
            }
        }]);
        return InspireDOM;
    }();

    return InspireDOM;

})));
