',
diff --git a/src/components/radioButton/radioButton.js b/src/components/radioButton/radioButton.js
index b59ba08b7f0..2c5ae810ed6 100644
--- a/src/components/radioButton/radioButton.js
+++ b/src/components/radioButton/radioButton.js
@@ -197,7 +197,7 @@ function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
restrict: 'E',
require: '^mdRadioGroup',
transclude: true,
- template: '
' +
+ template: '
' +
diff --git a/src/components/tabs/js/tabItemDirective.js b/src/components/tabs/js/tabItemDirective.js
index 36a0d3cdae0..361296f9e80 100644
--- a/src/components/tabs/js/tabItemDirective.js
+++ b/src/components/tabs/js/tabItemDirective.js
@@ -94,7 +94,7 @@ function MdTabDirective($mdInkRipple, $compile, $mdAria, $mdUtil, $mdConstant) {
transcludeTabContent();
configureAria();
- var detachRippleFn = $mdInkRipple.attachButtonBehavior(element);
+ var detachRippleFn = $mdInkRipple.attachButtonBehavior(scope, element);
tabsCtrl.add(tabItemCtrl);
scope.$on('$destroy', function() {
detachRippleFn();
diff --git a/src/core/services/ripple/ripple.js b/src/core/services/ripple/ripple.js
index a0990ecabc8..32d6905a10d 100644
--- a/src/core/services/ripple/ripple.js
+++ b/src/core/services/ripple/ripple.js
@@ -8,15 +8,18 @@ angular.module('material.core')
.directive('mdNoBar', attrNoDirective())
.directive('mdNoStretch', attrNoDirective());
-function InkRippleDirective($mdInkRipple) {
- return function(scope, element, attr) {
- if (attr.mdInkRipple == 'checkbox') {
- $mdInkRipple.attachCheckboxBehavior(element);
- } else {
- $mdInkRipple.attachButtonBehavior(element);
- }
- };
-}
+ function InkRippleDirective($mdInkRipple) {
+ return {
+ controller: angular.noop,
+ link: function (scope, element, attr) {
+ if (attr.hasOwnProperty('mdInkRippleCheckbox')) {
+ $mdInkRipple.attachCheckboxBehavior(scope, element);
+ } else {
+ $mdInkRipple.attachButtonBehavior(scope, element);
+ }
+ }
+ };
+ }
function InkRippleService($window, $timeout) {
@@ -26,27 +29,34 @@ function InkRippleService($window, $timeout) {
attach: attach
};
- function attachButtonBehavior(element) {
- return attach(element, {
+ function attachButtonBehavior(scope, element) {
+ return attach(scope, element, {
center: element.hasClass('md-fab'),
dimBackground: true
});
}
- function attachCheckboxBehavior(element) {
- return attach(element, {
+ function attachCheckboxBehavior(scope, element) {
+ return attach(scope, element, {
center: true,
dimBackground: false
});
}
- function attach(element, options) {
-
+ function attach(scope, element, options) {
if (element.controller('mdNoInk')) return angular.noop;
- var rippleContainer, rippleEl,
+ var rippleContainer,
+ controller = element.controller('mdInkRipple') || {},
+ counter = 0,
+ ripples = [],
+ states = [],
+ isActiveExpr = element.attr('md-active'),
+ isActive = false,
+ isHeld = false,
node = element[0],
hammertime = new Hammer(node),
+ color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(node).color || 'rgb(0, 0, 0)'),
contentParent = element.controller('mdContent');
options = angular.extend({
@@ -60,107 +70,215 @@ function InkRippleService($window, $timeout) {
options.mousedown && hammertime.on('hammer.input', onInput);
+ controller.createRipple = createRipple;
+
+ if (isActiveExpr) {
+ scope.$watch(
+ function () {
+ return scope.$eval(isActiveExpr);
+ },
+ function (newValue) {
+ isActive = newValue;
+ if (isActive) {
+ if (ripples.length === 0) {
+ createRipple(0, 0);
+ }
+ }
+ angular.forEach(ripples, updateElement);
+ }
+ );
+ }
+
// Publish self-detach method if desired...
return function detach() {
hammertime.destroy();
rippleContainer && rippleContainer.remove();
};
- function rippleIsAllowed() {
- var parent;
- return !element[0].hasAttribute('disabled') &&
- !((parent = element[0].parentNode) && parent.hasAttribute('disabled'));
- }
-
- function removeElement(element, wait) {
- $timeout(function () {
- element.remove();
- }, wait, false);
- }
+ function parseColor(color) {
+ if (!color) return;
+ if (color.indexOf('rgba') === 0) return color;
+ if (color.indexOf('rgb') === 0) return rgbToRGBA(color);
+ if (color.indexOf('#') === 0) return hexToRGBA(color);
- function createRipple(left, top, positionsAreAbsolute) {
+ /**
+ *
+ */
+ function hexToRGBA(color) {
+ var hex = color.charAt(0) === '#' ? color.substr(1) : color,
+ dig = hex.length / 3,
+ red = hex.substr(0, dig),
+ grn = hex.substr(dig, dig),
+ blu = hex.substr(dig * 2);
+ if (dig === 1) {
+ red += red;
+ grn += grn;
+ blu += blu;
+ }
+ return 'rgba(' + parseInt(red, 16) + ',' + parseInt(grn, 16) + ',' + parseInt(blu, 16) + ',0.1)';
+ }
- var rippleEl = angular.element('
');
+ /**
+ *
+ */
+ function rgbToRGBA(color) {
+ return color.replace(')', ', 0.1)').replace('(', 'a(')
+ }
- if (!rippleContainer) {
- rippleContainer = angular.element('
');
- element.append(rippleContainer);
}
- rippleContainer.append(rippleEl);
-
- var containerWidth = rippleContainer.prop('offsetWidth'),
- containerHeight = rippleContainer.prop('offsetHeight'),
- multiplier = element.hasClass('md-fab') ? 1.1 : 0.8,
- rippleWidth = Math.max(containerWidth, containerHeight) * multiplier;
- if (contentParent) {
- top += contentParent.$element.prop('scrollTop');
+ function removeElement(elem, wait) {
+ ripples.splice(ripples.indexOf(elem), 1);
+ if (ripples.length === 0) {
+ rippleContainer && rippleContainer.css({ backgroundColor: '' });
}
+ $timeout(function () { elem.remove(); }, wait, false);
+ }
- var css = {
- backgroundColor: $window.getComputedStyle(rippleEl[0]).color || $window.getComputedStyle(node).color,
- width: rippleWidth + 'px',
- height: rippleWidth + 'px',
- marginLeft: (rippleWidth * -0.5) + 'px',
- marginTop: (rippleWidth * -0.5) + 'px'
- };
-
- if (options.center) {
- css.left = '50%';
- css.top = '50%';
- } else if (positionsAreAbsolute) {
- var elementRect = node.getBoundingClientRect();
- left -= elementRect.left;
- top -= elementRect.top;
- css.left = Math.round(left / containerWidth * 100) + '%';
- css.top = Math.round(top / containerHeight * 100) + '%';
+ function updateElement(elem) {
+ var index = ripples.indexOf(elem),
+ state = states[index],
+ elemIsActive = ripples.length > 1 ? false : isActive,
+ elemIsHeld = ripples.length > 1 ? false : isHeld;
+ if (elemIsActive || state.animating || elemIsHeld) {
+ elem.addClass('md-ripple-visible');
+ } else {
+ elem.removeClass('md-ripple-visible');
+ removeElement(elem, 650);
}
+ }
+
+ /**
+ *
+ * @returns {*}
+ */
+ function createRipple(left, top) {
- rippleEl.css(css);
+ var container = getRippleContainer(),
+ size = getRippleSize(),
+ css = getRippleCss(size, left, top),
+ elem = getRippleElement(css),
+ index = ripples.indexOf(elem),
+ state = states[index];
+
+ state.animating = true;
- //-- Use minimum timeout to trigger CSS animation
$timeout(function () {
if (options.dimBackground) {
- rippleContainer.addClass('md-ripple-full md-ripple-visible');
- rippleContainer.css({ backgroundColor: css.backgroundColor.replace(')', ', 0.1').replace('(', 'a(') });
+ container.css({ backgroundColor: color });
}
- rippleEl.addClass('md-ripple-placed md-ripple-visible md-ripple-scaled md-ripple-full');
- rippleEl.css({ left: '50%', top: '50%' });
+ elem.addClass('md-ripple-placed md-ripple-scaled').css({ left: '50%', top: '50%' });
+ updateElement(elem);
$timeout(function () {
- if (rippleEl) {
- rippleEl.removeClass('md-ripple-full');
- if (!rippleEl.hasClass('md-ripple-visible')) {
- removeElement(rippleEl, 650);
- rippleEl = null;
- }
- }
- rippleEl && rippleEl.removeClass('md-ripple-full');
- if (rippleContainer && options.dimBackground) {
- rippleContainer.removeClass('md-ripple-full');
- if (!rippleContainer.hasClass('md-ripple-visible')) rippleContainer.css({ backgroundColor: '' });
- }
+ state.animating = false;
+ updateElement(elem);
}, 225, false);
}, 0, false);
- return rippleEl;
- }
- function onInput(ev) {
- if (ev.eventType === Hammer.INPUT_START && ev.isFirst && rippleIsAllowed()) {
- rippleEl = createRipple(ev.center.x, ev.center.y, true);
- } else if (ev.eventType === Hammer.INPUT_END && ev.isFinal) {
- if (rippleEl) {
- rippleEl.removeClass('md-ripple-visible');
- removeElement(rippleEl, 650);
- rippleEl = null;
+ return elem;
+
+ /**
+ *
+ * @returns {*}
+ */
+ function getRippleElement(css) {
+ var elem = angular.element('
');
+ ripples.unshift(elem);
+ states.unshift({ animating: true });
+ container.append(elem);
+ css && elem.css(css);
+ return elem;
}
- if (rippleContainer && options.dimBackground) {
- rippleContainer.removeClass('md-ripple-visible');
- if (!rippleContainer.hasClass('md-ripple-full')) rippleContainer.css({ backgroundColor: '' });
+
+ /**
+ *
+ * @returns {*}
+ */
+ function getRippleSize() {
+ var width = container.prop('offsetWidth'),
+ height = container.prop('offsetHeight'),
+ multiplier, size;
+ if (element.hasClass('md-menu-item')) {
+ size = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) );
+ } else {
+ multiplier = element.hasClass('md-fab') ? 1.1 : 0.8;
+ size = Math.max(width, height) * multiplier;
}
+ return size;
+ }
+
+ /**
+ *
+ * @returns {{backgroundColor: *, width: string, height: string, marginLeft: string, marginTop: string}}
+ */
+ function getRippleCss(size, left, top) {
+ var css = {
+ backgroundColor: rgbaToRGB(color),
+ width: size + 'px',
+ height: size + 'px',
+ marginLeft: (size * -0.5) + 'px',
+ marginTop: (size * -0.5) + 'px'
+ };
+
+ contentParent && (top += contentParent.$element.prop('scrollTop'));
+
+ if (options.center) {
+ css.left = css.top = '50%';
+ } else {
+ var rect = node.getBoundingClientRect();
+ css.left = Math.round((left - rect.left) / container.prop('offsetWidth') * 100) + '%';
+ css.top = Math.round((top - rect.top) / container.prop('offsetHeight') * 100) + '%';
+ }
+
+ return css;
+
+ /**
+ *
+ */
+ function rgbaToRGB(color) {
+ return color.replace('rgba', 'rgb').replace(/,[^\)\,]+\)/, ')');
+ }
+ }
+
+ /**
+ *
+ */
+ function getRippleContainer() {
+ if (rippleContainer) return rippleContainer;
+ var container = rippleContainer = angular.element('
');
+ element.append(container);
+ return container;
+ }
+ }
+
+ /**
+ *
+ */
+ function onInput(ev) {
+ var ripple, index;
+ if (ev.eventType === Hammer.INPUT_START && ev.isFirst && isRippleAllowed()) {
+ ripple = createRipple(ev.center.x, ev.center.y);
+ isHeld = true;
+ } else if (ev.eventType === Hammer.INPUT_END && ev.isFinal) {
+ isHeld = false;
+ index = ripples.length - 1;
+ ripple = ripples[index];
+ $timeout(function () {
+ updateElement(ripple);
+ }, 0, false);
+ }
+
+ /**
+ *
+ */
+ function isRippleAllowed() {
+ var parent = node.parentNode;
+ return !node.hasAttribute('disabled') && !(parent && parent.hasAttribute('disabled'));
+ }
+
}
}
}
-}
/**
* noink/nobar/nostretch directive: make any element that has one of
diff --git a/src/core/services/ripple/ripple.spec.js b/src/core/services/ripple/ripple.spec.js
new file mode 100644
index 00000000000..fc1f25cedaf
--- /dev/null
+++ b/src/core/services/ripple/ripple.spec.js
@@ -0,0 +1,31 @@
+describe('mdInkRipple diretive', function() {
+
+ function simulateEventAt(centerX, eventType) {
+ return {
+ eventType: eventType,
+ center: { x: centerX },
+ preventDefault: angular.noop,
+ srcEvent : {
+ stopPropagation : angular.noop
+ }
+ };
+ }
+
+ beforeEach(module('material.core'));
+
+ it('should support custom colors via md-ink-ripple', inject(function ($timeout, $compile, $rootScope) {
+ var elem = $compile('
')($rootScope.$new()),
+ container, ripple;
+
+ expect(elem.children('.md-ripple-container').length).toBe(0);
+
+ elem.controller('mdInkRipple').createRipple(0, 0);
+ container = elem.children('.md-ripple-container');
+ expect(container.length).toBe(1);
+
+ ripple = container.children('.md-ripple');
+ expect(ripple.length).toBe(1);
+ expect(ripple.css('backgroundColor')).toBe('rgb(187, 187, 187)');
+ }));
+
+});
diff --git a/src/core/style/structure.scss b/src/core/style/structure.scss
index b397a961f9c..242ff069e7d 100644
--- a/src/core/style/structure.scss
+++ b/src/core/style/structure.scss
@@ -248,12 +248,7 @@ input {
&.md-ripple-scaled {
transform: scale(1);
}
- &.md-ripple-full, &.md-ripple-visible {
+ &.md-ripple-active, &.md-ripple-full, &.md-ripple-visible {
opacity: 0.20;
}
- &.md-ripple-held {
- opacity: 0.15;
- transform: scale(0.35);
- transition: none;
- }
}