src/minibar.js
/*!
* MiniBar 0.5.1
* http://mobius.ovh/
*
* Released under the MIT license
*/
(function(root) {
"use strict";
var win = window,
doc = document,
body = doc.body,
// Dimension terms
trackPos = {
x: "left",
y: "top"
},
trackSize = {
x: "width",
y: "height"
},
scrollPos = {
x: "scrollLeft",
y: "scrollTop"
},
scrollSize = {
x: "scrollWidth",
y: "scrollHeight"
},
offsetSize = {
x: "offsetWidth",
y: "offsetHeight"
},
mAxis = {
x: "pageX",
y: "pageY"
};
/**
* Object.assign polyfill
* @param {Object} target
* @param {Object} args
* @return {Object}
*/
var extend = function(r, t) {
for (var e = Object(r), n = 1; n < arguments.length; n++) {
var a = arguments[n];
if (null != a)
for (var o in a) Object.prototype.hasOwnProperty.call(a, o) && (e[o] = a[o])
}
return e
};
var DOM = {
/**
* Mass assign style properties
* @param {Object} t
* @param {(String|Object)} e
* @param {String|Object}
*/
css: function(t, e) {
var i = t && t.style,
n = "[object Object]" === Object.prototype.toString.call(e);
if (i) {
if (!e) return win.getComputedStyle(t);
n && each(e, function(t, e) {
t in i || (t = "-webkit-" + t), i[t] = e + ("string" == typeof e ? "" : "opacity" === t ? "" : "px")
})
}
},
/**
* Get an element's DOMRect relative to the document instead of the viewport.
* @param {Object} t HTMLElement
* @param {Boolean} e Include margins
* @return {Object} Formatted DOMRect copy
*/
rect: function(e) {
var t = win,
o = e.getBoundingClientRect(),
b = doc.documentElement || body.parentNode || body,
d = (void 0 !== t.pageXOffset) ? t.pageXOffset : b.scrollLeft,
n = (void 0 !== t.pageYOffset) ? t.pageYOffset : b.scrollTop;
return {
x: o.left + d,
y: o.top + n,
x2: o.left + o.width + d,
y2: o.top + o.height + n,
height: Math.round(o.height),
width: Math.round(o.width)
}
},
/**
* classList shim
* @type {Object}
*/
classList: {
contains: function(s, a) {
if (s) return s.classList ? s.classList.contains(a) : !!s.className && !!s.className.match(new RegExp("(\\s|^)" + a + "(\\s|$)"))
},
add: function(s, a) {
DOM.classList.contains(s, a) || (s.classList ? s.classList.add(a) : s.className = s.className.trim() + " " + a)
},
remove: function(s, a) {
DOM.classList.contains(s, a) && (s.classList ? s.classList.remove(a) : s.className = s.className.replace(new RegExp("(^|\\s)" + a.split(" ").join("|") + "(\\s|$)", "gi"), " "))
},
toggle: function(s, a, c) {
var i = this.contains(s, a) ? !0 !== c && "remove" : !1 !== c && "add";
i && this[i](s, a)
}
},
/**
* Add event listener to target
* @param {Object} el
* @param {String} e
* @param {Function} fn
*/
on: function(el, e, fn) {
el.addEventListener(e, fn, false);
},
/**
* Remove event listener from target
* @param {Object} el
* @param {String} e
* @param {Function} fn
*/
off: function(el, e, fn) {
el.removeEventListener(e, fn);
},
/**
* Check is item array or array-like
* @param {Mixed} arr
* @return {Boolean}
*/
isCollection: function(arr) {
return Array.isArray(arr) || arr instanceof HTMLCollection || arr instanceof NodeList;
},
/**
* Get native scrollbar width
* @return {Number} Scrollbar width
*/
scrollWidth: function() {
var t = 0,
e = doc.createElement("div");
return e.style.cssText = "width: 100; height: 100; overflow: scroll; position: absolute; top: -9999;", doc.body.appendChild(e), t = e.offsetWidth - e.clientWidth, doc.body.removeChild(e), t
},
};
/**
* Iterator helper
* @param {(Array|Object)} arr Any object, array or array-like collection.
* @param {Function} f The callback function
* @param {Object} s Change the value of this
* @return {Void}
*/
var each = function(arr, fn, s) {
if ("[object Object]" === Object.prototype.toString.call(arr)) {
for (var d in arr) {
if (Object.prototype.hasOwnProperty.call(arr, d)) {
fn.call(s, d, arr[d]);
}
}
} else {
for (var e = 0, f = arr.length; e < f; e++) {
fn.call(s, e, arr[e]);
}
}
};
/**
* Returns a function, that, as long as it continues to be invoked, will not be triggered.
* @param {Function} fn
* @param {Number} wait
* @param {Boolean} now
* @return {Function}
*/
var debounce = function(n, t, u) {
var e;
return function() {
var i = this,
o = arguments,
a = u && !e;
clearTimeout(e), e = setTimeout(function() {
e = null, u || n.apply(i, o)
}, t), a && n.apply(i, o)
}
}
var raf = win.requestAnimationFrame || function() {
var e = 0;
return win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || function(n) {
var t, i = (new Date).getTime();
return t = Math.max(0, 16 - (i - e)), e = i + t, setTimeout(function() {
n(i + t)
}, t)
}
}();
var caf = win.cancelAnimationFrame || function(id) {
clearTimeout(id);
}();
/**
* Main Library
* @param {(String|Object)} content CSS3 selector string or node reference
* @param {Object} options User defined options
*/
var MiniBar = function(container, options) {
this.container = typeof container === "string" ? doc.querySelector(container) : container;
this.config = {
barType: "default",
minBarSize: 10,
alwaysShowBars: false,
horizontalMouseScroll: false,
scrollX: true,
scrollY: true,
navButtons: false,
scrollAmount: 10,
mutationObserver: {
attributes: false,
childList: true,
subtree: true
},
onInit: function() {},
onUpdate: function() {},
onStart: function() {},
onScroll: function() {},
onEnd: function() {},
classes: {
container: "mb-container",
content: "mb-content",
track: "mb-track",
bar: "mb-bar",
visible: "mb-visible",
progress: "mb-progress",
hover: "mb-hover",
scrolling: "mb-scrolling",
textarea: "mb-textarea",
wrapper: "mb-wrapper",
nav: "mb-nav",
btn: "mb-button",
btns: "mb-buttons",
increase: "mb-increase",
decrease: "mb-decrease",
item: "mb-item",
itemVisible: "mb-item-visible",
itemPartial: "mb-item-partial",
itemHidden: "mb-item-hidden"
}
};
// User options
if (options) {
this.config = extend({}, this.config, options);
} else if (win.MiniBarOptions) {
this.config = extend({}, this.config, win.MiniBarOptions);
}
this.css = win.getComputedStyle(this.container);
this.size = DOM.scrollWidth();
this.textarea = this.container.nodeName.toLowerCase() === "textarea";
this.bars = {
x: {},
y: {}
};
this.tracks = {
x: {},
y: {}
};
this.lastX = 0;
this.lastY = 0;
this.scrollDirection = {
x: 0,
y: 0
};
// Events
this.events = {};
// Bind events
var events = ["scroll", "mouseenter", "mousedown", "mousemove", "mouseup", "wheel"];
for (var i = 0; i < events.length; i++) {
this.events[events[i]] = this["_"+events[i]].bind(this);
}
this.events.update = this.update.bind(this);
// Debounce win resize
this.events.debounce = debounce(this.events.update, 50);
this.init();
};
var proto = MiniBar.prototype;
/**
* Init instance
* @return {Void}
*/
proto.init = function() {
var mb = this,
o = mb.config,
ev = mb.events;
if (!mb.initialised) {
// We need a seperate wrapper for the textarea that we can pad
// otherwise the text will be up against the container edges
if (mb.textarea) {
mb.content = mb.container;
mb.container = doc.createElement("div");
DOM.classList.add(mb.container, o.classes.textarea);
mb.wrapper = doc.createElement("div");
DOM.classList.add(mb.wrapper, o.classes.wrapper);
mb.container.appendChild(mb.wrapper);
mb.content.parentNode.insertBefore(mb.container, mb.content);
// Update the bar on input
mb.content.addEventListener("input", function(e) {
mb.update();
});
} else {
mb.content = doc.createElement("div");
// Move all nodes to the the new content node
while (mb.container.firstChild) {
mb.content.appendChild(mb.container.firstChild);
}
}
DOM.classList.add(mb.container, o.classes.container);
DOM.classList.add(mb.content, o.classes.content);
if (o.alwaysShowBars) {
DOM.classList.add(mb.container, o.classes.visible);
}
// Set the tracks and bars and append them to the container
each(mb.tracks, function(axis, track) {
mb.bars[axis].node = doc.createElement("div");
track.node = doc.createElement("div");
DOM.classList.add(track.node, o.classes.track);
DOM.classList.add(track.node, o.classes.track + "-" + axis);
DOM.classList.add(mb.bars[axis].node, o.classes.bar);
track.node.appendChild(mb.bars[axis].node);
// Add nav buttons
if (o.navButtons) {
var dec = doc.createElement("button"),
inc = doc.createElement("button"),
wrap = doc.createElement("div"),
amount = o.scrollAmount;
dec.className = o.classes.btn + " " + o.classes.decrease;
inc.className = o.classes.btn + " " + o.classes.increase;
wrap.className = o.classes.btns + " " + o.classes.btns + "-" + axis;
wrap.appendChild(dec);
wrap.appendChild(track.node);
wrap.appendChild(inc);
mb.container.appendChild(wrap);
DOM.classList.add(mb.container, o.classes.nav);
// Mousedown on buttons
DOM.on(wrap, "mousedown", function(e) {
var el = e.target;
caf(mb.frame);
if (el === inc || el === dec) {
var scroll = mb.content[scrollPos[axis]];
var move = function(c) {
switch (mb.content[scrollPos[axis]] = scroll, el) {
case dec:
scroll -= amount;
break;
case inc:
scroll += amount
}
mb.frame = raf(move)
};
move();
}
});
// Mouseup on buttons
DOM.on(wrap, "mouseup", function(e) {
var c = e.target,
m = 5 * amount;
caf(mb.frame), c !== inc && c !== dec || mb.scrollBy(c === dec ? -m : m, axis)
});
} else {
mb.container.appendChild(track.node);
}
if (o.barType === "progress") {
DOM.classList.add(track.node, o.classes.progress);
DOM.on(track.node, "mousedown", ev.mousedown);
} else {
DOM.on(track.node, "mousedown", ev.mousedown);
}
DOM.on(track.node, "mouseenter", function(e) {
DOM.classList.add(mb.container, o.classes.hover + "-" + axis);
});
DOM.on(track.node, "mouseleave", function(e) {
if (!mb.down) {
DOM.classList.remove(mb.container, o.classes.hover + "-" + axis);
}
});
});
// Append the content
if (mb.textarea) {
mb.wrapper.appendChild(mb.content);
} else {
mb.container.appendChild(mb.content);
}
if (mb.css.position === "static") {
mb.manualPosition = true;
mb.container.style.position = "relative";
}
if (o.observableItems) {
const items = this.getItems();
if (items.length && "IntersectionObserver" in window) {
mb.items = items;
var threshold = [];
// Increase / decrease to set granularity
var increment = 0.01
// Don't want to have to type all of them...
for (var i = 0; i < 1; i += increment) {
threshold.push(i);
}
var callback = function(entries, observer) {
entries.forEach(entry => {
var node = entry.target;
var ratio = entry.intersectionRatio;
var intersecting = entry.isIntersecting;
var visible = intersecting && ratio >= 1;
var hidden = !intersecting && ratio <= 0;
var partial = intersecting && ratio > 0 && ratio < 1;
DOM.classList.toggle(node, o.classes.itemVisible, visible);
DOM.classList.toggle(node, o.classes.itemPartial, partial);
DOM.classList.toggle(node, o.classes.itemHidden, hidden);
});
};
this.intersectionObserver = new IntersectionObserver(callback, {
root: null,
rootMargin: '0px',
threshold: threshold
});
each(items, function(i, item) {
mb.intersectionObserver.observe(item);
});
}
}
mb.update();
DOM.on(mb.content, "scroll", ev.scroll);
DOM.on(mb.container, "mouseenter", ev.mouseenter);
if (o.horizontalMouseScroll) {
DOM.on(mb.content, "wheel", ev.wheel);
}
DOM.on(win, "resize", ev.debounce);
DOM.on(doc, 'DOMContentLoaded', ev.update);
DOM.on(win, 'load', ev.update);
// check for MutationObserver support
if ("MutationObserver" in window) {
var callback = function(mutationsList, observer) {
if (mb.intersectionObserver) {
for (var mutation of mutationsList) {
// update the instance if content changes
if (mutation.type == 'childList') {
// observe / unobserve items
for (var node of mutation.addedNodes) {
mb.intersectionObserver.observe(node);
}
for (var node of mutation.removedNodes) {
mb.intersectionObserver.unobserve(node);
}
}
}
}
if (mb.intersectionObserver) {
mb.items = mb.getItems();
}
// setTimeout(mb.update.bind(mb), 500);
mb.update();
};
this.mutationObserver = new MutationObserver(callback);
this.mutationObserver.observe(this.content, this.config.mutationObserver);
}
mb.initialised = true;
setTimeout(function() {
mb.config.onInit.call(mb, mb.getData());
}, 10);
}
};
proto.getItems = function() {
const o = this.config;
let items;
if (typeof o.observableItems === "string") {
items = this.content.querySelectorAll(o.observableItems);
}
if (o.observableItems instanceof HTMLCollection || o.observableItems instanceof NodeList) {
items = [].slice.call(o.observableItems);
}
return items;
};
/**
* Get instance data
* @return {Object}
*/
proto.getData = function(scrolling) {
var c = this.content;
const scrollTop = c.scrollTop;
const scrollLeft = c.scrollLeft;
const scrollHeight = c.scrollHeight;
const scrollWidth = c.scrollWidth;
const offsetWidth = c.offsetWidth;
const offsetHeight = c.offsetHeight;
const barSize = this.size;
const containerRect = this.rect;
return {
scrollTop,
scrollLeft,
scrollHeight,
scrollWidth,
offsetWidth,
offsetHeight,
containerRect,
barSize
}
};
/**
* Scroll content by amount
* @param {Number|String} position Position to scroll to
* @param {String} axis Scroll axis
* @return {Void}
*/
proto.scrollTo = function(position, axis) {
if (axis === undefined ) {
axis = "y";
}
var data = this.getData(),
amount;
if (typeof position === "string") {
if (position === "start") {
amount = -data[scrollPos[axis]];
} else if (position === "end") {
amount = data[scrollSize[axis]] - data[offsetSize[axis]] - data[scrollPos[axis]];
}
} else {
amount = position - data[scrollPos[axis]];
}
this.scrollBy(amount, axis);
};
/**
* Scroll content by amount
* @param {Number} amount Number of pixels to scroll
* @param {String} axis Scroll axis
* @param {Number} duration Duration of scroll animation in ms
* @param {Function} easing Easing function
* @return {Void}
*/
proto.scrollBy = function(amount, axis, duration, easing) {
if (axis === undefined ) {
axis = "y";
}
// No animation
if (duration === 0) {
this.content[scrollPos[axis]] += amount;
return;
}
// Duration of scroll
if (duration === undefined) {
duration = 250;
}
// Easing function
easing = easing || function(t, b, c, d) {
t /= d;
return -c * t * (t - 2) + b;
};
var mb = this,
st = Date.now(),
pos = mb.content[scrollPos[axis]];
// Scroll function
var scroll = function() {
var now = Date.now(),
ct = now - st;
// Cancel after allotted interval
if (ct > duration) {
caf(mb.frame);
mb.content[scrollPos[axis]] = Math.ceil(pos + amount);
return;
}
// Update scroll position
mb.content[scrollPos[axis]] = Math.ceil(easing(ct, pos, amount, duration));
// requestAnimationFrame
mb.frame = raf(scroll);
};
mb.frame = scroll();
};
/**
* Scroll to top
* @return {Void}
*/
proto.scrollToTop = function() {
this.scrollTo(0)
};
/**
* Scroll to bottom
* @return {Void}
*/
proto.scrollToBottom = function() {
var data = this.getData();
this.scrollTo(data.scrollHeight - data.offsetHeight)
};
/**
* Update cached values and recalculate sizes / positions
* @param {Object} e Event interface
* @return {Void}
*/
proto.update = function() {
var mb = this,
o = mb.config,
ct = mb.content,
s = mb.size;
// Cache the dimensions
mb.rect = DOM.rect(mb.container);
mb.scrollTop = ct.scrollTop;
mb.scrollLeft = ct.scrollLeft;
mb.scrollHeight = ct.scrollHeight;
mb.scrollWidth = ct.scrollWidth;
mb.offsetWidth = ct.offsetWidth;
mb.offsetHeight = ct.offsetHeight;
mb.clientWidth = ct.clientWidth;
mb.clientHeight = ct.clientHeight;
// Do we need horizontal scrolling?
var sx = mb.scrollWidth > mb.offsetWidth && !mb.textarea;
// Do we need vertical scrolling?
var sy = mb.scrollHeight > mb.offsetHeight;
DOM.classList.toggle(mb.container, "mb-scroll-x", sx && o.scrollX && !o.hideBars);
DOM.classList.toggle(mb.container, "mb-scroll-y", sy && o.scrollY && !o.hideBars);
// Style the content
DOM.css(ct, {
overflowX: sx ? "auto" : "",
overflowY: sy ? "auto" : "",
marginBottom: sx ? -s : "",
paddingBottom: sx ? s : "",
marginRight: sy ? -s : "",
paddingRight: sy && !o.hideBars ? s : ""
});
mb.scrollX = sx;
mb.scrollY = sy;
each(mb.tracks, function(i, track) {
extend(track, DOM.rect(track.node));
extend(mb.bars[i], DOM.rect(mb.bars[i].node));
});
// Update scrollbars
mb.updateBars();
mb.wrapperPadding = 0;
if (mb.textarea) {
var css = DOM.css(mb.wrapper);
// Textarea wrapper has added padding
mb.wrapperPadding = parseInt(css.paddingTop, 10) + parseInt(css.paddingBottom, 10);
// Only scroll to bottom if the cursor is at the end of the content and we're not dragging
if (!mb.down && mb.content.selectionStart >= mb.content.value.length) {
mb.content.scrollTop = mb.scrollToBottom();
}
}
this.config.onUpdate.call(this, this.getData());
};
/**
* Update a scrollbar's size and position
* @param {String} axis
* @return {Void}
*/
proto.updateBar = function(axis) {
var mb = this,
css = {},
ts = trackSize,
ss = scrollSize,
o = mb.config,
// Width or height of track
tsize = mb.tracks[axis][ts[axis]],
// Width or height of content
cs = mb.rect[ts[axis]] - mb.wrapperPadding,
// We need a live value, not cached
so = mb.content[scrollPos[axis]],
br = tsize / mb[ss[axis]],
sr = so / (mb[ss[axis]] - cs);
if (o.barType === "progress") {
// Only need to set the size of a progress bar
css[ts[axis]] = Math.floor(tsize * sr);
} else {
// Set the scrollbar size
css[ts[axis]] = Math.max(Math.floor(br * cs), o.minBarSize);
// Set the scrollbar position
css[trackPos[axis]] = Math.floor((tsize - css[ts[axis]]) * sr);
}
raf(function() {
DOM.css(mb.bars[axis].node, css);
});
};
/**
* Update all scrollbars
* @return {Void}
*/
proto.updateBars = function() {
each(this.bars, function(i, v) {
this.updateBar(i);
}, this);
};
/**
* Destroy instance
* @return {Void}
*/
proto.destroy = function() {
var mb = this,
o = mb.config,
ct = mb.container;
if (mb.initialised) {
// Remove the event listeners
DOM.off(ct, "mouseenter", mb.events.mouseenter);
DOM.off(win, "resize", mb.events.debounce);
// Remove the main classes from the container
DOM.classList.remove(ct, o.classes.visible);
DOM.classList.remove(ct, o.classes.container);
DOM.classList.remove(ct, o.classes.nav);
// Remove the tracks and / or buttons
each(mb.tracks, function(i, track) {
ct.removeChild(o.navButtons ? track.node.parentNode : track.node);
DOM.classList.remove(ct, "mb-scroll-" + i);
});
// Move the nodes back to their original container
while (mb.content.firstChild) {
ct.appendChild(mb.content.firstChild);
}
// Remove the content node
ct.removeChild(mb.content);
// Remove manual positioning
if (mb.manualPosition) {
ct.style.position = "";
}
// Clear node references
mb.bars = {
x: {},
y: {}
};
mb.tracks = {
x: {},
y: {}
};
mb.content = null;
if (mb.mutationObserver) {
mb.mutationObserver.disconnect();
mb.mutationObserver = false;
}
if (o.observableItems) {
if (mb.intersectionObserver) {
mb.intersectionObserver.disconnect();
mb.intersectionObserver = false;
}
each(mb.items, function(i, item) {
const node = item.node || item;
DOM.classList.remove(node, o.classes.item);
DOM.classList.remove(node, o.classes.itemVisible);
DOM.classList.remove(node, o.classes.itemPartial);
DOM.classList.remove(node, o.classes.itemHidden);
});
}
mb.initialised = false;
}
};
/* PRIVATE METHODS */
/**
* Scroll callback
* @param {Object} e Event interface
* @return {Void}
*/
proto._scroll = function(e) {
const data = this.getData(true);
if (data.scrollLeft > this.lastX) {
this.scrollDirection.x = 1;
} else if (data.scrollLeft < this.lastX) {
this.scrollDirection.x = -1;
}
if (data.scrollTop > this.lastY) {
this.scrollDirection.y = 1;
} else if (data.scrollTop < this.lastY) {
this.scrollDirection.y = -1;
}
this.updateBars();
this.config.onScroll.call(this, data);
this.lastX = data.scrollLeft;
this.lastY = data.scrollTop;
};
/**
* Mousewheel callback
* @param {Object} e Event interface
* @return {Void}
*/
proto._wheel = function(e) {
e.preventDefault();
this.scrollBy(e.deltaY * 100, "x");
};
/**
* Mouseenter callack
* @param {Object} e Event interface
* @return {Void}
*/
proto._mouseenter = function(e) {
this.updateBars();
};
/**
* Mousedown callack
* @param {Object} e Event interface
* @return {Void}
*/
proto._mousedown = function(e) {
e.preventDefault();
var mb = this,
o = mb.config,
type = o.barType === "progress" ? "tracks" : "bars",
axis = e.target === mb[type].x.node ? "x" : "y";
if (DOM.classList.contains(e.target, "mb-track")) {
axis = e.target === mb.tracks.x.node ? "x" : "y"
var track = mb.tracks[axis];
var ts = track[trackSize[axis]];
var offset = e[mAxis[axis]] - track[axis];
var ratio = offset / ts;
var scroll = ratio * (mb.content[scrollSize[axis]] - mb.rect[trackSize[axis]]);
return this.scrollTo(scroll, axis);
}
mb.down = true;
mb.currentAxis = axis;
// Lets do all the nasty reflow-triggering stuff now
// otherwise it'll be a shit-show during mousemove
mb.update();
// Keep the tracks visible during drag
DOM.classList.add(mb.container, o.classes.visible);
DOM.classList.add(mb.container, o.classes.scrolling + "-" + axis);
// Save data for use during mousemove
o.barType === "progress" ? (mb.origin = {
x: e.pageX - mb.tracks[axis].x,
y: e.pageY - mb.tracks[axis].y
}, mb._mousemove(e)) : mb.origin = {
x: e.pageX - mb.bars[axis].x,
y: e.pageY - mb.bars[axis].y
};
// Attach the mousemove and mouseup listeners now
// instead of permanently having them on
DOM.on(doc, "mousemove", mb.events.mousemove);
DOM.on(doc, "mouseup", mb.events.mouseup);
};
/**
* Mousemove callack
* @param {Object} e Event interface
* @return {Void}
*/
proto._mousemove = function(e) {
e.preventDefault();
var mb = this,
o = this.origin,
axis = this.currentAxis,
track = mb.tracks[axis],
ts = track[trackSize[axis]],
offset, ratio, scroll,
progress = mb.config.barType === "progress";
offset = progress ? e[mAxis[axis]] - track[axis] : e[mAxis[axis]] - o[axis] - track[axis];
ratio = offset / ts;
scroll = progress ? ratio * (mb.content[scrollSize[axis]] - mb.rect[trackSize[axis]]) : ratio * mb[scrollSize[axis]];
// Update scroll position
raf(function() {
mb.content[scrollPos[axis]] = scroll;
});
};
/**
* Mouseup callack
* @param {Object} e Event interface
* @return {Void}
*/
proto._mouseup = function(e) {
var mb = this,
o = mb.config,
ev = mb.events;
DOM.classList.toggle(mb.container, o.classes.visible, o.alwaysShowBars);
DOM.classList.remove(mb.container, o.classes.scrolling + "-" + mb.currentAxis);
if (!DOM.classList.contains(e.target, o.classes.bar)) {
DOM.classList.remove(mb.container, o.classes.hover + "-x");
DOM.classList.remove(mb.container, o.classes.hover + "-y");
}
mb.currentAxis = null;
mb.down = false;
DOM.off(doc, "mousemove", ev.mousemove);
DOM.off(doc, "mouseup", ev.mouseup);
};
root.MiniBar = MiniBar;
}(this));