if (typeof lastCall !== 'undefined' && now() - lastCall < 10) {
// Some browsers call events a little too frequently, refuse to run more than is reasonable
if (pendingTimeout != null) {
clearTimeout(pendingTimeout);
lastDuration = now() - lastCall;
if (typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
['resize', 'scroll', 'touchmove'].forEach(function (event) {
window.addEventListener(event, tick);
var autoToFixedAttachment = function autoToFixedAttachment(attachment, relativeToAttachment) {
var left = attachment.left;
var top = attachment.top;
left = MIRROR_LR[relativeToAttachment.left];
top = MIRROR_TB[relativeToAttachment.top];
return { left: left, top: top };
var attachmentToOffset = function attachmentToOffset(attachment) {
var left = attachment.left;
var top = attachment.top;
if (typeof OFFSET_MAP[attachment.left] !== 'undefined') {
left = OFFSET_MAP[attachment.left];
if (typeof OFFSET_MAP[attachment.top] !== 'undefined') {
top = OFFSET_MAP[attachment.top];
return { left: left, top: top };
var out = { top: 0, left: 0 };
for (var _len = arguments.length, offsets = Array(_len), _key = 0; _key < _len; _key++) {
offsets[_key] = arguments[_key];
offsets.forEach(function (_ref) {
if (typeof top === 'string') {
top = parseFloat(top, 10);
if (typeof left === 'string') {
left = parseFloat(left, 10);
function offsetToPx(offset, size) {
if (typeof offset.left === 'string' && offset.left.indexOf('%') !== -1) {
offset.left = parseFloat(offset.left, 10) / 100 * size.width;
if (typeof offset.top === 'string' && offset.top.indexOf('%') !== -1) {
offset.top = parseFloat(offset.top, 10) / 100 * size.height;
var parseOffset = function parseOffset(value) {
var _value$split = value.split(' ');
var _value$split2 = _slicedToArray(_value$split, 2);
var top = _value$split2[0];
var left = _value$split2[1];
return { top: top, left: left };
var parseAttachment = parseOffset;
var TetherClass = (function (_Evented) {
_inherits(TetherClass, _Evented);
function TetherClass(options) {
_classCallCheck(this, TetherClass);
_get(Object.getPrototypeOf(TetherClass.prototype), 'constructor', this).call(this);
this.position = this.position.bind(this);
this.setOptions(options, false);
TetherBase.modules.forEach(function (module) {
if (typeof module.initialize !== 'undefined') {
module.initialize.call(_this);
_createClass(TetherClass, [{
value: function getClass() {
var key = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0];
var classes = this.options.classes;
if (typeof classes !== 'undefined' && classes[key]) {
return this.options.classes[key];
} else if (this.options.classPrefix) {
return this.options.classPrefix + '-' + key;
value: function setOptions(options) {
var pos = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
targetAttachment: 'auto auto',
this.options = extend(defaults, options);
var _options = this.options;
var element = _options.element;
var target = _options.target;
var targetModifier = _options.targetModifier;
this.targetModifier = targetModifier;
if (this.target === 'viewport') {
this.target = document.body;
this.targetModifier = 'visible';
} else if (this.target === 'scroll-handle') {
this.target = document.body;
this.targetModifier = 'scroll-handle';
['element', 'target'].forEach(function (key) {
if (typeof _this2[key] === 'undefined') {
throw new Error('Tether Error: Both element and target must be defined');
if (typeof _this2[key].jquery !== 'undefined') {
_this2[key] = _this2[key][0];
} else if (typeof _this2[key] === 'string') {
_this2[key] = document.querySelector(_this2[key]);
addClass(this.element, this.getClass('element'));
if (!(this.options.addTargetClasses === false)) {
addClass(this.target, this.getClass('target'));
if (!this.options.attachment) {
throw new Error('Tether Error: You must provide an attachment');
this.targetAttachment = parseAttachment(this.options.targetAttachment);
this.attachment = parseAttachment(this.options.attachment);
this.offset = parseOffset(this.options.offset);
this.targetOffset = parseOffset(this.options.targetOffset);
if (typeof this.scrollParents !== 'undefined') {
if (this.targetModifier === 'scroll-handle') {
this.scrollParents = [this.target];
this.scrollParents = getScrollParents(this.target);
if (!(this.options.enabled === false)) {
value: function getTargetBounds() {
if (typeof this.targetModifier !== 'undefined') {
if (this.targetModifier === 'visible') {
if (this.target === document.body) {
return { top: pageYOffset, left: pageXOffset, height: innerHeight, width: innerWidth };
var bounds = getBounds(this.target);
out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top));
out.height = Math.min(out.height, bounds.height - (bounds.top + bounds.height - (pageYOffset + innerHeight)));
out.height = Math.min(innerHeight, out.height);
out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left));
out.width = Math.min(out.width, bounds.width - (bounds.left + bounds.width - (pageXOffset + innerWidth)));
out.width = Math.min(innerWidth, out.width);
if (out.top < pageYOffset) {
if (out.left < pageXOffset) {
} else if (this.targetModifier === 'scroll-handle') {
var target = this.target;
if (target === document.body) {
target = document.documentElement;
bounds = getBounds(target);
var style = getComputedStyle(target);
var hasBottomScroll = target.scrollWidth > target.clientWidth || [style.overflow, style.overflowX].indexOf('scroll') >= 0 || this.target !== document.body;
var height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom;
height: height * 0.975 * (height / target.scrollHeight),
left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15
if (height < 408 && this.target === document.body) {
fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58;
if (this.target !== document.body) {
out.height = Math.max(out.height, 24);
var scrollPercentage = this.target.scrollTop / (target.scrollHeight - height);
out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth);
if (this.target === document.body) {
out.height = Math.max(out.height, 24);
return getBounds(this.target);
value: function clearCache() {
value: function cache(k, getter) {
// More than one module will often need the same DOM info, so
// we keep a cache which is cleared on each position call
if (typeof this._cache === 'undefined') {
if (typeof this._cache[k] === 'undefined') {
this._cache[k] = getter.call(this);
value: function enable() {
var pos = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
if (!(this.options.addTargetClasses === false)) {
addClass(this.target, this.getClass('enabled'));
addClass(this.element, this.getClass('enabled'));
this.scrollParents.forEach(function (parent) {
if (parent !== _this3.target.ownerDocument) {
parent.addEventListener('scroll', _this3.position);
value: function disable() {
removeClass(this.target, this.getClass('enabled'));
removeClass(this.element, this.getClass('enabled'));
if (typeof this.scrollParents !== 'undefined') {
this.scrollParents.forEach(function (parent) {
parent.removeEventListener('scroll', _this4.position);
value: function destroy() {
tethers.forEach(function (tether, i) {
// Remove any elements we were using for convenience from the DOM
if (tethers.length === 0) {
key: 'updateAttachClasses',
value: function updateAttachClasses(elementAttach, targetAttach) {
elementAttach = elementAttach || this.attachment;
targetAttach = targetAttach || this.targetAttachment;
var sides = ['left', 'top', 'bottom', 'right', 'middle', 'center'];
if (typeof this._addAttachClasses !== 'undefined' && this._addAttachClasses.length) {
// updateAttachClasses can be called more than once in a position call, so
// we need to clean up after ourselves such that when the last defer gets
// ran it doesn't add any extra classes from previous calls.
this._addAttachClasses.splice(0, this._addAttachClasses.length);
if (typeof this._addAttachClasses === 'undefined') {
this._addAttachClasses = [];
var add = this._addAttachClasses;
add.push(this.getClass('element-attached') + '-' + elementAttach.top);
if (elementAttach.left) {
add.push(this.getClass('element-attached') + '-' + elementAttach.left);
add.push(this.getClass('target-attached') + '-' + targetAttach.top);
add.push(this.getClass('target-attached') + '-' + targetAttach.left);
sides.forEach(function (side) {
all.push(_this6.getClass('element-attached') + '-' + side);
all.push(_this6.getClass('target-attached') + '-' + side);
if (!(typeof _this6._addAttachClasses !== 'undefined')) {
updateClasses(_this6.element, _this6._addAttachClasses, all);
if (!(_this6.options.addTargetClasses === false)) {
updateClasses(_this6.target, _this6._addAttachClasses, all);
delete _this6._addAttachClasses;
value: function position() {
var flushChanges = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
// flushChanges commits the changes immediately, leave true unless you are positioning multiple
// tethers (in which case call Tether.Utils.flush yourself when you're done)
// Turn 'auto' attachments into the appropriate corner or edge
var targetAttachment = autoToFixedAttachment(this.targetAttachment, this.attachment);
this.updateAttachClasses(this.attachment, targetAttachment);
var elementPos = this.cache('element-bounds', function () {
return getBounds(_this7.element);
var width = elementPos.width;
var height = elementPos.height;
if (width === 0 && height === 0 && typeof this.lastSize !== 'undefined') {
var _lastSize = this.lastSize;
// We cache the height and width to make it possible to position elements that are
height = _lastSize.height;
this.lastSize = { width: width, height: height };
var targetPos = this.cache('target-bounds', function () {
return _this7.getTargetBounds();
var targetSize = targetPos;