From 6aab69a3a58075e5b2dcae9ddbd128d3b4ac1668 Mon Sep 17 00:00:00 2001 From: Elad Bezalel Date: Tue, 12 Apr 2016 20:39:13 +0300 Subject: [PATCH] feat(colors): directive and service to use any color from any palette on any element - directive and documentation - service and documentation - tests - smart watch option, when the directive recognize at least one of it's values needs interpolation it starts to watch it - mdColorsWatch - when set to false the directive doesn't watch for changes - when using `background` property the directive will seamlessly set the foreground color according the palette hue - $mdColorUtil - aggregate color related utils - fixed theming scoping issue - THEME was defined globally which made it leak and changed for other modules fixes #1269 --- src/components/colors/colors.js | 247 +++++++++++++ src/components/colors/colors.spec.js | 331 ++++++++++++++++++ .../colors/demoBasicUsage/index.html | 18 + .../colors/demoBasicUsage/script.js | 22 ++ .../colors/demoBasicUsage/style.css | 22 ++ .../colors/demoBasicUsage/userCard.tmpl.html | 16 + .../colors/demoThemePicker/index.html | 18 + .../colors/demoThemePicker/script.js | 38 ++ .../colors/demoThemePicker/style.css | 18 + .../demoThemePicker/themePreview.tmpl.html | 29 ++ src/core/services/ripple/ripple.js | 49 +-- src/core/services/theming/theme.palette.js | 6 +- src/core/services/theming/theming.js | 14 +- src/core/util/color.js | 73 ++++ 14 files changed, 850 insertions(+), 51 deletions(-) create mode 100644 src/components/colors/colors.js create mode 100644 src/components/colors/colors.spec.js create mode 100644 src/components/colors/demoBasicUsage/index.html create mode 100644 src/components/colors/demoBasicUsage/script.js create mode 100644 src/components/colors/demoBasicUsage/style.css create mode 100644 src/components/colors/demoBasicUsage/userCard.tmpl.html create mode 100644 src/components/colors/demoThemePicker/index.html create mode 100644 src/components/colors/demoThemePicker/script.js create mode 100644 src/components/colors/demoThemePicker/style.css create mode 100644 src/components/colors/demoThemePicker/themePreview.tmpl.html create mode 100644 src/core/util/color.js diff --git a/src/components/colors/colors.js b/src/components/colors/colors.js new file mode 100644 index 0000000000..1a6cbd461e --- /dev/null +++ b/src/components/colors/colors.js @@ -0,0 +1,247 @@ +(function () { + "use strict"; + + /** + * Use a RegExp to check if the `md-colors=""` is static string + * or one that should be observed and dynamically interpolated. + */ + var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/; + var COLOR_PALETTES = undefined; + + /** + * @ngdoc module + * @name material.components.colors + * + * @description + * Define $mdColors service and a `md-colors=""` attribute directive + */ + angular + .module('material.components.colors', ['material.core']) + .directive('mdColors', MdColorsDirective) + .service('$mdColors', MdColorsService); + + /** + * @ngdoc service + * @name $mdColors + * @module material.core.theming.colors + * + * @description + * With only defining themes, one couldn't get non ngMaterial elements colored with Material colors, + * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply + * those values to element as CSS property values. + * + * @usage + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + */ + function MdColorsService($mdTheming, $mdColorPalette, $mdUtil, $parse) { + COLOR_PALETTES = COLOR_PALETTES || Object.keys($mdColorPalette); + + // Publish service instance + return { + applyThemeColors: applyThemeColors, + getThemeColor: getThemeColor + }; + + // ******************************************** + // Internal Methods + // ******************************************** + + /** + * Convert the color expression into an object with scope-interpolated values + * Then calculate the rgba() values based on the theme color parts + */ + function applyThemeColors(element, scope, colorExpression) { + // Json.parse() does not work because the keys are not quoted; + // use $parse to convert to a hash map + var themeColors = $parse(colorExpression)(scope); + + // Assign the calculate RGBA color values directly as inline CSS + element.css(interpolateColors(themeColors)); + } + + /** + * Public api to get parsed color from expression + * + */ + function getThemeColor(expression) { + var color = extractColorOptions(expression); + + return parseColor(color); + } + + /** + * Return the parsed color + * @param color hashmap of color definitions + * @param contrast whether use contrast color for foreground + * @returns rgba color string + */ + function parseColor(color, contrast) { + contrast = contrast || false; + var rgbValues = $mdColorPalette[color.palette][color.hue]; + + rgbValues = contrast ? rgbValues.contrast : rgbValues.value; + + return $mdUtil.supplant('rgba( {0}, {1}, {2}, {3} )', + [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity] + ); + } + + /** + * Convert the color expression into an object with scope-interpolated values + * Then calculate the rgba() values based on the theme color parts + * + * @results Hashmap of CSS properties with associated `rgba( )` string vales + * + * + */ + function interpolateColors(themeColors) { + var rgbColors = {}; + + var hasColorProperty = themeColors.hasOwnProperty('color'); + + angular.forEach(themeColors, function (value, key) { + var color = extractColorOptions(value); + + rgbColors[key] = parseColor(color); + + if (key === 'background' && !hasColorProperty) { + rgbColors['color'] = parseColor(color, true); + } + }); + + return rgbColors; + } + + /** + * For the evaluated expression, extract the color parts into a hash map + */ + function extractColorOptions(expression) { + var parts = expression.split('-'), + hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]), + theme = hasTheme ? parts.splice(0, 1)[0] : 'default'; + + var defaultHue = parts[0] !== 'accent' ? 500 : 'A200'; + + return { + theme: theme, + palette: extractPalette(parts, theme), + hue: parts[1] || defaultHue, + opacity: parts[2] || 1 + }; + } + + /** + * Calculate the theme palette name + */ + function extractPalette(parts, theme) { + // If the next section is one of the palettes we assume it's a two word palette + // Two word palette can be also written in camelCase, forming camelCase to dash-case + + var isTwoWord = parts.length > 1 && COLOR_PALETTES.indexOf(parts[1]) !== -1; + var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + + if (isTwoWord) palette = parts[0] + '-' + parts.splice(1, 1); + + if (COLOR_PALETTES.indexOf(palette) === -1) { + // If the palette is not in the palette list it's one of primary/accent/warn/background + var scheme = $mdTheming.THEMES[theme].colors[palette]; + if (!scheme) { + throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette})); + } + palette = scheme.name; + } + + return palette; + } + } + + /** + * @ngdoc directive + * @name mdColors + * @module material.components.colors + * + * @restrict A + * + * @description + * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values. + * + * The format will be similar to our color defining in the scss files: + * + * ## ‘[?theme]-[palette]-[?hue]-[?opacity]’ + * - [theme] - default value is the default theme + * - [palette] - can be either palette name or primary/accent/warn/background + * - [hue] - default is 500 + * - [opacity] - default is 1 + * - ? - optional + * + * @usage + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + * @param {boolean=} md-colors-watch `md-colors` directive automatically will watch for changes if there's an + * interpolated value or a function. `md-colors-watch` will indicate whether the value should be watched or not.
+ * `default` value is true. + * + */ + + function MdColorsDirective($mdColors, $mdUtil, $log) { + return { + restrict: 'A', + compile: function (tElem, tAttrs) { + var shouldWatch = shouldColorsWatch(); + + return function (scope, element, attrs) { + var colorExpression = function () { + return attrs.mdColors; + }; + + try { + if (shouldWatch) { + scope.$watch(colorExpression, angular.bind(this, + $mdColors.applyThemeColors, element, scope + )); + } + else { + $mdColors.applyThemeColors(element, scope, colorExpression()); + } + + } + catch (e) { + $log.error(e.message); + } + + }; + + function shouldColorsWatch() { + // Simulate 1x binding and mark mdColorsWatch == false + var rawColorExpression = tAttrs.mdColors; + var bindOnce = rawColorExpression.indexOf('::') > -1; + var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors); + + // Remove it for the postLink... + tAttrs.mdColors = rawColorExpression.replace('::',''); + + var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch); + + return (bindOnce || isStatic) ? false : + hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true; + } + } + }; + + } + + +})(); diff --git a/src/components/colors/colors.spec.js b/src/components/colors/colors.spec.js new file mode 100644 index 0000000000..1a4f151f6f --- /dev/null +++ b/src/components/colors/colors.spec.js @@ -0,0 +1,331 @@ +describe('md-colors', function () { + var $compile, $rootScope; + var $mdColorPalette, $mdTheming; + var supplant, scope; + + beforeEach(module('material.components.colors', function ($mdThemingProvider) { + $mdThemingProvider.theme('myTheme') + .primaryPalette('light-blue') + .accentPalette('yellow'); + })); + + beforeEach(inject(function ($injector) { + $compile = $injector.get('$compile'); + $rootScope = $injector.get('$rootScope'); + $mdColorPalette = $injector.get('$mdColorPalette'); + $mdTheming = $injector.get('$mdTheming'); + supplant = $injector.get('$mdUtil').supplant; + scope = $rootScope.$new(); + })); + + describe('directive', function () { + + function createElement(scope, options) { + var elementString = supplant('
', { + attrs : options.attrs, + palette : options.palette, + theme : options.theme || 'default', + hue : '-' + (options.hue || '500'), + opacity : '-' + (options.opacity || 1) + }); + + var element = $compile(elementString)(scope); + scope.$apply(); + + return element; + } + + function setup(options) { + var element = createElement(scope, { + palette: options.palette, + hue: options.hue = options.hue || '500', + opacity: options.opacity, + theme: options.theme + }); + var color = $mdColorPalette[options.palette][options.hue]; + + color = options.contrast ? color.contrast : color.value; + + return { + elementStyle: element[0].style, + scope: scope, + color: color[3] || options.opacity ? + supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], color[3] || options.opacity]) : + supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]) + } + } + + /** + *
+ */ + it('should accept color palette', function () { + var build = setup({ palette: 'red' }); + expect(build.elementStyle.background).toContain(build.color); + }); + + describe('two-worded palette', function () { + /** + *
+ */ + it('should accept palette spliced with dash', function () { + var build = setup({ palette: 'blue-grey' }); + expect(build.elementStyle.background).toContain(build.color); + }); + + /** + *
+ */ + it('should accept palette formatted as camelCase', function () { + var element = createElement(scope, { palette: 'blueGrey', hue: '200', opacity: '0.8' }); + var color = $mdColorPalette['blue-grey']['200'].value; + var expectedRGBa = supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], '0.8']); + + expect(element[0].style.background).toContain( expectedRGBa ); + }); + }); + + /** + *
+ */ + it('should accept color palette and hue', function () { + var build = setup({ palette: 'red', hue: '200' }); + expect(build.elementStyle.background).toContain(build.color); + }); + + /** + *
+ */ + it('should accept color palette, hue and opacity', function () { + var build = setup({ palette: 'red', hue: '200', opacity: '0.8' }); + expect(build.elementStyle.background).toContain(build.color); + }); + + /** + * md-colors applies smart foreground colors (in case 'background' property is used) according the palettes from + * https://www.google.com/design/spec/style/color.html#color-color-palette + */ + describe('foreground color', function () { + /** + *
+ */ + it('should set background to red-500 and foreground color white', function () { + var build = setup({ palette: 'red', contrast: true }); + expect(build.elementStyle.color).toContain(build.color); + }); + /** + *
+ */ + xit('should set background to red-50 and foreground color black', function () { + var build = setup({ palette: 'red', hue: '50', contrast: true }); + expect(build.elementStyle.color).toContain(build.color); + }); + + }); + + describe('themes', function () { + /** + *
+ */ + it('should accept primary palette', function() { + var type = 'primary'; + var paletteName = $mdTheming.THEMES['default'].colors[type].name; + var color = $mdColorPalette[paletteName]['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: type }); + + expect(element[0].style.background).toContain(expectedRGB); + }); + + /** + *
+ */ + it('should accept accent palette', function() { + var type = 'accent'; + var paletteName = $mdTheming.THEMES['default'].colors[type].name; + var color = $mdColorPalette[paletteName]['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: type }); + + expect(element[0].style.background).toContain( expectedRGB ); + }); + + /** + *
+ */ + it('should accept warn palette', function() { + var type = 'warn'; + var paletteName = $mdTheming.THEMES['default'].colors[type].name; + var color = $mdColorPalette[paletteName]['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: type }); + + expect(element[0].style.background).toContain( expectedRGB ); + }); + + /** + *
+ */ + it('should accept background palette', function() { + var type = 'background'; + var paletteName = $mdTheming.THEMES['default'].colors[type].name; + var color = $mdColorPalette[paletteName]['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: type }); + + expect(element[0].style.background).toContain( expectedRGB ); + }); + + describe('custom themes', function () { + + /** + *
+ */ + it('should accept theme, color palette, and hue', function () { + var type = 'primary'; + var paletteName = $mdTheming.THEMES['myTheme'].colors[type].name; + var color = $mdColorPalette[paletteName]['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { theme: 'myTheme', palette: type, hue: '500' }); + + expect(element[0].style.background).toContain( expectedRGB ); + }); + }); + }); + + describe('watched values', function () { + + /** + *
+ */ + it('should accept interpolated value', function() { + var color = $mdColorPalette['red']['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + + scope.color = 'red'; + var element = createElement(scope, { palette: '{{color}}' }); + + expect(element[0].style.background).toContain( expectedRGB ); + + scope.color = 'lightBlue-200-0.8'; + scope.$apply(); + + color = $mdColorPalette['light-blue']['200'].value; + var expectedRGBa = supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], '0.8']); + + expect(element[0].style.background).toContain( expectedRGBa ); + }); + + /** + *
+ */ + it('should accept function', inject(function ($compile) { + var color = $mdColorPalette['light-blue']['200'].value; + var element = $compile('
')(scope); + var expectedRGBa = supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], '0.8']); + + scope.color = function () { + return 'lightBlue-200-0.8'; + }; + scope.$apply(); + + expect(element[0].style.background).toContain( expectedRGBa ); + })); + + /** + *
+ */ + it('should accept ternary value', inject(function ($compile, $timeout) { + var element = $compile('
')(scope); + var color = $mdColorPalette['light-blue']['500'].value; + var red = $mdColorPalette['red']['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + + scope.$apply(function() { + scope.test = false; + }); + + expect(element[0].style.background).toContain( expectedRGB ); + + scope.$apply(function() { + scope.test = true; + }); + $timeout.flush(); + + expectedRGB = supplant('rgb({0}, {1}, {2})', [red[0], red[1], red[2]]); + expect(element[0].style.background).toContain( expectedRGB ); + })); + + describe('md-colors-watch', function () { + it('should watch if mdColorsWatch attribute is set (without value)', function () { + scope.color = 'red'; + + var color = $mdColorPalette['red']['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: '{{color}}', attrs: 'md-colors-watch' }); + + expect(element[0].style.background).toContain( expectedRGB ); + + scope.$apply(function() { + scope.color = 'lightBlue-200-0.8'; + }); + + color = $mdColorPalette['light-blue']['200'].value; + var expectedRGBa = supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], '0.8']); + expect(element[0].style.background).toContain( expectedRGBa ) + }); + + it('should not watch if mdColorsWatch attribute is set to false', function () { + scope.color = 'red'; + + var color = $mdColorPalette['red']['500'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + var element = createElement(scope, { palette: '{{color}}', attrs: 'md-colors-watch="false"' }); + + expect(element[0].style.background).toContain( expectedRGB ); + + scope.$apply(function() { + scope.color = 'lightBlue-200-0.8'; + }); + + expect(element[0].style.background).toContain( expectedRGB ) + }); + + it('should watch if mdColorsWatch attribute is set to true', function () { + scope.$apply(function() { + scope.color = 'red'; + }); + + var color = $mdColorPalette['red']['500'].value; + var element = createElement(scope, { + palette: '{{color}}', + attrs: 'md-colors-watch="true"' + }); + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + + + expect(element[0].style.background).toContain( expectedRGB ); + + scope.$apply(function() { + scope.color = 'lightBlue-200-0.8'; + }); + + color = $mdColorPalette['light-blue']['200'].value; + var expectedRGBa = supplant('rgba({0}, {1}, {2}, {3})', [color[0], color[1], color[2], '0.8']); + + expect(element[0].style.background).toContain( expectedRGBa ); + }); + }); + }) + }); + + describe('service', function () { + it('should apply colors on an element', inject(function ($mdColors) { + var element = angular.element('
'); + var color = $mdColorPalette['red']['200'].value; + var expectedRGB = supplant('rgb({0}, {1}, {2})', [color[0], color[1], color[2]]); + + $mdColors.applyThemeColors(element, scope, '{background: \'red-200\'}'); + expect(element[0].style.background).toContain( expectedRGB ); + })) + }) +}); \ No newline at end of file diff --git a/src/components/colors/demoBasicUsage/index.html b/src/components/colors/demoBasicUsage/index.html new file mode 100644 index 0000000000..8b52e5d34f --- /dev/null +++ b/src/components/colors/demoBasicUsage/index.html @@ -0,0 +1,18 @@ +
+

+ Developers implementing custom components using material elements of often want to easily apply Theme colors to to their custom components. + The md-colors directive allows pre-defined theme colors to be easily applied as CSS style properties. +

+

+ Consider the custom components <user-card> used below where the md-colors attribute is used to color the background and text colors + with the current theme palette colors using md-colors="{background: '{{theme}}-accent'}". +

+ + + + +

+ Note: md-colors uses the specifications defined at Material Design Colors + and the Themes defined using $mdThemingProvider... and is simply an extension of the $mdTheming features. +

+
diff --git a/src/components/colors/demoBasicUsage/script.js b/src/components/colors/demoBasicUsage/script.js new file mode 100644 index 0000000000..ecd429a787 --- /dev/null +++ b/src/components/colors/demoBasicUsage/script.js @@ -0,0 +1,22 @@ +angular.module('colorsDemo', ['ngMaterial']) + .config(function ($mdThemingProvider, $mdIconProvider) { + $mdThemingProvider.theme('forest') + .primaryPalette('brown') + .accentPalette('green'); + + $mdIconProvider + .defaultIconSet('img/icons/sets/social-icons.svg', 24); + }) + .directive('userCard', function () { + return { + restrict: 'E', + templateUrl: 'userCard.tmpl.html', + scope: { + name: '@', + theme: '@' + }, + controller: function ($scope) { + $scope.theme = $scope.theme || 'default'; + } + } + }); diff --git a/src/components/colors/demoBasicUsage/style.css b/src/components/colors/demoBasicUsage/style.css new file mode 100644 index 0000000000..7bc276caaa --- /dev/null +++ b/src/components/colors/demoBasicUsage/style.css @@ -0,0 +1,22 @@ +.card-media { + margin-right: 16px; + border-radius: 50%; + overflow: hidden; +} + +.md-subhead.description { + color: rgba(255, 255, 255, 0.7); +} + +.card-media md-icon { + width: 40px; + height: 40px; + color: rgba(255, 255, 255, 0.87); +} + +user-card { + margin-bottom: 20px; +} +user-card > span { + padding-left: 15px; +} diff --git a/src/components/colors/demoBasicUsage/userCard.tmpl.html b/src/components/colors/demoBasicUsage/userCard.tmpl.html new file mode 100644 index 0000000000..01e3842925 --- /dev/null +++ b/src/components/colors/demoBasicUsage/userCard.tmpl.html @@ -0,0 +1,16 @@ + Coloring using the '{{theme}}' theme + + + + +
+ +
+
+ + {{name}} + This card is colored according the {{theme}} theme + +
+
diff --git a/src/components/colors/demoThemePicker/index.html b/src/components/colors/demoThemePicker/index.html new file mode 100644 index 0000000000..5426042f8b --- /dev/null +++ b/src/components/colors/demoThemePicker/index.html @@ -0,0 +1,18 @@ +
+

+ md-colors directive will automatically watch for changes if it recognizes an interpolation or a function
+ This can be prevented using md-colors-watch attribute +

+
+ + {{color}} + +
+
+
+ +
+
+
diff --git a/src/components/colors/demoThemePicker/script.js b/src/components/colors/demoThemePicker/script.js new file mode 100644 index 0000000000..abbd4112b5 --- /dev/null +++ b/src/components/colors/demoThemePicker/script.js @@ -0,0 +1,38 @@ +angular + .module('colorsThemePickerDemo', ['ngMaterial']) + .controller('ThemeDemoCtrl', function ($scope, $mdColorPalette) { + $scope.colors = Object.keys($mdColorPalette); + + $scope.primary = 'purple'; + $scope.accent = 'green'; + + $scope.isPrimary = true; + + $scope.selectTheme = function (color) { + if ($scope.isPrimary) { + $scope.primary = color; + + $scope.isPrimary = false; + } + else { + $scope.accent = color; + + $scope.isPrimary = true; + } + }; + }) + .directive('themePreview', function () { + return { + restrict: 'E', + templateUrl: 'themePreview.tmpl.html', + scope: { + primary: '=', + accent: '=' + }, + controller: function ($scope, $mdColors, $mdColorUtil) { + $scope.getColor = function (color) { + return $mdColorUtil.rgbaToHex($mdColors.getThemeColor(color)) + }; + } + } + }); \ No newline at end of file diff --git a/src/components/colors/demoThemePicker/style.css b/src/components/colors/demoThemePicker/style.css new file mode 100644 index 0000000000..b7e62dd5da --- /dev/null +++ b/src/components/colors/demoThemePicker/style.css @@ -0,0 +1,18 @@ +.section { + font-size: 12px; + padding: 16px; + font-weight: bold; +} + +.line { + padding: 16px; +} + +.primary, .accent { + margin: 4px 0; + height: 120px; +} + +[md-colors] { + transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1); +} \ No newline at end of file diff --git a/src/components/colors/demoThemePicker/themePreview.tmpl.html b/src/components/colors/demoThemePicker/themePreview.tmpl.html new file mode 100644 index 0000000000..c093e6623e --- /dev/null +++ b/src/components/colors/demoThemePicker/themePreview.tmpl.html @@ -0,0 +1,29 @@ +

{{primary}} - {{accent}}

+
+
+ Primary - {{primary}} +
+ 500 + {{getColor(primary + '-500')}} +
+
+
+ 700 + {{getColor(primary + '-700')}} +
+
+ 800 + {{getColor(primary + '-800')}} +
+
+ Accent - {{accent}} +
+ A200 + {{getColor(accent + '-A200')}} +
+
+
\ No newline at end of file diff --git a/src/core/services/ripple/ripple.js b/src/core/services/ripple/ripple.js index 58f8407cfe..ae33e59cc5 100644 --- a/src/core/services/ripple/ripple.js +++ b/src/core/services/ripple/ripple.js @@ -117,10 +117,11 @@ function InkRippleService ($injector) { * Controller used by the ripple service in order to apply ripples * @ngInject */ -function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil) { +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; @@ -200,39 +201,12 @@ InkRippleCtrl.prototype.calculateColor = function () { 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 rgbToRGBA(color); - if (color.indexOf('#') === 0) return hexToRGBA(color); - - /** - * Converts hex value to RGBA string - * @param color {string} - * @returns {string} - */ - function hexToRGBA (color) { - var hex = color[ 0 ] === '#' ? color.substr(1) : color, - dig = hex.length / 3, - red = hex.substr(0, dig), - green = hex.substr(dig, dig), - blue = hex.substr(dig * 2); - if (dig === 1) { - red += red; - green += green; - blue += blue; - } - return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)'; - } - - /** - * Converts an RGB color to RGBA - * @param color {string} - * @returns {string} - */ - function rgbToRGBA (color) { - return color.replace(')', ', 0.1)').replace('(', 'a('); - } + if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color); + if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color); }; @@ -356,6 +330,7 @@ InkRippleCtrl.prototype.createRipple = function (left, top) { if (!this.isRippleAllowed()) return; var ctrl = this; + var colorUtil = ctrl.$mdColorUtil; var ripple = angular.element('
'); var width = this.$element.prop('clientWidth'); var height = this.$element.prop('clientHeight'); @@ -370,8 +345,8 @@ InkRippleCtrl.prototype.createRipple = function (left, top) { background: 'black', width: size + 'px', height: size + 'px', - backgroundColor: rgbaToRGB(color), - borderColor: rgbaToRGB(color) + backgroundColor: colorUtil.rgbaToRgb(color), + borderColor: colorUtil.rgbaToRgb(color) }); this.lastRipple = ripple; @@ -396,12 +371,6 @@ InkRippleCtrl.prototype.createRipple = function (left, top) { }, false); - function rgbaToRGB (color) { - return color - ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')') - : 'rgb(0,0,0)'; - } - function getSize (fit, x, y) { return fit ? Math.max(x, y) diff --git a/src/core/services/theming/theme.palette.js b/src/core/services/theming/theme.palette.js index 79a16e2eb1..c6a3e5368a 100644 --- a/src/core/services/theming/theme.palette.js +++ b/src/core/services/theming/theme.palette.js @@ -316,7 +316,7 @@ angular.module('material.core.theming.palette', []) 'A400': '#8d6e63', 'A700': '#5d4037', 'contrastDefaultColor': 'light', - 'contrastDarkColors': '50 100 200', + 'contrastDarkColors': '50 100 200 A100 A200', 'contrastStrongLightColors': '300 400' }, 'grey': { @@ -335,7 +335,7 @@ angular.module('material.core.theming.palette', []) 'A400': '#303030', 'A700': '#616161', 'contrastDefaultColor': 'dark', - 'contrastLightColors': '600 700 800 900' + 'contrastLightColors': '600 700 800 900 A200 A400 A700' }, 'blue-grey': { '50': '#eceff1', @@ -353,7 +353,7 @@ angular.module('material.core.theming.palette', []) 'A400': '#78909c', 'A700': '#455a64', 'contrastDefaultColor': 'light', - 'contrastDarkColors': '50 100 200 300 700', + 'contrastDarkColors': '50 100 200 300 A100 A200', 'contrastStrongLightColors': '400 500 700' } }); diff --git a/src/core/services/theming/theming.js b/src/core/services/theming/theming.js index 8872346474..1fa24dfd51 100644 --- a/src/core/services/theming/theming.js +++ b/src/core/services/theming/theming.js @@ -61,7 +61,6 @@ var GENERATED = { }; // In memory storage of defined themes and color palettes (both loaded by CSS, and user specified) var PALETTES; -var THEMES; // Text Colors on light and dakr backgrounds // @see https://www.google.com/design/spec/style/color.html#color-text-background-colors @@ -140,7 +139,7 @@ var disableTheming = false; function ThemingProvider($mdColorPalette) { PALETTES = { }; - THEMES = { }; + var THEMES = { }; var themingProvider; var defaultTheme = 'default'; @@ -357,7 +356,7 @@ function ThemingProvider($mdColorPalette) { applyTheme.inherit = inheritTheme; applyTheme.registered = registered; applyTheme.defaultTheme = function() { return defaultTheme; }; - applyTheme.generateTheme = function(name) { generateTheme(name, nonce); }; + applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, nonce); }; return applyTheme; @@ -496,7 +495,7 @@ function parseRules(theme, colorType, rules) { var rulesByType = {}; // Generate our themes at run time given the state of THEMES and PALETTES -function generateAllThemes($injector) { +function generateAllThemes($injector, $mdTheming ) { var head = document.head; var firstChild = head ? head.firstElementChild : null; var themeCss = !disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : ''; @@ -550,9 +549,9 @@ function generateAllThemes($injector) { // call generateTheme to do this on a theme-by-theme basis. if (generateOnDemand) return; - angular.forEach(THEMES, function(theme) { + angular.forEach($mdTheming.THEMES, function(theme) { if (!GENERATED[theme.name]) { - generateTheme(theme.name, nonce); + generateTheme(theme, theme.name, nonce); } }); @@ -617,8 +616,7 @@ function generateAllThemes($injector) { } } -function generateTheme(name, nonce) { - var theme = THEMES[name]; +function generateTheme(theme, name, nonce) { var head = document.head; var firstChild = head ? head.firstElementChild : null; diff --git a/src/core/util/color.js b/src/core/util/color.js new file mode 100644 index 0000000000..fd2dcd1853 --- /dev/null +++ b/src/core/util/color.js @@ -0,0 +1,73 @@ +/** + * @ngdoc module + * @name material.core.colorUtil + * @description + * Color Util + */ +angular + .module('material.core') + .factory('$mdColorUtil', ColorUtilFactory); + +function ColorUtilFactory() { + /** + * Converts hex value to RGBA string + * @param color {string} + * @returns {string} + */ + function hexToRgba (color) { + var hex = color[ 0 ] === '#' ? color.substr(1) : color, + dig = hex.length / 3, + red = hex.substr(0, dig), + green = hex.substr(dig, dig), + blue = hex.substr(dig * 2); + if (dig === 1) { + red += red; + green += green; + blue += blue; + } + return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)'; + } + + /** + * Converts rgba value to hex string + * @param color {string} + * @returns {string} + */ + function rgbaToHex(color) { + color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); + + var hex = (color && color.length === 4) ? "#" + + ("0" + parseInt(color[1],10).toString(16)).slice(-2) + + ("0" + parseInt(color[2],10).toString(16)).slice(-2) + + ("0" + parseInt(color[3],10).toString(16)).slice(-2) : ''; + + return hex.toUpperCase(); + } + + /** + * Converts an RGB color to RGBA + * @param color {string} + * @returns {string} + */ + function rgbToRgba (color) { + return color.replace(')', ', 0.1)').replace('(', 'a('); + } + + /** + * Converts an RGBA color to RGB + * @param color {string} + * @returns {string} + */ + function rgbaToRgb (color) { + return color + ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')') + : 'rgb(0,0,0)'; + } + + return { + rgbaToHex: rgbaToHex, + hexToRgba: hexToRgba, + rgbToRgba: rgbToRgba, + rgbaToRgb: rgbaToRgb + } +} \ No newline at end of file