2 * Angular Material Design
3 * https://github.com/angular/material
7 goog.provide('ngmaterial.components.colors');
8 goog.require('ngmaterial.core');
13 * Use a RegExp to check if the `md-colors="<expression>"` is static string
14 * or one that should be observed and dynamically interpolated.
16 MdColorsDirective['$inject'] = ["$mdColors", "$mdUtil", "$log", "$parse"];
17 MdColorsService['$inject'] = ["$mdTheming", "$mdUtil", "$log"];
18 var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/;
19 var colorPalettes = null;
23 * @name material.components.colors
26 * Define $mdColors service and a `md-colors=""` attribute directive
29 .module('material.components.colors', ['material.core'])
30 .directive('mdColors', MdColorsDirective)
31 .service('$mdColors', MdColorsService);
36 * @module material.components.colors
39 * With only defining themes, one couldn't get non ngMaterial elements colored with Material colors,
40 * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply
41 * those values to element as CSS property values.
45 * angular.controller('myCtrl', function ($mdColors) {
46 * var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
52 function MdColorsService($mdTheming, $mdUtil, $log) {
53 colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES);
55 // Publish service instance
57 applyThemeColors: applyThemeColors,
58 getThemeColor: getThemeColor,
62 // ********************************************
64 // ********************************************
68 * @name $mdColors#applyThemeColors
71 * Gets a color json object, keys are css properties and values are string of the wanted color
72 * Then calculate the rgba() values based on the theme color parts
74 * @param {DOMElement} element the element to apply the styles on.
75 * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color,
76 * for example: `{color: 'red-A200-0.3'}`.
80 * app.directive('myDirective', function($mdColors) {
83 * link: function (scope, elem) {
84 * $mdColors.applyThemeColors(elem, {color: 'red'});
90 function applyThemeColors(element, colorExpression) {
92 if (colorExpression) {
93 // Assign the calculate RGBA color values directly as inline CSS
94 element.css(interpolateColors(colorExpression));
97 $log.error(e.message);
104 * @name $mdColors#getThemeColor
107 * Get parsed color from expression
109 * @param {string} expression string of a color expression (for instance `'red-700-0.8'`)
111 * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`)
115 * angular.controller('myCtrl', function ($mdColors) {
116 * var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
121 function getThemeColor(expression) {
122 var color = extractColorOptions(expression);
124 return parseColor(color);
128 * Return the parsed color
129 * @param color hashmap of color definitions
130 * @param contrast whether use contrast color for foreground
131 * @returns rgba color string
133 function parseColor(color, contrast) {
134 contrast = contrast || false;
135 var rgbValues = $mdTheming.PALETTES[color.palette][color.hue];
137 rgbValues = contrast ? rgbValues.contrast : rgbValues.value;
139 return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})',
140 [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity]
145 * Convert the color expression into an object with scope-interpolated values
146 * Then calculate the rgba() values based on the theme color parts
148 * @results Hashmap of CSS properties with associated `rgba( )` string vales
152 function interpolateColors(themeColors) {
155 var hasColorProperty = themeColors.hasOwnProperty('color');
157 angular.forEach(themeColors, function (value, key) {
158 var color = extractColorOptions(value);
159 var hasBackground = key.indexOf('background') > -1;
161 rgbColors[key] = parseColor(color);
162 if (hasBackground && !hasColorProperty) {
163 rgbColors.color = parseColor(color, true);
171 * Check if expression has defined theme
173 * 'myTheme-primary' => true
176 function hasTheme(expression) {
177 return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]);
181 * For the evaluated expression, extract the color parts into a hash map
183 function extractColorOptions(expression) {
184 var parts = expression.split('-');
185 var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]);
186 var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme();
190 palette: extractPalette(parts, theme),
191 hue: extractHue(parts, theme),
192 opacity: parts[2] || 1
197 * Calculate the theme palette name
199 function extractPalette(parts, theme) {
200 // If the next section is one of the palettes we assume it's a two word palette
201 // Two word palette can be also written in camelCase, forming camelCase to dash-case
203 var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1;
204 var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
206 if (isTwoWord) palette = parts[0] + '-' + parts.splice(1, 1);
208 if (colorPalettes.indexOf(palette) === -1) {
209 // If the palette is not in the palette list it's one of primary/accent/warn/background
210 var scheme = $mdTheming.THEMES[theme].colors[palette];
212 throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette}));
214 palette = scheme.name;
220 function extractHue(parts, theme) {
221 var themeColors = $mdTheming.THEMES[theme].colors;
223 if (parts[1] === 'hue') {
224 var hueNumber = parseInt(parts.splice(2, 1)[0], 10);
226 if (hueNumber < 1 || hueNumber > 3) {
227 throw new Error($mdUtil.supplant('mdColors: \'hue-{hueNumber}\' is not a valid hue, can be only \'hue-1\', \'hue-2\' and \'hue-3\'', {hueNumber: hueNumber}));
229 parts[1] = 'hue-' + hueNumber;
231 if (!(parts[0] in themeColors)) {
232 throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', {
233 availableThemes: Object.keys(themeColors).join(', '),
238 return themeColors[parts[0]].hues[parts[1]];
241 return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default'];
248 * @module material.components.colors
253 * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values.
255 * The format will be similar to our color defining in the scss files:
257 * ## `[?theme]-[palette]-[?hue]-[?opacity]`
258 * - [theme] - default value is the default theme
259 * - [palette] - can be either palette name or primary/accent/warn/background
260 * - [hue] - default is 500 (hue-x can be used with primary/accent/warn/background)
261 * - [opacity] - default is 1
263 * > `?` indicates optional parameter
267 * <div md-colors="{background: 'myTheme-accent-900-0.43'}">
268 * <div md-colors="{color: 'red-A100', 'border-color': 'primary-600'}">
269 * <span>Color demo</span>
274 * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation
275 * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression
276 * to indicate a one-time data binding.
278 * <md-card md-colors="::{background: '{{theme}}-primary-700'}">
283 function MdColorsDirective($mdColors, $mdUtil, $log, $parse) {
286 require: ['^?mdTheme'],
287 compile: function (tElem, tAttrs) {
288 var shouldWatch = shouldColorsWatch();
290 return function (scope, element, attrs, ctrl) {
291 var mdThemeController = ctrl[0];
295 var parseColors = function (theme) {
296 if (typeof theme !== 'string') {
300 if (!attrs.mdColors) {
301 attrs.mdColors = '{}';
305 * Json.parse() does not work because the keys are not quoted;
306 * use $parse to convert to a hash map
308 var colors = $parse(attrs.mdColors)(scope);
311 * If mdTheme is defined up the DOM tree
312 * we add mdTheme theme to colors who doesn't specified a theme
316 * <div md-theme="myTheme">
317 * <div md-colors="{background: 'primary-600'}">
318 * <span md-colors="{background: 'mySecondTheme-accent-200'}">Color demo</span>
323 * 'primary-600' will be 'myTheme-primary-600',
324 * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix
326 if (mdThemeController) {
327 Object.keys(colors).forEach(function (prop) {
328 var color = colors[prop];
329 if (!$mdColors.hasTheme(color)) {
330 colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color;
335 cleanElement(colors);
340 var cleanElement = function (colors) {
341 if (!angular.equals(colors, lastColors)) {
342 var keys = Object.keys(lastColors);
344 if (lastColors.background && !keys.color) {
348 keys.forEach(function (key) {
349 element.css(key, '');
357 * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes
359 var unregisterChanges = angular.noop;
361 if (mdThemeController) {
362 unregisterChanges = mdThemeController.registerChanges(function (theme) {
363 $mdColors.applyThemeColors(element, parseColors(theme));
367 scope.$on('$destroy', function () {
373 scope.$watch(parseColors, angular.bind(this,
374 $mdColors.applyThemeColors, element
378 $mdColors.applyThemeColors(element, parseColors());
383 $log.error(e.message);
388 function shouldColorsWatch() {
389 // Simulate 1x binding and mark mdColorsWatch == false
390 var rawColorExpression = tAttrs.mdColors;
391 var bindOnce = rawColorExpression.indexOf('::') > -1;
392 var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors);
394 // Remove it for the postLink...
395 tAttrs.mdColors = rawColorExpression.replace('::', '');
397 var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch);
399 return (bindOnce || isStatic) ? false :
400 hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true;
410 ngmaterial.components.colors = angular.module("material.components.colors");