este-library-oldschool
Version:
Library for github.com/steida/este.git
579 lines (525 loc) • 17.4 kB
JavaScript
// Generated by github.com/steida/coffee2closure 900.1.18
/**
@fileoverview OS X Mountain Lion style scrolling for web.
Features:
- designed with obsession for UX details
- uses hidden native scrollbars to preserve native scroll momentum
- very customizable
- accessible: ARIA, keyboard
- CSS3 animations
- size of container can be dynamically adjusted and scrollbars will adapt
- declarativeness, lazy instantiation on mouseenter
- tested on: Chrome, Safari, Firefox, Internet Explorer 8+ (Mac, Win, iOS)
TODO:
- fix Mac Firefox on scroll overlay workaround.
- fix IE jump when scrollbar is clicked or on mouseenter at first shown.
- once IE<10 die, implement showTimeout and hideTimeout as CSS transition.
@see /demos/ui/scrollbar.html
*/
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.superClass_ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
goog.provide('este.ui.Scrollbar');
goog.provide('este.ui.Scrollbar.lazyCreate');
goog.require('este.dom');
goog.require('este.events.Delegation');
goog.require('este.ui.Component');
goog.require('este.ui.InvisibleOverlay');
goog.require('este.ui.userAgent');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.device');
goog.require('goog.memoize');
goog.require('goog.style');
goog.require('goog.ui.Slider');
/**
@constructor
@extends {este.ui.Component}
*/
este.ui.Scrollbar = function() {
este.ui.Scrollbar.superClass_.constructor.call(this);
}
extend(este.ui.Scrollbar, superClass);
/**
@enum {string}
*/
este.ui.Scrollbar.ClassName = {
ELEMENT: 'este-scrollbar',
CONTENT: 'este-scrollbar-content',
SHOWN: 'este-scrollbar-shown',
SHOWN_BG: 'este-scrollbar-shown-bg'
};
/**
Just create <div class="whatever este-scrollbar">... element in DOM, and
este.ui.Scrollbar will be created lazily on mouseenter.
*/
este.ui.Scrollbar.lazyCreate = function() {
var delegation;
if (!goog.labs.userAgent.device.isDesktop()) {
return;
}
delegation = new este.events.Delegation(document.documentElement, 'mouseover');
delegation.targetFilter = function(el) {
return goog.dom.classlist.contains(el, este.ui.Scrollbar.ClassName.ELEMENT);
};
return goog.events.listen(delegation, 'mouseover', function(e) {
var scrollbar;
if (e.target.hasAttribute('e-ui-scrollbar')) {
return;
}
scrollbar = new este.ui.Scrollbar;
scrollbar.decorate(e.target);
return scrollbar.showScrollbarsTemporarily();
});
};
/**
@return {number}
*/
este.ui.Scrollbar.getNativeScrollbarWidth = goog.memoize(goog.style.getScrollbarWidth);
/**
@type {number}
*/
este.ui.Scrollbar.prototype.hideTimeout = 750;
/**
@type {number}
*/
este.ui.Scrollbar.prototype.showTimeout = 75;
/**
@type {number}
*/
este.ui.Scrollbar.prototype.scrollTimeout = 250;
/**
Like Mac OS X.
@type {boolean}
*/
este.ui.Scrollbar.prototype.showScrollbarsOnlyOnScroll = false;
/**
@type {?number}
@protected
*/
este.ui.Scrollbar.prototype.hideTimer = null;
/**
@type {?number}
@protected
*/
este.ui.Scrollbar.prototype.showTimer = null;
/**
@type {?number}
@protected
*/
este.ui.Scrollbar.prototype.overlayScrollTimer = null;
/**
@type {?number}
@protected
*/
este.ui.Scrollbar.prototype.contentScrollTimer = null;
/**
@type {Element}
@protected
*/
este.ui.Scrollbar.prototype.content = null;
/**
Overlay temporarily shown on mousewheel over custom scrollbars.
@type {Element}
@protected
*/
este.ui.Scrollbar.prototype.overlay = null;
/**
@type {goog.ui.Slider}
@protected
*/
este.ui.Scrollbar.prototype.verticalSlider = null;
/**
@type {goog.ui.Slider}
@protected
*/
este.ui.Scrollbar.prototype.horizontalSlider = null;
/**
@type {boolean}
@protected
*/
este.ui.Scrollbar.prototype.dragging = false;
/**
@type {number}
@protected
*/
este.ui.Scrollbar.prototype.mouseClientX = 0;
/**
@type {number}
@protected
*/
este.ui.Scrollbar.prototype.mouseClientY = 0;
/**
Show scrollbars temporarily.
*/
este.ui.Scrollbar.prototype.showScrollbarsTemporarily = function() {
if (!goog.labs.userAgent.device.isDesktop()) {
return;
}
if (this.dragging) {
return;
}
this.showScrollbars();
return this.hideTimer = setTimeout(goog.bind(this.hideScrollbars, this), this.hideTimeout);
};
/**
Update scrollbars position and size.
*/
este.ui.Scrollbar.prototype.update = function() {
if (!goog.labs.userAgent.device.isDesktop()) {
return;
}
this.updateVerticalScrollBar();
return this.updateHorizontalScrollBar();
};
/**
@protected
*/
este.ui.Scrollbar.prototype.updateVerticalScrollBar = function() {
var maximum, ratio;
ratio = this.content.clientHeight / this.content.scrollHeight;
goog.style.setElementShown(this.verticalSlider.getElement(), ratio < 1);
if (ratio === 1) {
return;
}
goog.style.setHeight(this.verticalSlider.getValueThumb(), this.content.clientHeight * ratio);
maximum = this.content.scrollHeight - this.content.clientHeight;
this.verticalSlider.setMaximum(maximum);
return this.verticalSlider.setValue(Math.max(0, maximum - this.content.scrollTop));
};
/**
@protected
*/
este.ui.Scrollbar.prototype.updateHorizontalScrollBar = function() {
var ratio;
ratio = this.content.clientWidth / this.content.scrollWidth;
goog.style.setElementShown(this.horizontalSlider.getElement(), ratio < 1);
if (ratio === 1) {
return;
}
goog.style.setWidth(this.horizontalSlider.getValueThumb(), this.content.clientWidth * ratio);
this.horizontalSlider.setMaximum(this.content.scrollWidth - this.content.clientWidth);
return this.horizontalSlider.setValue(this.content.scrollLeft);
};
/**
@override
*/
este.ui.Scrollbar.prototype.createDom = function() {
este.ui.Scrollbar.superClass_.createDom.call(this);
this.setElementInternal(this.dom_.createDom('div', este.ui.Scrollbar.ClassName.ELEMENT, this.dom_.createDom('div', este.ui.Scrollbar.ClassName.CONTENT)));
this.decorateInternal(this.getElement());
};
/**
@override
*/
este.ui.Scrollbar.prototype.decorateInternal = function(element) {
este.ui.Scrollbar.superClass_.decorateInternal.call(this, element);
element.setAttribute('e-ui-scrollbar', true);
goog.dom.classlist.add(this.getElement(), este.ui.Scrollbar.ClassName.ELEMENT);
this.content = this.getElementByClass(este.ui.Scrollbar.ClassName.CONTENT);
this.content.style.overflow = 'scroll';
if (!goog.labs.userAgent.device.isDesktop()) {
return;
}
this.setContentRightAndBottomStyleToHideNativeScrollbars();
this.createSliders();
};
/**
@protected
*/
este.ui.Scrollbar.prototype.setContentRightAndBottomStyleToHideNativeScrollbars = function() {
var offset;
offset = "-" + (este.ui.Scrollbar.getNativeScrollbarWidth()) + "px";
this.content.style.right = offset;
return this.content.style.bottom = offset;
};
/**
@protected
*/
este.ui.Scrollbar.prototype.createSliders = function() {
this.verticalSlider = new goog.ui.Slider;
this.verticalSlider.setOrientation(goog.ui.Slider.Orientation.VERTICAL);
this.verticalSlider.setMoveToPointEnabled(true);
this.verticalSlider.setHandleMouseWheel(false);
this.verticalSlider.setMinimum(0);
this.horizontalSlider = new goog.ui.Slider;
this.horizontalSlider.setOrientation(goog.ui.Slider.Orientation.HORIZONTAL);
this.horizontalSlider.setMoveToPointEnabled(true);
this.horizontalSlider.setHandleMouseWheel(false);
this.horizontalSlider.setMinimum(0);
this.addChild(this.verticalSlider, true);
this.addChild(this.horizontalSlider, true);
};
/**
@override
*/
este.ui.Scrollbar.prototype.enterDocument = function() {
var isIeLess9, isMacFirefox, overlayScrollTrickDoesNotWork;
este.ui.Scrollbar.superClass_.enterDocument.call(this);
if (!goog.labs.userAgent.device.isDesktop()) {
return;
}
this.update();
this.on(this.getElement(), ['mouseover', 'mouseout'], this.onElementMouseOverOut);
this.on(this.content, 'scroll', this.onContentScroll);
this.on(this.verticalSlider, 'change', this.onVerticalSliderChange);
this.on(this.horizontalSlider, 'change', this.onHorizontalSliderChange);
this.on(this.verticalSlider, goog.ui.SliderBase.EventType.DRAG_VALUE_START, this.onDragStart);
this.on(this.verticalSlider, goog.ui.SliderBase.EventType.DRAG_VALUE_END, this.onDragEnd);
this.on(this.horizontalSlider, goog.ui.SliderBase.EventType.DRAG_VALUE_START, this.onDragStart);
this.on(this.horizontalSlider, goog.ui.SliderBase.EventType.DRAG_VALUE_END, this.onDragEnd);
this.on('.goog-slider-vertical', 'mouseover', this.onGoogSliderMouseEnter);
this.on('.goog-slider-vertical', 'mouseout', this.onGoogSliderMouseLeave);
this.on('.goog-slider-horizontal', 'mouseover', this.onGoogSliderMouseEnter);
this.on('.goog-slider-horizontal', 'mouseout', this.onGoogSliderMouseLeave);
isMacFirefox = goog.labs.userAgent.platform.isMacintosh() && goog.labs.userAgent.browser.isFirefox();
isIeLess9 = goog.labs.userAgent.browser.isIE() && !goog.labs.userAgent.browser.isVersionOrHigher(9);
overlayScrollTrickDoesNotWork = isMacFirefox || isIeLess9;
if (!overlayScrollTrickDoesNotWork) {
this.on('.goog-slider-vertical', 'mousewheel', this.onSliderMouseWheel);
this.on('.goog-slider-horizontal', 'mousewheel', this.onSliderMouseWheel);
}
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onElementMouseOverOut = function(e) {
if (este.dom.isMouseHoverEventWithinElement(e, this.getElement())) {
return;
}
if (this.overlay) {
return;
}
switch (e.type) {
case 'mouseover':
if (this.showScrollbarsOnlyOnScroll) {
return;
}
this.showScrollbarsTemporarily();
break;
case 'mouseout':
this.hideScrollbars();
}
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onContentScroll = function(e) {
if (!this.isAnyScrollbarBackgroundShown()) {
this.showScrollbarsTemporarily();
}
if (this.dragging || this.verticalSlider.isAnimating() || this.horizontalSlider.isAnimating()) {
return;
}
this.off(this.verticalSlider, 'change', this.onVerticalSliderChange);
this.off(this.horizontalSlider, 'change', this.onHorizontalSliderChange);
clearTimeout(this.contentScrollTimer);
this.contentScrollTimer = setTimeout((function(_this) {
return function() {
_this.on(_this.verticalSlider, 'change', _this.onVerticalSliderChange);
return _this.on(_this.horizontalSlider, 'change', _this.onHorizontalSliderChange);
};
})(this), this.scrollTimeout);
return this.update();
};
/**
@protected
@return {boolean}
*/
este.ui.Scrollbar.prototype.isAnyScrollbarBackgroundShown = function() {
return this.isVerticalScrollbarBackgroundShown() || this.isHorizontalScrollbarBackgroundShown();
};
/**
@protected
@return {boolean}
*/
este.ui.Scrollbar.prototype.isVerticalScrollbarBackgroundShown = function() {
return goog.dom.classlist.contains(this.verticalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN_BG);
};
/**
@protected
@return {boolean}
*/
este.ui.Scrollbar.prototype.isHorizontalScrollbarBackgroundShown = function() {
return goog.dom.classlist.contains(this.horizontalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN_BG);
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onGoogSliderMouseEnter = function(e) {
if (this.overlay) {
return;
}
clearTimeout(this.showTimer);
return this.showTimer = setTimeout((function(_this) {
return function() {
var target;
_this.showScrollbars();
target = /** @type {Element} */(e.target);
_this.setScrollbarsClassesEnabled(true, goog.dom.classlist.contains(target, 'goog-slider-vertical'), goog.dom.classlist.contains(target, 'goog-slider-horizontal'));
_this.on('.goog-slider-vertical', 'mouseout', _this.onGoogSliderActiveMouseLeave);
return _this.on('.goog-slider-horizontal', 'mouseout', _this.onGoogSliderActiveMouseLeave);
};
})(this), this.showTimeout);
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onGoogSliderActiveMouseLeave = function(e) {
if (this.overlay) {
return;
}
this.off('.goog-slider-vertical', 'mouseout', this.onGoogSliderActiveMouseLeave);
this.off('.goog-slider-horizontal', 'mouseout', this.onGoogSliderActiveMouseLeave);
return this.hideScrollbars();
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onGoogSliderMouseLeave = function(e) {
return clearTimeout(this.showTimer);
};
/**
@param {goog.events.Event} e
@protected
*/
este.ui.Scrollbar.prototype.onVerticalSliderChange = function(e) {
var scrollTop;
scrollTop = this.verticalSlider.getMaximum() - this.verticalSlider.getValue();
return this.content.scrollTop = scrollTop;
};
/**
@param {goog.events.Event} e
@protected
*/
este.ui.Scrollbar.prototype.onHorizontalSliderChange = function(e) {
return this.content.scrollLeft = this.horizontalSlider.getValue();
};
/**
@param {goog.events.Event} e
@protected
*/
este.ui.Scrollbar.prototype.onDragStart = function(e) {
this.dragging = true;
return this.setMouseTrackingEnabled(true);
};
/**
@param {goog.events.Event} e
@protected
*/
este.ui.Scrollbar.prototype.onDragEnd = function(e) {
this.dragging = false;
this.setMouseTrackingEnabled(false);
return this.hideScrollbarsIfMouseIsOutOfThem();
};
/**
@protected
*/
este.ui.Scrollbar.prototype.showScrollbars = function() {
if (this.dragging) {
return;
}
clearTimeout(this.hideTimer);
return this.setScrollbarsClassesEnabled(true, this.isVerticalScrollbarBackgroundShown(), this.isHorizontalScrollbarBackgroundShown());
};
/**
@protected
*/
este.ui.Scrollbar.prototype.hideScrollbars = function() {
if (this.dragging) {
return;
}
clearTimeout(this.hideTimer);
return this.setScrollbarsClassesEnabled(false);
};
/**
@param {boolean} enable
@param {boolean=} verticalBg
@param {boolean=} horizontalBg
@protected
*/
este.ui.Scrollbar.prototype.setScrollbarsClassesEnabled = function(enable, verticalBg, horizontalBg) {
goog.dom.classlist.enable(this.verticalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN, enable);
goog.dom.classlist.enable(this.horizontalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN, enable);
goog.dom.classlist.enable(this.verticalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN_BG, verticalBg || false);
return goog.dom.classlist.enable(this.horizontalSlider.getElement(), este.ui.Scrollbar.ClassName.SHOWN_BG, horizontalBg || false);
};
/**
@param {boolean} enable
@protected
*/
este.ui.Scrollbar.prototype.setMouseTrackingEnabled = function(enable) {
if (enable) {
return this.on(document, 'mousemove', this.onDocumentMouseMove);
} else {
return this.off(document, 'mousemove', this.onDocumentMouseMove);
}
};
/**
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onDocumentMouseMove = function(e) {
this.mouseClientX = e.clientX;
return this.mouseClientY = e.clientY;
};
/**
@protected
*/
este.ui.Scrollbar.prototype.hideScrollbarsIfMouseIsOutOfThem = function() {
var el, isElementWithinScrollbars;
el = this.dom_.getDocument().elementFromPoint(this.mouseClientX, this.mouseClientY);
isElementWithinScrollbars = goog.dom.contains(this.verticalSlider.getElement(), el) || goog.dom.contains(this.horizontalSlider.getElement(), el);
if (isElementWithinScrollbars) {
return;
}
return this.hideScrollbars();
};
/**
This handler ensures native mousewheel scroll behavior even on custom
scrollbars. It creates invisible overlay over scrollable content on first
mousewheel event and hide it after scroll end.
@param {goog.events.BrowserEvent} e
@protected
*/
este.ui.Scrollbar.prototype.onSliderMouseWheel = function(e) {
if (!this.overlay) {
this.createOverlay();
this.content.appendChild(this.overlay);
this.setMouseTrackingEnabled(true);
this.on(this.content, 'scroll', this.onContentScrollWhenOverlayIsShown);
this.onContentScrollWhenOverlayIsShown();
}
return e.preventDefault();
};
/**
@protected
*/
este.ui.Scrollbar.prototype.createOverlay = function() {
return this.overlay = this.dom_.createDom('div', {
style: "position: absolute; z-index: 2147483647; left: 0; top: 0; width: " + this.content.scrollWidth + "px; height: " + this.content.scrollHeight + "px; background-color: #000; opacity: 0;"
});
};
/**
@param {goog.events.BrowserEvent=} e
@protected
*/
este.ui.Scrollbar.prototype.onContentScrollWhenOverlayIsShown = function(e) {
clearTimeout(this.overlayScrollTimer);
return this.overlayScrollTimer = setTimeout((function(_this) {
return function() {
if (!_this.overlay) {
return;
}
_this.content.removeChild(_this.overlay);
_this.overlay = null;
_this.setMouseTrackingEnabled(false);
_this.off(_this.content, 'scroll', _this.onContentScrollWhenOverlayIsShown);
return _this.hideScrollbarsIfMouseIsOutOfThem();
};
})(this), this.scrollTimeout);
};