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
1,119 lines (968 loc) • 37.6 kB
JavaScript
(function(angular) {
'use strict';
/**
* @ngdoc module
* @name material.core.theming
* @description
* Theming
*/
angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
.directive('mdTheme', ThemingDirective)
.directive('mdThemable', ThemableDirective)
.directive('mdThemesDisabled', disableThemesDirective )
.provider('$mdTheming', ThemingProvider)
.config( detectDisabledThemes )
.run(generateAllThemes);
/**
* Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute
* If yes, then immediately disable all theme stylesheet generation and DOM injection
*/
/**
* @ngInject
*/
function detectDisabledThemes($mdThemingProvider) {
var isDisabled = !!document.querySelector('[md-themes-disabled]');
$mdThemingProvider.disableTheming(isDisabled);
}
/**
* @ngdoc service
* @name $mdThemingProvider
* @module material.core.theming
*
* @description Provider to configure the `$mdTheming` service.
*
* ### Default Theme
* The `$mdThemingProvider` uses by default the following theme configuration:
*
* - Primary Palette: `Blue`
* - Accent Palette: `Pink`
* - Warn Palette: `Deep-Orange`
* - Background Palette: `Grey`
*
* If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite
* the default theme.<br/>
* This can be done by using the following markup.
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* $mdThemingProvider
* .theme('default')
* .primaryPalette('blue')
* .accentPalette('teal')
* .warnPalette('red')
* .backgroundPalette('grey');
* });
* </hljs>
*
* ### Dynamic Themes
*
* By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.<br/>
* If you have an application, which changes its theme on runtime, you have to enable theme watching.
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* // Enable theme watching.
* $mdThemingProvider.alwaysWatchTheme(true);
* });
* </hljs>
*
* ### Custom Theme Styles
*
* Sometimes you may want to use your own theme styles for some custom components.<br/>
* You are able to register your own styles by using the following markup.
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* // Register our custom stylesheet into the theming provider.
* $mdThemingProvider.registerStyles(STYLESHEET);
* });
* </hljs>
*
* The `registerStyles` method only accepts strings as value, so you're actually not able to load an external
* stylesheet file into the `$mdThemingProvider`.
*
* If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,
* like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* // Register your custom stylesheet into the theming provider.
* $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
* });
* </hljs>
*
* ### Browser color
*
* Enables browser header coloring
* for more info please visit:
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
*
* Options parameter: <br/>
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
* `hue` - The hue from the selected palette. Default is `800`<br/>
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* // Enable browser color
* $mdThemingProvider.enableBrowserColor({
* theme: 'myTheme', // Default is 'default'
* palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
* hue: '200' // Default is '800'
* });
* });
* </hljs>
*/
/**
* @ngdoc method
* @name $mdThemingProvider#registerStyles
* @param {string} styles The styles to be appended to Angular Material's built in theme css.
*/
/**
* @ngdoc method
* @name $mdThemingProvider#setNonce
* @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.
* Setting a value allows the use of CSP policy without using the unsafe-inline directive.
*/
/**
* @ngdoc method
* @name $mdThemingProvider#setDefaultTheme
* @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
*/
/**
* @ngdoc method
* @name $mdThemingProvider#alwaysWatchTheme
* @param {boolean} watch Whether or not to always watch themes for changes and re-apply
* classes when they change. Default is `false`. Enabling can reduce performance.
*/
/**
* @ngdoc method
* @name $mdThemingProvider#enableBrowserColor
* @param {Object=} options Options object for the browser color<br/>
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
* `hue` - The hue from the selected palette. Default is `800`<br/>
* @returns {Function} remove function of the browser color
*/
/* Some Example Valid Theming Expressions
* =======================================
*
* Intention group expansion: (valid for primary, accent, warn, background)
*
* {{primary-100}} - grab shade 100 from the primary palette
* {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
* {{primary-100-contrast}} - grab shade 100's contrast color
* {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
* {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
* {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
* {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
* {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
* {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
*
* Foreground expansion: Applies rgba to black/white foreground text
*
* {{foreground-1}} - used for primary text
* {{foreground-2}} - used for secondary text/divider
* {{foreground-3}} - used for disabled text
* {{foreground-4}} - used for dividers
*
*/
// In memory generated CSS rules; registered by theme.name
var GENERATED = { };
// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
var PALETTES;
// Text Colors on light and dark backgrounds
// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors
var DARK_FOREGROUND = {
name: 'dark',
'1': 'rgba(0,0,0,0.87)',
'2': 'rgba(0,0,0,0.54)',
'3': 'rgba(0,0,0,0.38)',
'4': 'rgba(0,0,0,0.12)'
};
var LIGHT_FOREGROUND = {
name: 'light',
'1': 'rgba(255,255,255,1.0)',
'2': 'rgba(255,255,255,0.7)',
'3': 'rgba(255,255,255,0.5)',
'4': 'rgba(255,255,255,0.12)'
};
var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
var LIGHT_SHADOW = '';
var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');
var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
var DEFAULT_COLOR_TYPE = 'primary';
// A color in a theme will use these hues by default, if not specified by user.
var LIGHT_DEFAULT_HUES = {
'accent': {
'default': 'A200',
'hue-1': 'A100',
'hue-2': 'A400',
'hue-3': 'A700'
},
'background': {
'default': '50',
'hue-1': 'A100',
'hue-2': '100',
'hue-3': '300'
}
};
var DARK_DEFAULT_HUES = {
'background': {
'default': 'A400',
'hue-1': '800',
'hue-2': '900',
'hue-3': 'A200'
}
};
THEME_COLOR_TYPES.forEach(function(colorType) {
// Color types with unspecified default hues will use these default hue values
var defaultDefaultHues = {
'default': '500',
'hue-1': '300',
'hue-2': '800',
'hue-3': 'A100'
};
if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
});
var VALID_HUE_VALUES = [
'50', '100', '200', '300', '400', '500', '600',
'700', '800', '900', 'A100', 'A200', 'A400', 'A700'
];
var themeConfig = {
disableTheming : false, // Generate our themes at run time; also disable stylesheet DOM injection
generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).
registeredStyles : [], // Custom styles registered to be used in the theming of custom components.
nonce : null // Nonce to be added as an attribute to the generated themes style tags.
};
/**
*
*/
function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
PALETTES = { };
var THEMES = { };
var themingProvider;
var alwaysWatchTheme = false;
var defaultTheme = 'default';
// Load JS Defined Palettes
angular.extend(PALETTES, $mdColorPalette);
// Default theme defined in core.js
/**
* Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
* @param {string} color Hex value of the wanted browser color
* @returns {Function} Remove function of the meta tags
*/
var setBrowserColor = function (color) {
// Chrome, Firefox OS and Opera
var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
// Windows Phone
var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);
return function () {
removeChrome();
removeWindows();
};
};
/**
* Enables browser header coloring
* for more info please visit:
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
*
* The default color is `800` from `primary` palette of the `default` theme
*
* options are:
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`
* `hue` - The hue from the selected palette. Default is `800`
*
* @param {Object=} options Options object for the browser color
* @returns {Function} remove function of the browser color
*/
var enableBrowserColor = function (options) {
options = angular.isObject(options) ? options : {};
var theme = options.theme || 'default';
var hue = options.hue || '800';
var palette = PALETTES[options.palette] ||
PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];
var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];
return setBrowserColor(color);
};
return themingProvider = {
definePalette: definePalette,
extendPalette: extendPalette,
theme: registerTheme,
/**
* return a read-only clone of the current theme configuration
*/
configuration : function() {
return angular.extend( { }, themeConfig, {
defaultTheme : defaultTheme,
alwaysWatchTheme : alwaysWatchTheme,
registeredStyles : [].concat(themeConfig.registeredStyles)
});
},
/**
* Easy way to disable theming without having to use
* `.constant("$MD_THEME_CSS","");` This disables
* all dynamic theme style sheet generations and injections...
*/
disableTheming: function(isDisabled) {
themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;
},
registerStyles: function(styles) {
themeConfig.registeredStyles.push(styles);
},
setNonce: function(nonceValue) {
themeConfig.nonce = nonceValue;
},
generateThemesOnDemand: function(onDemand) {
themeConfig.generateOnDemand = onDemand;
},
setDefaultTheme: function(theme) {
defaultTheme = theme;
},
alwaysWatchTheme: function(alwaysWatch) {
alwaysWatchTheme = alwaysWatch;
},
enableBrowserColor: enableBrowserColor,
$get: ThemingService,
_LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
_DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
_PALETTES: PALETTES,
_THEMES: THEMES,
_parseRules: parseRules,
_rgba: rgba
};
// Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
function definePalette(name, map) {
map = map || {};
PALETTES[name] = checkPaletteValid(name, map);
return themingProvider;
}
// Returns an new object which is a copy of a given palette `name` with variables from
// `map` overwritten
// Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
function extendPalette(name, map) {
return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) );
}
// Make sure that palette has all required hues
function checkPaletteValid(name, map) {
var missingColors = VALID_HUE_VALUES.filter(function(field) {
return !map[field];
});
if (missingColors.length) {
throw new Error("Missing colors %1 in palette %2!"
.replace('%1', missingColors.join(', '))
.replace('%2', name));
}
return map;
}
// Register a theme (which is a collection of color palettes to use with various states
// ie. warn, accent, primary )
// Optionally inherit from an existing theme
// $mdThemingProvider.theme('custom-theme').primaryPalette('red');
function registerTheme(name, inheritFrom) {
if (THEMES[name]) return THEMES[name];
inheritFrom = inheritFrom || 'default';
var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
var theme = new Theme(name);
if (parentTheme) {
angular.forEach(parentTheme.colors, function(color, colorType) {
theme.colors[colorType] = {
name: color.name,
// Make sure a COPY of the hues is given to the child color,
// not the same reference.
hues: angular.extend({}, color.hues)
};
});
}
THEMES[name] = theme;
return theme;
}
function Theme(name) {
var self = this;
self.name = name;
self.colors = {};
self.dark = setDark;
setDark(false);
function setDark(isDark) {
isDark = arguments.length === 0 ? true : !!isDark;
// If no change, abort
if (isDark === self.isDark) return;
self.isDark = isDark;
self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
// Light and dark themes have different default hues.
// Go through each existing color type for this theme, and for every
// hue value that is still the default hue value from the previous light/dark setting,
// set it to the default hue value from the new light/dark setting.
var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
angular.forEach(newDefaultHues, function(newDefaults, colorType) {
var color = self.colors[colorType];
var oldDefaults = oldDefaultHues[colorType];
if (color) {
for (var hueName in color.hues) {
if (color.hues[hueName] === oldDefaults[hueName]) {
color.hues[hueName] = newDefaults[hueName];
}
}
}
});
return self;
}
THEME_COLOR_TYPES.forEach(function(colorType) {
var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
var color = self.colors[colorType] = {
name: paletteName,
hues: angular.extend({}, defaultHues, hues)
};
Object.keys(color.hues).forEach(function(name) {
if (!defaultHues[name]) {
throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
.replace('%1', name)
.replace('%2', self.name)
.replace('%3', paletteName)
.replace('%4', Object.keys(defaultHues).join(', '))
);
}
});
Object.keys(color.hues).map(function(key) {
return color.hues[key];
}).forEach(function(hueValue) {
if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
.replace('%1', hueValue)
.replace('%2', self.name)
.replace('%3', colorType)
.replace('%4', paletteName)
.replace('%5', VALID_HUE_VALUES.join(', '))
);
}
});
return self;
};
self[colorType + 'Color'] = function() {
var args = Array.prototype.slice.call(arguments);
console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
return self[colorType + 'Palette'].apply(self, args);
};
});
}
/**
* @ngdoc service
* @name $mdTheming
* @module material.core.theming
*
* @description
*
* Service that makes an element apply theming related <b>classes</b> to itself.
*
* <hljs lang="js">
* app.directive('myFancyDirective', function($mdTheming) {
* return {
* restrict: 'e',
* link: function(scope, el, attrs) {
* $mdTheming(el);
* }
* };
* });
* </hljs>
* @param {element=} element to apply theming to
*/
/**
* @ngdoc property
* @name $mdTheming#THEMES
* @description
* Property to get all the themes defined
* @returns {Object} All the themes defined with their properties
*/
/**
* @ngdoc property
* @name $mdTheming#PALETTES
* @description
* Property to get all the palettes defined
* @returns {Object} All the palettes defined with their colors
*/
/**
* @ngdoc method
* @name $mdTheming#registered
* @description
* Determine is specified theme name is a valid, registered theme
* @param {string} themeName the theme to check if registered
* @returns {boolean} whether the theme is registered or not
*/
/**
* @ngdoc method
* @name $mdTheming#defaultTheme
* @description
* Returns the default theme
* @returns {string} The default theme
*/
/**
* @ngdoc method
* @name $mdTheming#generateTheme
* @description
* Lazy generate themes - by default, every theme is generated when defined.
* You can disable this in the configuration section using the
* `$mdThemingProvider.generateThemesOnDemand(true);`
*
* The theme name that is passed in must match the name of the theme that was defined as part of the configuration block.
*
* @param name {string} theme name to generate
*/
/**
* @ngdoc method
* @name $mdTheming#setBrowserColor
* @description
* Sets browser header coloring
* for more info please visit:
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
*
* The default color is `800` from `primary` palette of the `default` theme
*
* options are:<br/>
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.<br/>
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`<br/>
* `hue` - The hue from the selected palette. Default is `800`
*
* @param {Object} options Options object for the browser color
* @returns {Function} remove function of the browser color
*/
/* @ngInject */
function ThemingService($rootScope, $mdUtil, $q, $log) {
// Allow us to be invoked via a linking function signature.
var applyTheme = function (scope, el) {
if (el === undefined) { el = scope; scope = undefined; }
if (scope === undefined) { scope = $rootScope; }
applyTheme.inherit(el, el);
};
Object.defineProperty(applyTheme, 'THEMES', {
get: function () {
return angular.extend({}, THEMES);
}
});
Object.defineProperty(applyTheme, 'PALETTES', {
get: function () {
return angular.extend({}, PALETTES);
}
});
Object.defineProperty(applyTheme, 'ALWAYS_WATCH', {
get: function () {
return alwaysWatchTheme;
}
});
applyTheme.inherit = inheritTheme;
applyTheme.registered = registered;
applyTheme.defaultTheme = function() { return defaultTheme; };
applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
applyTheme.defineTheme = function(name, options) {
options = options || {};
var theme = registerTheme(name);
if (options.primary) {
theme.primaryPalette(options.primary);
}
if (options.accent) {
theme.accentPalette(options.accent);
}
if (options.warn) {
theme.warnPalette(options.warn);
}
if (options.background) {
theme.backgroundPalette(options.background);
}
if (options.dark){
theme.dark();
}
this.generateTheme(name);
return $q.resolve(name);
};
applyTheme.setBrowserColor = enableBrowserColor;
return applyTheme;
/**
* Determine is specified theme name is a valid, registered theme
*/
function registered(themeName) {
if (themeName === undefined || themeName === '') return true;
return applyTheme.THEMES[themeName] !== undefined;
}
/**
* Get theme name for the element, then update with Theme CSS class
*/
function inheritTheme (el, parent) {
var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
updateThemeClass(lookupThemeName());
if (ctrl) {
var watchTheme = alwaysWatchTheme ||
ctrl.$shouldWatch ||
$mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
var unwatch = ctrl.registerChanges(function (name) {
updateThemeClass(name);
if (!watchTheme) {
unwatch();
}
else {
el.on('$destroy', unwatch);
}
});
}
/**
* Find the theme name from the parent controller or element data
*/
function lookupThemeName() {
// As a few components (dialog) add their controllers later, we should also watch for a controller init.
return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
}
/**
* Remove old theme class and apply a new one
* NOTE: if not a valid theme name, then the current name is not changed
*/
function updateThemeClass(theme) {
if (!theme) return;
if (!registered(theme)) {
$log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
'Register it with $mdThemingProvider.theme().');
}
var oldTheme = el.data('$mdThemeName');
if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
el.addClass('md-' + theme + '-theme');
el.data('$mdThemeName', theme);
if (ctrl) {
el.data('$mdThemeController', ctrl);
}
}
}
}
}
function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
return {
priority: 101, // has to be more than 100 to be before interpolation (issue on IE)
link: {
pre: function(scope, el, attrs) {
var registeredCallbacks = [];
var startSymbol = $interpolate.startSymbol();
var endSymbol = $interpolate.endSymbol();
var theme = attrs.mdTheme.trim();
var hasInterpolation =
theme.substr(0, startSymbol.length) === startSymbol &&
theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;
var oneTimeOperator = '::';
var oneTimeBind = attrs.mdTheme
.split(startSymbol).join('')
.split(endSymbol).join('')
.trim()
.substr(0, oneTimeOperator.length) === oneTimeOperator;
var ctrl = {
registerChanges: function (cb, context) {
if (context) {
cb = angular.bind(context, cb);
}
registeredCallbacks.push(cb);
return function () {
var index = registeredCallbacks.indexOf(cb);
if (index > -1) {
registeredCallbacks.splice(index, 1);
}
};
},
$setTheme: function (theme) {
if (!$mdTheming.registered(theme)) {
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
}
ctrl.$mdTheme = theme;
// Iterating backwards to support unregistering during iteration
// http://stackoverflow.com/a/9882349/890293
// we don't use `reverse()` of array because it mutates the array and we don't want it to get re-indexed
for (var i = registeredCallbacks.length; i--;) {
registeredCallbacks[i](theme);
}
},
$shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) ||
$mdTheming.ALWAYS_WATCH ||
(hasInterpolation && !oneTimeBind)
};
el.data('$mdThemeController', ctrl);
var getTheme = function () {
var interpolation = $interpolate(attrs.mdTheme)(scope);
return $parse(interpolation)(scope) || interpolation;
};
var setParsedTheme = function (theme) {
if (typeof theme === 'string') {
return ctrl.$setTheme(theme);
}
$q.when( angular.isFunction(theme) ? theme() : theme )
.then(function(name){
ctrl.$setTheme(name);
});
};
setParsedTheme(getTheme());
var unwatch = scope.$watch(getTheme, function(theme) {
if (theme) {
setParsedTheme(theme);
if (!ctrl.$shouldWatch) {
unwatch();
}
}
});
}
}
};
}
/**
* Special directive that will disable ALL runtime Theme style generation and DOM injection
*
* <link rel="stylesheet" href="angular-material.min.css">
* <link rel="stylesheet" href="angular-material.themes.css">
*
* <body md-themes-disabled>
* ...
* </body>
*
* Note: Using md-themes-css directive requires the developer to load external
* theme stylesheets; e.g. custom themes from Material-Tools:
*
* `angular-material.themes.css`
*
* Another option is to use the ThemingProvider to configure and disable the attribute
* conversions; this would obviate the use of the `md-themes-css` directive
*
*/
function disableThemesDirective() {
themeConfig.disableTheming = true;
// Return a 1x-only, first-match attribute directive
return {
restrict : 'A',
priority : '900'
};
}
function ThemableDirective($mdTheming) {
return $mdTheming;
}
function parseRules(theme, colorType, rules) {
checkValidPalette(theme, colorType);
rules = rules.replace(/THEME_NAME/g, theme.name);
var generatedRules = [];
var color = theme.colors[colorType];
var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g');
// Matches '{{ primary-color }}', etc
var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g;
var palette = PALETTES[color.name];
// find and replace simple variables where we use a specific hue, not an entire palette
// eg. "{{primary-100}}"
//\(' + THEME_COLOR_TYPES.join('\|') + '\)'
rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {
if (colorType === 'foreground') {
if (hue == 'shadow') {
return theme.foregroundShadow;
} else {
return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
}
}
// `default` is also accepted as a hue-value, because the background palettes are
// using it as a name for the default hue.
if (hue.indexOf('hue') === 0 || hue === 'default') {
hue = theme.colors[colorType].hues[hue];
}
return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity );
});
// For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
angular.forEach(color.hues, function(hueValue, hueName) {
var newRule = rules
.replace(hueRegex, function(match, _, colorType, hueType, opacity) {
return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
});
if (hueName !== 'default') {
newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
}
// Don't apply a selector rule to the default theme, making it easier to override
// styles of the base-component
if (theme.name == 'default') {
var themeRuleRegex = /((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)\.md-default-theme((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;
newRule = newRule.replace(themeRuleRegex, function(match, start, end) {
return match + ', ' + start + end;
});
}
generatedRules.push(newRule);
});
return generatedRules;
}
var rulesByType = {};
// Generate our themes at run time given the state of THEMES and PALETTES
function generateAllThemes($injector, $mdTheming) {
var head = document.head;
var firstChild = head ? head.firstElementChild : null;
var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
// Append our custom registered styles to the theme stylesheet.
themeCss += themeConfig.registeredStyles.join('');
if ( !firstChild ) return;
if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
// Expose contrast colors for palettes to ensure that text is always readable
angular.forEach(PALETTES, sanitizePalette);
// MD_THEME_CSS is a string generated by the build process that includes all the themable
// components as templates
// Break the CSS into individual rules
var rules = themeCss
.split(/\}(?!(\}|'|"|;))/)
.filter(function(rule) { return rule && rule.trim().length; })
.map(function(rule) { return rule.trim() + '}'; });
var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
THEME_COLOR_TYPES.forEach(function(type) {
rulesByType[type] = '';
});
// Sort the rules based on type, allowing us to do color substitution on a per-type basis
rules.forEach(function(rule) {
var match = rule.match(ruleMatchRegex);
// First: test that if the rule has '.md-accent', it goes into the accent set of rules
for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
if (rule.indexOf('.md-' + type) > -1) {
return rulesByType[type] += rule;
}
}
// If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
// there
for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
if (rule.indexOf(type) > -1) {
return rulesByType[type] += rule;
}
}
// Default to the primary array
return rulesByType[DEFAULT_COLOR_TYPE] += rule;
});
// If themes are being generated on-demand, quit here. The user will later manually
// call generateTheme to do this on a theme-by-theme basis.
if (themeConfig.generateOnDemand) return;
angular.forEach($mdTheming.THEMES, function(theme) {
if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {
generateTheme(theme, theme.name, themeConfig.nonce);
}
});
// *************************
// Internal functions
// *************************
// The user specifies a 'default' contrast color as either light or dark,
// then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
function sanitizePalette(palette, name) {
var defaultContrast = palette.contrastDefaultColor;
var lightColors = palette.contrastLightColors || [];
var strongLightColors = palette.contrastStrongLightColors || [];
var darkColors = palette.contrastDarkColors || [];
// These colors are provided as space-separated lists
if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
// Cleanup after ourselves
delete palette.contrastDefaultColor;
delete palette.contrastLightColors;
delete palette.contrastStrongLightColors;
delete palette.contrastDarkColors;
// Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
angular.forEach(palette, function(hueValue, hueName) {
if (angular.isObject(hueValue)) return; // Already converted
// Map everything to rgb colors
var rgbValue = colorToRgbaArray(hueValue);
if (!rgbValue) {
throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
.replace('%1', hueValue)
.replace('%2', palette.name)
.replace('%3', hueName));
}
palette[hueName] = {
hex: palette[hueName],
value: rgbValue,
contrast: getContrastColor()
};
function getContrastColor() {
if (defaultContrast === 'light') {
if (darkColors.indexOf(hueName) > -1) {
return DARK_CONTRAST_COLOR;
} else {
return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
: LIGHT_CONTRAST_COLOR;
}
} else {
if (lightColors.indexOf(hueName) > -1) {
return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
: LIGHT_CONTRAST_COLOR;
} else {
return DARK_CONTRAST_COLOR;
}
}
}
});
}
}
function generateTheme(theme, name, nonce) {
var head = document.head;
var firstChild = head ? head.firstElementChild : null;
if (!GENERATED[name]) {
// For each theme, use the color palettes specified for
// `primary`, `warn` and `accent` to generate CSS rules.
THEME_COLOR_TYPES.forEach(function(colorType) {
var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
while (styleStrings.length) {
var styleContent = styleStrings.shift();
if (styleContent) {
var style = document.createElement('style');
style.setAttribute('md-theme-style', '');
if (nonce) {
style.setAttribute('nonce', nonce);
}
style.appendChild(document.createTextNode(styleContent));
head.insertBefore(style, firstChild);
}
}
});
GENERATED[theme.name] = true;
}
}
function checkValidPalette(theme, colorType) {
// If theme attempts to use a palette that doesnt exist, throw error
if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
throw new Error(
"You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
.replace('%1', theme.name)
.replace('%2', colorType)
.replace('%3', Object.keys(PALETTES).join(', '))
);
}
}
function colorToRgbaArray(clr) {
if (angular.isArray(clr) && clr.length == 3) return clr;
if (/^rgb/.test(clr)) {
return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
});
}
if (clr.charAt(0) == '#') clr = clr.substring(1);
if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
var dig = clr.length / 3;
var red = clr.substr(0, dig);
var grn = clr.substr(dig, dig);
var blu = clr.substr(dig * 2);
if (dig === 1) {
red += red;
grn += grn;
blu += blu;
}
return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
}
function rgba(rgbArray, opacity) {
if ( !rgbArray ) return "rgb('0,0,0')";
if (rgbArray.length == 4) {
rgbArray = angular.copy(rgbArray);
opacity ? rgbArray.pop() : opacity = rgbArray.pop();
}
return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
'rgb(' + rgbArray.join(',') + ')';
}
})(window.angular);