text: let TetherBase;
  if (typeof TetherBase === 'undefined') {
    TetherBase = {modules: []};
  }
  
  let zeroElement = null;
  
  // Same as native getBoundingClientRect, except it takes into account parent <frame> offsets
  // if the element lies within a nested document (<frame> or <iframe>-like).
  function getActualBoundingClientRect(node) {
    let boundingRect = node.getBoundingClientRect();
  
    // The original object returned by getBoundingClientRect is immutable, so we clone it
    // We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
    let rect = {};
    for (var k in boundingRect) {
      rect[k] = boundingRect[k];
    }
  
    if (node.ownerDocument !== document) {
      let frameElement = node.ownerDocument.defaultView.frameElement;
      if (frameElement) {
        let frameRect = getActualBoundingClientRect(frameElement);
        rect.top += frameRect.top;
        rect.bottom += frameRect.top;
        rect.left += frameRect.left;
        rect.right += frameRect.left;
      }
    }
  
    return rect;
  }
  
  function getScrollParents(el) {
    // In firefox if the el is inside an iframe with display: none; window.getComputedStyle() will return null;
    // https://bugzilla.mozilla.org/show_bug.cgi?id=548397
    const computedStyle = getComputedStyle(el) || {};
    const position = computedStyle.position;
    let parents = [];
  
    if (position === 'fixed') {
      return [el];
    }
  
    let parent = el;
    while ((parent = parent.parentNode) && parent && parent.nodeType === 1) {
      let style;
      try {
        style = getComputedStyle(parent);
      } catch (err) {}
  
      if (typeof style === 'undefined' || style === null) {
        parents.push(parent);
        return parents;
      }
  
      const {overflow, overflowX, overflowY} = style;
      if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
        if (position !== 'absolute' || ['relative', 'absolute', 'fixed'].indexOf(style.position) >= 0) {
          parents.push(parent)
        }
      }
    }
  
    parents.push(el.ownerDocument.body);
    
    // If the node is within a frame, account for the parent window scroll
    if (el.ownerDocument !== document) {
      parents.push(el.ownerDocument.defaultView);
    }
    
    return parents;
  }
  
  const uniqueId = (() => {
    let id = 0;
    return () => ++id;
  })();
  
  const zeroPosCache = {};
  const getOrigin = () => {
    // getBoundingClientRect is unfortunately too accurate.  It introduces a pixel or two of
    // jitter as the user scrolls that messes with our ability to detect if two positions
    // are equivilant or not.  We place an element at the top left of the page that will
    // get the same jitter, so we can cancel the two out.
    let node = zeroElement;
    if (!node) {
      node = document.createElement('div');
      node.setAttribute('data-tether-id', uniqueId());
      extend(node.style, {
        top: 0,
        left: 0,
        position: 'absolute'
      });
  
      document.body.appendChild(node);
  
      zeroElement = node;
    }
  
    const id = node.getAttribute('data-tether-id');
    if (typeof zeroPosCache[id] === 'undefined') {
      zeroPosCache[id] = getActualBoundingClientRect(node);
  
      // Clear the cache when this position call is done
      defer(() => {
        delete zeroPosCache[id];
      });
    }
  
    return zeroPosCache[id];
  };
  
  function removeUtilElements() {
    if (zeroElement) {
      document.body.removeChild(zeroElement);
    }
    zeroElement = null;
  };
  
  function getBounds(el) {
    let doc;
    if (el === document) {
      doc = document;
      el = document.documentElement;
    } else {
      doc = el.ownerDocument;
    }
  
    const docEl = doc.documentElement;
  
    const box = getActualBoundingClientRect(el);
  
    const origin = getOrigin();
  
    box.top -= origin.top;
    box.left -= origin.left;
  
    if (typeof box.width === 'undefined') {
      box.width = document.body.scrollWidth - box.left - box.right;
    }
    if (typeof box.height === 'undefined') {
      box.height = document.body.scrollHeight - box.top - box.bottom;
    }
  
    box.top = box.top - docEl.clientTop;
    box.left = box.left - docEl.clientLeft;
    box.right = doc.body.clientWidth - box.width - box.left;
    box.bottom = doc.body.clientHeight - box.height - box.top;
  
    return box;
  }
  
  function getOffsetParent(el) {
    return el.offsetParent || document.documentElement;
  }
  
  function getScrollBarSize() {
    const inner = document.createElement('div');
    inner.style.width = '100%';
    inner.style.height = '200px';
  
    const outer = document.createElement('div');
    extend(outer.style, {
      position: 'absolute',
      top: 0,
      left: 0,
      pointerEvents: 'none',
      visibility: 'hidden',
      width: '200px',
      height: '150px',
      overflow: 'hidden'
    });
  
    outer.appendChild(inner);
  
    document.body.appendChild(outer);
  
    const widthContained = inner.offsetWidth;
    outer.style.overflow = 'scroll';
    let widthScroll = inner.offsetWidth;
  
    if (widthContained === widthScroll) {
      widthScroll = outer.clientWidth;
    }
  
    document.body.removeChild(outer);
  
    const width = widthContained - widthScroll;
  
    return {width, height: width};
  }
  
  function extend(out={}) {
    const args = [];
  
    Array.prototype.push.apply(args, arguments);
  
    args.slice(1).forEach(obj => {
      if (obj) {
        for (let key in obj) {
          if ({}.hasOwnProperty.call(obj, key)) {
            out[key] = obj[key];
          }
        }
      }
    });
  
    return out;
  }
  
  function removeClass(el, name) {
    if (typeof el.classList !== 'undefined') {
      name.split(' ').forEach(cls => {
        if (cls.trim()) {
          el.classList.remove(cls);
        }
      });
    } else {
      const regex = new RegExp(`(^| )${ name.split(' ').join('|') }( |$)`, 'gi');
      const className = getClassName(el).replace(regex, ' ');
      setClassName(el, className);
    }
  }
  
  function addClass(el, name) {
    if (typeof el.classList !== 'undefined') {
      name.split(' ').forEach(cls => {
        if (cls.trim()) {
          el.classList.add(cls);
        }
      });
    } else {
      removeClass(el, name);
      const cls = getClassName(el) + ` ${name}`;
      setClassName(el, cls);
    }
  }
  
  function hasClass(el, name) {
    if (typeof el.classList !== 'undefined') {
      return el.classList.contains(name);
    }
    const className = getClassName(el);
    return new RegExp(`(^| )${ name }( |$)`, 'gi').test(className);
  }
  
  function getClassName(el) {
    // Can't use just SVGAnimatedString here since nodes within a Frame in IE have
    // completely separately SVGAnimatedString base classes
    if (el.className instanceof el.ownerDocument.defaultView.SVGAnimatedString) {
      return el.className.baseVal;
    }
    return el.className;
  }
  
  function setClassName(el, className) {
    el.setAttribute('class', className);
  }
  
  
  function updateClasses(el, add, all) {
    // Of the set of 'all' classes, we need the 'add' classes, and only the
    // 'add' classes to be set.
    all.forEach(cls => {
      if (add.indexOf(cls) === -1 && hasClass(el, cls)) {
        removeClass(el, cls);
      }
    });
  
    add.forEach(cls => {
      if (!hasClass(el, cls)) {
        addClass(el, cls);
      }
    });
  }
  
  const deferred = [];
  
  const defer = (fn) => {
    deferred.push(fn);
  };
  
  const flush = () => {
    let fn;
    while(fn = deferred.pop()) {
      fn();
    }
  };
  
  class Evented {
    on(event, handler, ctx, once=false) {
      if (typeof this.bindings === 'undefined') {
        this.bindings = {};
      }
      if (typeof this.bindings[event] === 'undefined') {
        this.bindings[event] = [];
      }
      this.bindings[event].push({handler, ctx, once});
    }
  
    once(event, handler, ctx) {
      this.on(event, handler, ctx, true);
    }
  
    off(event, handler) {
      if (typeof this.bindings === 'undefined' ||
          typeof this.bindings[event] === 'undefined') {
        return;
      }
  
      if (typeof handler === 'undefined') {
        delete this.bindings[event];
      } else {
        let i = 0;
        while (i < this.bindings[event].length) {
          if (this.bindings[event][i].handler === handler) {
            this.bindings[event].splice(i, 1);
          } else {
            ++i;
          }
        }
      }
    }
  
    trigger(event, ...args) {
      if (typeof this.bindings !== 'undefined' && this.bindings[event]) {
        let i = 0;
        while (i < this.bindings[event].length) {
          const {handler, ctx, once} = this.bindings[event][i];
  
          let context = ctx;
          if (typeof context === 'undefined') {
            context = this;
          }
  
          handler.apply(context, args);
  
          if (once) {
            this.bindings[event].splice(i, 1);
          } else {
            ++i;
          }
        }
      }
    }
  }
  
  TetherBase.Utils = {
    getActualBoundingClientRect,
    getScrollParents,
    getBounds,
    getOffsetParent,
    extend,
    addClass,
    removeClass,
    hasClass,
    updateClasses,
    defer,
    flush,
    uniqueId,
    Evented,
    getScrollBarSize,
    removeUtilElements
  };
  