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

528 lines (445 loc) 17.3 kB
(function() { 'use strict'; var $mdUtil, $interpolate, $log; var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g; var WHITESPACE = /\s+/g; var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ]; var LAYOUT_OPTIONS = ['row', 'column']; var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ]; var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ]; var config = { /** * Enable directive attribute-to-class conversions * Developers can use `<body md-layout-css />` to quickly * disable the Layout directives and prohibit the injection of Layout classNames */ enabled: true, /** * List of mediaQuery breakpoints and associated suffixes * * [ * { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" }, * { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" } * ] */ breakpoints: [] }; registerLayoutAPI( angular.module('material.core.layout', ['ng']) ); /** * registerLayoutAPI() * * The original ngMaterial Layout solution used attribute selectors and CSS. * * ```html * <div layout="column"> My Content </div> * ``` * * ```css * [layout] { * box-sizing: border-box; * display:flex; * } * [layout=column] { * flex-direction : column * } * ``` * * Use of attribute selectors creates significant performance impacts in some * browsers... mainly IE. * * This module registers directives that allow the same layout attributes to be * interpreted and converted to class selectors. The directive will add equivalent classes to each element that * contains a Layout directive. * * ```html * <div layout="column" class="layout layout-column"> My Content </div> *``` * * ```css * .layout { * box-sizing: border-box; * display:flex; * } * .layout-column { * flex-direction : column * } * ``` */ function registerLayoutAPI(module){ var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA var BREAKPOINTS = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ]; var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ]; var API_NO_VALUES = [ "show", "hide", "layout-padding", "layout-margin" ]; // Build directive registration functions for the standard Layout API... for all breakpoints. angular.forEach(BREAKPOINTS, function(mqb) { // Attribute directives with expected, observable value(s) angular.forEach( API_WITH_VALUES, function(name){ var fullName = mqb ? name + "-" + mqb : name; module.directive( directiveNormalize(fullName), attributeWithObserve(fullName)); }); // Attribute directives with no expected value(s) angular.forEach( API_NO_VALUES, function(name){ var fullName = mqb ? name + "-" + mqb : name; module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName)); }); }); // Register other, special directive functions for the Layout features: module .provider('$$mdLayout' , function() { // Publish internal service for Layouts return { $get : angular.noop, validateAttributeValue : validateAttributeValue, validateAttributeUsage : validateAttributeUsage, /** * Easy way to disable/enable the Layout API. * When disabled, this stops all attribute-to-classname generations */ disableLayouts : function(isDisabled) { config.enabled = (isDisabled !== true); } }; }) .directive('mdLayoutCss' , disableLayoutDirective ) .directive('ngCloak' , buildCloakInterceptor('ng-cloak')) .directive('layoutWrap' , attributeWithoutValue('layout-wrap')) .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap')) .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap')) .directive('layoutFill' , attributeWithoutValue('layout-fill')) // !! Deprecated attributes: use the `-lt` (aka less-than) notations .directive('layoutLtMd' , warnAttrNotSupported('layout-lt-md', true)) .directive('layoutLtLg' , warnAttrNotSupported('layout-lt-lg', true)) .directive('flexLtMd' , warnAttrNotSupported('flex-lt-md', true)) .directive('flexLtLg' , warnAttrNotSupported('flex-lt-lg', true)) .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md')) .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg')) .directive('flexOrderLtMd' , warnAttrNotSupported('flex-order-lt-md')) .directive('flexOrderLtLg' , warnAttrNotSupported('flex-order-lt-lg')) .directive('offsetLtMd' , warnAttrNotSupported('flex-offset-lt-md')) .directive('offsetLtLg' , warnAttrNotSupported('flex-offset-lt-lg')) .directive('hideLtMd' , warnAttrNotSupported('hide-lt-md')) .directive('hideLtLg' , warnAttrNotSupported('hide-lt-lg')) .directive('showLtMd' , warnAttrNotSupported('show-lt-md')) .directive('showLtLg' , warnAttrNotSupported('show-lt-lg')) // Determine if .config( detectDisabledLayouts ); /** * Converts snake_case to camelCase. * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function directiveNormalize(name) { return name .replace(PREFIX_REGEXP, '') .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { return offset ? letter.toUpperCase() : letter; }); } } /** * Detect if any of the HTML tags has a [md-layouts-disabled] attribute; * If yes, then immediately disable all layout API features * * Note: this attribute should be specified on either the HTML or BODY tags */ /** * @ngInject */ function detectDisabledLayouts() { var isDisabled = !!document.querySelector('[md-layouts-disabled]'); config.enabled = !isDisabled; } /** * Special directive that will disable ALL Layout conversions of layout * attribute(s) to classname(s). * * <link rel="stylesheet" href="angular-material.min.css"> * <link rel="stylesheet" href="angular-material.layout.css"> * * <body md-layout-css> * ... * </body> * * Note: Using md-layout-css directive requires the developer to load the Material * Layout Attribute stylesheet (which only uses attribute selectors): * * `angular-material.layout.css` * * Another option is to use the LayoutProvider to configure and disable the attribute * conversions; this would obviate the use of the `md-layout-css` directive * */ function disableLayoutDirective() { // Return a 1x-only, first-match attribute directive config.enabled = false; return { restrict : 'A', priority : '900' }; } /** * Tail-hook ngCloak to delay the uncloaking while Layout transformers * finish processing. Eliminates flicker with Material.Layouts */ function buildCloakInterceptor(className) { return [ '$timeout', function($timeout){ return { restrict : 'A', priority : -10, // run after normal ng-cloak compile : function( element ) { if (!config.enabled) return angular.noop; // Re-add the cloak element.addClass(className); return function( scope, element ) { // Wait while layout injectors configure, then uncloak // NOTE: $rAF does not delay enough... and this is a 1x-only event, // $timeout is acceptable. $timeout( function(){ element.removeClass(className); }, 10, false); }; } }; }]; } // ********************************************************************************* // // These functions create registration functions for ngMaterial Layout attribute directives // This provides easy translation to switch ngMaterial attribute selectors to // CLASS selectors and directives; which has huge performance implications // for IE Browsers // // ********************************************************************************* /** * Creates a directive registration function where a possible dynamic attribute * value will be observed/watched. * @param {string} className attribute name; eg `layout-gt-md` with value ="row" */ function attributeWithObserve(className) { return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) { $mdUtil = _$mdUtil_; $interpolate = _$interpolate_; $log = _$log_; return { restrict: 'A', compile: function(element, attr) { var linkFn; if (config.enabled) { // immediately replace static (non-interpolated) invalid values... validateAttributeUsage(className, attr, element, $log); validateAttributeValue( className, getNormalizedAttrValue(className, attr, ""), buildUpdateFn(element, className, attr) ); linkFn = translateWithValueToCssClass; } // Use for postLink to account for transforms after ng-transclude. return linkFn || angular.noop; } }; }]; /** * Add as transformed class selector(s), then * remove the deprecated attribute selector */ function translateWithValueToCssClass(scope, element, attrs) { var updateFn = updateClassWithValue(element, className, attrs); var unwatch = attrs.$observe(attrs.$normalize(className), updateFn); updateFn(getNormalizedAttrValue(className, attrs, "")); scope.$on("$destroy", function() { unwatch(); }); } } /** * Creates a registration function for ngMaterial Layout attribute directive. * This is a `simple` transpose of attribute usage to class usage; where we ignore * any attribute value */ function attributeWithoutValue(className) { return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) { $mdUtil = _$mdUtil_; $interpolate = _$interpolate_; $log = _$log_; return { restrict: 'A', compile: function(element, attr) { var linkFn; if (config.enabled) { // immediately replace static (non-interpolated) invalid values... validateAttributeValue( className, getNormalizedAttrValue(className, attr, ""), buildUpdateFn(element, className, attr) ); translateToCssClass(null, element); // Use for postLink to account for transforms after ng-transclude. linkFn = translateToCssClass; } return linkFn || angular.noop; } }; }]; /** * Add as transformed class selector, then * remove the deprecated attribute selector */ function translateToCssClass(scope, element) { element.addClass(className); } } /** * After link-phase, do NOT remove deprecated layout attribute selector. * Instead watch the attribute so interpolated data-bindings to layout * selectors will continue to be supported. * * $observe() the className and update with new class (after removing the last one) * * e.g. `layout="{{layoutDemo.direction}}"` will update... * * NOTE: The value must match one of the specified styles in the CSS. * For example `flex-gt-md="{{size}}` where `scope.size == 47` will NOT work since * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined. * */ function updateClassWithValue(element, className) { var lastClass; return function updateClassFn(newValue) { var value = validateAttributeValue(className, newValue || ""); if ( angular.isDefined(value) ) { if (lastClass) element.removeClass(lastClass); lastClass = !value ? className : className + "-" + value.replace(WHITESPACE, "-"); element.addClass(lastClass); } }; } /** * Provide console warning that this layout attribute has been deprecated * */ function warnAttrNotSupported(className) { var parts = className.split("-"); return ["$log", function($log) { $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-<xxx>` variant."); return angular.noop; }]; } /** * Centralize warnings for known flexbox issues (especially IE-related issues) */ function validateAttributeUsage(className, attr, element, $log){ var message, usage, url; var nodeName = element[0].nodeName.toLowerCase(); switch(className.replace(SUFFIXES,"")) { case "flex": if ((nodeName == "md-button") || (nodeName == "fieldset")){ // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers // Use <div flex> wrapper inside (preferred) or outside usage = "<" + nodeName + " " + className + "></" + nodeName + ">"; url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers"; message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details."; $log.warn( $mdUtil.supplant(message, [usage, url]) ); } } } /** * For the Layout attribute value, validate or replace with default * fallback value */ function validateAttributeValue(className, value, updateFn) { var origValue = value; if (!needsInterpolation(value)) { switch (className.replace(SUFFIXES,"")) { case 'layout' : if ( !findIn(value, LAYOUT_OPTIONS) ) { value = LAYOUT_OPTIONS[0]; // 'row'; } break; case 'flex' : if (!findIn(value, FLEX_OPTIONS)) { if (isNaN(value)) { value = ''; } } break; case 'flex-offset' : case 'flex-order' : if (!value || isNaN(+value)) { value = '0'; } break; case 'layout-align' : var axis = extractAlignAxis(value); value = $mdUtil.supplant("{main}-{cross}",axis); break; case 'layout-padding' : case 'layout-margin' : case 'layout-fill' : case 'layout-wrap' : case 'layout-nowrap' : case 'layout-nowrap' : value = ''; break; } if (value != origValue) { (updateFn || angular.noop)(value); } } return value; } /** * Replace current attribute value with fallback value */ function buildUpdateFn(element, className, attrs) { return function updateAttrValue(fallback) { if (!needsInterpolation(fallback)) { // Do not modify the element's attribute value; so // uses '<ui-layout layout="/api/sidebar.html" />' will not // be affected. Just update the attrs value. attrs[attrs.$normalize(className)] = fallback; } }; } /** * See if the original value has interpolation symbols: * e.g. flex-gt-md="{{triggerPoint}}" */ function needsInterpolation(value) { return (value || "").indexOf($interpolate.startSymbol()) > -1; } function getNormalizedAttrValue(className, attrs, defaultVal) { var normalizedAttr = attrs.$normalize(className); return attrs[normalizedAttr] ? attrs[normalizedAttr].replace(WHITESPACE, "-") : defaultVal || null; } function findIn(item, list, replaceWith) { item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item; var found = false; if (item) { list.forEach(function(it) { it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it; found = found || (it === item); }); } return found; } function extractAlignAxis(attrValue) { var axis = { main : "start", cross: "stretch" }, values; attrValue = (attrValue || ""); if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) { // For missing main-axis values attrValue = "none" + attrValue; } values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-"); if ( values.length && (values[0] === "space") ) { // for main-axis values of "space-around" or "space-between" values = [ values[0]+"-"+values[1],values[2] ]; } if ( values.length > 0 ) axis.main = values[0] || axis.main; if ( values.length > 1 ) axis.cross = values[1] || axis.cross; if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 ) axis.main = "start"; if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch"; return axis; } })();