UNPKG

angular-material-npfixed

Version:

The Angular Material project is an implementation of Material Design in Angular.js. This project provides a set of reusable, well-tested, and accessible Material Design UI components. Angular Material is supported internally at Google by the Angular.js, M

462 lines (403 loc) 13.8 kB
/** * @ngdoc module * @name material.core.ripple * @description * Ripple */ angular.module('material.core') .provider('$mdInkRipple', InkRippleProvider) .directive('mdInkRipple', InkRippleDirective) .directive('mdNoInk', attrNoDirective) .directive('mdNoBar', attrNoDirective) .directive('mdNoStretch', attrNoDirective); var DURATION = 450; /** * @ngdoc directive * @name mdInkRipple * @module material.core.ripple * * @description * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed. * * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple * * @usage * ### String values * <hljs lang="html"> * <ANY md-ink-ripple="#FF0000"> * Ripples in red * </ANY> * * <ANY md-ink-ripple="false"> * Not rippling * </ANY> * </hljs> * * ### Interpolated values * <hljs lang="html"> * <ANY md-ink-ripple="{{ randomColor() }}"> * Ripples with the return value of 'randomColor' function * </ANY> * * <ANY md-ink-ripple="{{ canRipple() }}"> * Ripples if 'canRipple' function return value is not 'false' or '0' * </ANY> * </hljs> */ function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) { return { controller: angular.noop, link: function (scope, element, attr) { attr.hasOwnProperty('mdInkRippleCheckbox') ? $mdCheckboxInkRipple.attach(scope, element) : $mdButtonInkRipple.attach(scope, element); } }; } /** * @ngdoc service * @name $mdInkRipple * @module material.core.ripple * * @description * `$mdInkRipple` is a service for adding ripples to any element * * @usage * <hljs lang="js"> * app.factory('$myElementInkRipple', function($mdInkRipple) { * return { * attach: function (scope, element, options) { * return $mdInkRipple.attach(scope, element, angular.extend({ * center: false, * dimBackground: true * }, options)); * } * }; * }); * * app.controller('myController', function ($scope, $element, $myElementInkRipple) { * $scope.onClick = function (ev) { * $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true }); * } * }); * </hljs> * * ### Disabling ripples globally * If you want to disable ink ripples globally, for all components, you can call the * `disableInkRipple` method in your app's config. * * <hljs lang="js"> * app.config(function ($mdInkRippleProvider) { * $mdInkRippleProvider.disableInkRipple(); * }); */ function InkRippleProvider () { var isDisabledGlobally = false; return { disableInkRipple: disableInkRipple, $get: function($injector) { return { attach: attach }; /** * @ngdoc method * @name $mdInkRipple#attach * * @description * Attaching given scope, element and options to inkRipple controller * * @param {object=} scope Scope within the current context * @param {object=} element The element the ripple effect should be applied to * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration * * `center` - Whether the ripple should start from the center of the container element * * `dimBackground` - Whether the background should be dimmed with the ripple color * * `colorElement` - The element the ripple should take its color from, defined by css property `color` * * `fitRipple` - Whether the ripple should fill the element */ function attach (scope, element, options) { if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop; return $injector.instantiate(InkRippleCtrl, { $scope: scope, $element: element, rippleOptions: options }); } } }; /** * @ngdoc method * @name $mdInkRipple#disableInkRipple * * @description * A config-time method that, when called, disables ripples globally. */ function disableInkRipple () { isDisabledGlobally = true; } } /** * Controller used by the ripple service in order to apply ripples * @ngInject */ function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) { this.$window = $window; this.$timeout = $timeout; this.$mdUtil = $mdUtil; this.$mdColorUtil = $mdColorUtil; this.$scope = $scope; this.$element = $element; this.options = rippleOptions; this.mousedown = false; this.ripples = []; this.timeout = null; // Stores a reference to the most-recent ripple timeout this.lastRipple = null; $mdUtil.valueOnUse(this, 'container', this.createContainer); this.$element.addClass('md-ink-ripple'); // attach method for unit tests ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple); ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color); this.bindEvents(); } /** * Either remove or unlock any remaining ripples when the user mouses off of the element (either by * mouseup or mouseleave event) */ function autoCleanup (self, cleanupFn) { if ( self.mousedown || self.lastRipple ) { self.mousedown = false; self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false); } } /** * Returns the color that the ripple should be (either based on CSS or hard-coded) * @returns {string} */ InkRippleCtrl.prototype.color = function (value) { var self = this; // If assigning a color value, apply it to background and the ripple color if (angular.isDefined(value)) { self._color = self._parseColor(value); } // If color lookup, use assigned, defined, or inherited return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() ); /** * Finds the color element and returns its text color for use as default ripple color * @returns {string} */ function getElementColor () { var items = self.options && self.options.colorElement ? self.options.colorElement : []; var elem = items.length ? items[ 0 ] : self.$element[ 0 ]; return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)'; } }; /** * Updating the ripple colors based on the current inkRipple value * or the element's computed style color */ InkRippleCtrl.prototype.calculateColor = function () { return this.color(); }; /** * Takes a string color and converts it to RGBA format * @param color {string} * @param [multiplier] {int} * @returns {string} */ InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) { multiplier = multiplier || 1; var colorUtil = this.$mdColorUtil; if (!color) return; if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')'); if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color); if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color); }; /** * Binds events to the root element for */ InkRippleCtrl.prototype.bindEvents = function () { this.$element.on('mousedown', angular.bind(this, this.handleMousedown)); this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup)); this.$element.on('mouseleave', angular.bind(this, this.handleMouseup)); this.$element.on('touchmove', angular.bind(this, this.handleTouchmove)); }; /** * Create a new ripple on every mousedown event from the root element * @param event {MouseEvent} */ InkRippleCtrl.prototype.handleMousedown = function (event) { if ( this.mousedown ) return; // When jQuery is loaded, we have to get the original event if (event.hasOwnProperty('originalEvent')) event = event.originalEvent; this.mousedown = true; if (this.options.center) { this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2); } else { // We need to calculate the relative coordinates if the target is a sublayer of the ripple element if (event.srcElement !== this.$element[0]) { var layerRect = this.$element[0].getBoundingClientRect(); var layerX = event.clientX - layerRect.left; var layerY = event.clientY - layerRect.top; this.createRipple(layerX, layerY); } else { this.createRipple(event.offsetX, event.offsetY); } } }; /** * Either remove or unlock any remaining ripples when the user mouses off of the element (either by * mouseup, touchend or mouseleave event) */ InkRippleCtrl.prototype.handleMouseup = function () { autoCleanup(this, this.clearRipples); }; /** * Either remove or unlock any remaining ripples when the user mouses off of the element (by * touchmove) */ InkRippleCtrl.prototype.handleTouchmove = function () { autoCleanup(this, this.deleteRipples); }; /** * Cycles through all ripples and attempts to remove them. */ InkRippleCtrl.prototype.deleteRipples = function () { for (var i = 0; i < this.ripples.length; i++) { this.ripples[ i ].remove(); } }; /** * Cycles through all ripples and attempts to remove them with fade. * Depending on logic within `fadeInComplete`, some removals will be postponed. */ InkRippleCtrl.prototype.clearRipples = function () { for (var i = 0; i < this.ripples.length; i++) { this.fadeInComplete(this.ripples[ i ]); } }; /** * Creates the ripple container element * @returns {*} */ InkRippleCtrl.prototype.createContainer = function () { var container = angular.element('<div class="md-ripple-container"></div>'); this.$element.append(container); return container; }; InkRippleCtrl.prototype.clearTimeout = function () { if (this.timeout) { this.$timeout.cancel(this.timeout); this.timeout = null; } }; InkRippleCtrl.prototype.isRippleAllowed = function () { var element = this.$element[0]; do { if (!element.tagName || element.tagName === 'BODY') break; if (element && angular.isFunction(element.hasAttribute)) { if (element.hasAttribute('disabled')) return false; if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false; } } while (element = element.parentNode); return true; }; /** * The attribute `md-ink-ripple` may be a static or interpolated * color value OR a boolean indicator (used to disable ripples) */ InkRippleCtrl.prototype.inkRipple = function () { return this.$element.attr('md-ink-ripple'); }; /** * Creates a new ripple and adds it to the container. Also tracks ripple in `this.ripples`. * @param left * @param top */ InkRippleCtrl.prototype.createRipple = function (left, top) { if (!this.isRippleAllowed()) return; var ctrl = this; var colorUtil = ctrl.$mdColorUtil; var ripple = angular.element('<div class="md-ripple"></div>'); var width = this.$element.prop('clientWidth'); var height = this.$element.prop('clientHeight'); var x = Math.max(Math.abs(width - left), left) * 2; var y = Math.max(Math.abs(height - top), top) * 2; var size = getSize(this.options.fitRipple, x, y); var color = this.calculateColor(); ripple.css({ left: left + 'px', top: top + 'px', background: 'black', width: size + 'px', height: size + 'px', backgroundColor: colorUtil.rgbaToRgb(color), borderColor: colorUtil.rgbaToRgb(color) }); this.lastRipple = ripple; // we only want one timeout to be running at a time this.clearTimeout(); this.timeout = this.$timeout(function () { ctrl.clearTimeout(); if (!ctrl.mousedown) ctrl.fadeInComplete(ripple); }, DURATION * 0.35, false); if (this.options.dimBackground) this.container.css({ backgroundColor: color }); this.container.append(ripple); this.ripples.push(ripple); ripple.addClass('md-ripple-placed'); this.$mdUtil.nextTick(function () { ripple.addClass('md-ripple-scaled md-ripple-active'); ctrl.$timeout(function () { ctrl.clearRipples(); }, DURATION, false); }, false); function getSize (fit, x, y) { return fit ? Math.max(x, y) : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); } }; /** * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup * @param ripple */ InkRippleCtrl.prototype.fadeInComplete = function (ripple) { if (this.lastRipple === ripple) { if (!this.timeout && !this.mousedown) { this.removeRipple(ripple); } } else { this.removeRipple(ripple); } }; /** * Kicks off the animation for removing a ripple * @param ripple {Element} */ InkRippleCtrl.prototype.removeRipple = function (ripple) { var ctrl = this; var index = this.ripples.indexOf(ripple); if (index < 0) return; this.ripples.splice(this.ripples.indexOf(ripple), 1); ripple.removeClass('md-ripple-active'); ripple.addClass('md-ripple-remove'); if (this.ripples.length === 0) this.container.css({ backgroundColor: '' }); // use a 2-second timeout in order to allow for the animation to finish // we don't actually care how long the animation takes this.$timeout(function () { ctrl.fadeOutComplete(ripple); }, DURATION, false); }; /** * Removes the provided ripple from the DOM * @param ripple */ InkRippleCtrl.prototype.fadeOutComplete = function (ripple) { ripple.remove(); this.lastRipple = null; }; /** * Used to create an empty directive. This is used to track flag-directives whose children may have * functionality based on them. * * Example: `md-no-ink` will potentially be used by all child directives. */ function attrNoDirective () { return { controller: angular.noop }; }