UNPKG

este-library-oldschool

Version:

Library for github.com/steida/este.git

579 lines (525 loc) 17.4 kB
// 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); };