function InspireTree(opts) {
      var _this;
      _classCallCheck(this, InspireTree);
      _this = _callSuper(this, InspireTree);

      // Init properties
      _this._lastSelectedNode;
      _this._muted = false;
      _this.allowsLoadEvents = false;
      _this.id = v4();
      _this.initialized = false;
      _this.isDynamic = false;
      _this.opts = opts;
      _this.preventDeselection = false;

      // Assign defaults
      _this.config = defaultsDeep({}, opts, {
        allowLoadEvents: [],
        cancelEditOnBlur: true,
        checkbox: {
          autoCheckChildren: true
        },
        contextMenu: false,
        data: false,
        editable: false,
        editing: {
          add: false,
          edit: false,
          remove: false
        },
        nodes: {
          resetStateOnRestore: true
        },
        pagination: {
          limit: -1
        },
        search: {
          matcher: false,
          matchProcessor: false
        },
        selection: {
          allow: noop,
          autoDeselect: true,
          autoSelectChildren: false,
          autoSelectOnNodeRemoval: true,
          disableDirectDeselection: false,
          mode: 'default',
          multiple: false,
          require: false
        },
        showCheckboxes: false,
        sort: false
      });

      // If checkbox mode, we must force auto-selecting children
      if (_this.config.selection.mode === 'checkbox') {
        _this.config.selection.autoSelectChildren = true;

        // In checkbox mode, checked=selected
        _this.on('node.checked', function (node) {
          if (!node.selected()) {
            node.select(true);
          }
        });
        _this.on('node.selected', function (node) {
          if (!node.checked()) {
            node.check(true);
          }
        });
        _this.on('node.unchecked', function (node) {
          if (node.selected()) {
            node.deselect(true);
          }
        });
        _this.on('node.deselected', function (node) {
          if (node.checked()) {
            node.uncheck(true);
          }
        });
      }

      // If auto-selecting children, we must force multiselect
      if (_this.config.selection.autoSelectChildren) {
        _this.config.selection.multiple = true;
        _this.config.selection.autoDeselect = false;
      }

      // Treat editable as full edit mode
      if (opts.editable && !opts.editing) {
        _this.config.editing.add = true;
        _this.config.editing.edit = true;
        _this.config.editing.remove = true;
      }

      // Support simple config for search
      if (isFunction(opts.search)) {
        _this.config.search = {
          matcher: opts.search,
          matchProcessor: false
        };
      }

      // Init the default state for nodes
      _this.defaultState = {
        collapsed: true,
        editable: get(_assertThisInitialized(_this), 'config.editing.edit'),
        editing: false,
        draggable: true,
        'drop-target': true,
        focused: false,
        hidden: false,
        indeterminate: false,
        loading: false,
        matched: false,
        removed: false,
        rendered: false,
        selectable: true,
        selected: false
      };

      // Cache some configs
      _this.allowsLoadEvents = isArray(_this.config.allowLoadEvents) && _this.config.allowLoadEvents.length > 0;
      _this.isDynamic = isFunction(_this.config.data);

      // Override emitter so we can better control flow
      var emit = _this.emit;
      _this.emit = function () {
        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }
        if (!_this.isEventMuted(args[0])) {
          // Duck-type for a DOM event
          if (isFunction(get(args, '[0].preventDefault'))) {
            var event = args[0];
            event.treeDefaultPrevented = false;
            event.preventTreeDefault = function () {
              event.treeDefaultPrevented = true;
            };
          }
          emit.apply(_assertThisInitialized(_this), args);
        }
      };

      // Init the model
      _this.model = new TreeNodes(_assertThisInitialized(_this));

      // Init nodes values
      _this._previouslySelectedNodes = new TreeNodes(_assertThisInitialized(_this));

      // Load initial user data
      if (_this.config.data) {
        _this.load(_this.config.data);
      }
      _this.initialized = true;
      return _this;
    }
Represents a singe tree instance.

@return {InspireTree} Tree instance.