From a10749074ee05b5f12685c546c52165bf7420eb9 Mon Sep 17 00:00:00 2001 From: Thomas Burleson Date: Sun, 31 May 2015 20:00:02 -0500 Subject: [PATCH] feat(icons): add support for font-icons with ligatures and Material-Icon demos Closes #3059. --- .../index.html | 0 .../script.js | 2 +- .../style.css | 10 +- .../demoFontIconsWithLigatures/index.html | 31 ++++ .../icon/demoFontIconsWithLigatures/script.js | 23 +++ .../icon/demoFontIconsWithLigatures/style.css | 73 ++++++++++ src/components/icon/iconDirective.js | 137 ++++++++++++++++-- src/components/icon/iconDirective.spec.js | 96 ++++++++---- src/components/icon/iconService.js | 7 +- src/components/tooltip/tooltip.js | 2 +- 10 files changed, 328 insertions(+), 53 deletions(-) rename src/components/icon/{demoFontIcons => demoFontIconsWithClassnames}/index.html (100%) rename src/components/icon/{demoFontIcons => demoFontIconsWithClassnames}/script.js (94%) rename src/components/icon/{demoFontIcons => demoFontIconsWithClassnames}/style.css (92%) create mode 100644 src/components/icon/demoFontIconsWithLigatures/index.html create mode 100644 src/components/icon/demoFontIconsWithLigatures/script.js create mode 100644 src/components/icon/demoFontIconsWithLigatures/style.css diff --git a/src/components/icon/demoFontIcons/index.html b/src/components/icon/demoFontIconsWithClassnames/index.html similarity index 100% rename from src/components/icon/demoFontIcons/index.html rename to src/components/icon/demoFontIconsWithClassnames/index.html diff --git a/src/components/icon/demoFontIcons/script.js b/src/components/icon/demoFontIconsWithClassnames/script.js similarity index 94% rename from src/components/icon/demoFontIcons/script.js rename to src/components/icon/demoFontIconsWithClassnames/script.js index 2ab14e9498c..f48f3e6160a 100644 --- a/src/components/icon/demoFontIcons/script.js +++ b/src/components/icon/demoFontIconsWithClassnames/script.js @@ -1,6 +1,6 @@ angular - .module('appDemoFontIcons', ['ngMaterial']) + .module('appDemoFontIconsWithClassnames', ['ngMaterial']) .controller('DemoCtrl', function( $scope ) { // Create list of font-icon names with color overrides var iconData = [ diff --git a/src/components/icon/demoFontIcons/style.css b/src/components/icon/demoFontIconsWithClassnames/style.css similarity index 92% rename from src/components/icon/demoFontIcons/style.css rename to src/components/icon/demoFontIconsWithClassnames/style.css index 7c9cca858d5..68a0ce47756 100644 --- a/src/components/icon/demoFontIcons/style.css +++ b/src/components/icon/demoFontIconsWithClassnames/style.css @@ -1,11 +1,11 @@ -.appDemoFontIcons { +.appDemoFontIconsWithClassnames { padding:25px; width: 100%; } -.appDemoFontIcons, -.appDemoFontIcons *:before, -.appDemoFontIcons *:after { +.appDemoFontIconsWithClassnames, +.appDemoFontIconsWithClassnames *:before, +.appDemoFontIconsWithClassnames *:after { box-sizing: border-box; } @@ -18,7 +18,7 @@ line-height:1; font-weight:normal; font-style:normal; - speak:none; + text-decoration:inherit; text-transform:none; text-rendering:optimizeLegibility; diff --git a/src/components/icon/demoFontIconsWithLigatures/index.html b/src/components/icon/demoFontIconsWithLigatures/index.html new file mode 100644 index 00000000000..cfb6b2ac239 --- /dev/null +++ b/src/components/icon/demoFontIconsWithLigatures/index.html @@ -0,0 +1,31 @@ +
+ +

+ Display 4 Material Design font-icons using ligatures [instead of CSS names]; each with different sizes and colors
+

+ + +
+
+
+
+ + {{ font.name }} + +
+
+
+ +

+ Cool Tip: + Copy an icon and then paste in a text editor to see its textual name! +

+ + + +
diff --git a/src/components/icon/demoFontIconsWithLigatures/script.js b/src/components/icon/demoFontIconsWithLigatures/script.js new file mode 100644 index 00000000000..f63ac463593 --- /dev/null +++ b/src/components/icon/demoFontIconsWithLigatures/script.js @@ -0,0 +1,23 @@ + +angular + .module('appDemoFontIconsWithLigatures', ['ngMaterial']) + .controller('DemoCtrl', function( $scope ) { + // Specify a list of font-icons with ligatures and color overrides + var iconData = [ + {name: 'accessibility' , color: "#777" }, + {name: 'question_answer', color: "rgb(89, 226, 168)" }, + {name: 'backup' , color: "#A00" }, + {name: 'email' , color: "#00A" } + ]; + + $scope.fonts = [].concat(iconData); + + // Create a set of sizes... + $scope.sizes = [ + {size:"md-18",padding:0}, + {size:"md-24",padding:2}, + {size:"md-36",padding:6}, + {size:"md-48",padding:10} + ]; + + }); diff --git a/src/components/icon/demoFontIconsWithLigatures/style.css b/src/components/icon/demoFontIconsWithLigatures/style.css new file mode 100644 index 00000000000..0a1338dc79e --- /dev/null +++ b/src/components/icon/demoFontIconsWithLigatures/style.css @@ -0,0 +1,73 @@ + +.appDemoFontIconsWithLigatures { + padding:25px; + width: 100%; +} +.appDemoFontIconsWithLigatures, +.appDemoFontIconsWithLigatures *:before, +.appDemoFontIconsWithLigatures *:after { + box-sizing: border-box; +} + +header { + overflow: hidden; +} + +header h1 { + color: #888; + font-size: 36px; + font-weight: 300; +} + +.container { + margin: 0 auto; + max-width: 1200px; + min-width: 960px; + padding: 40px 40px; + font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.glyph { + border-bottom: 1px dotted #ccc; + padding: 10px 0 20px; + margin-bottom: 20px; +} + +.preview-scale, +.preview-glyphs { + display: flex; + flex-direction: row; +} + +.preview-scale { + color: #888; + font-size: 12px; + margin-top: 24px; +} + +.step { + flex-grow: 1; + line-height: 0.5; +} + +.usage { margin-top: 10px; } +.usage input { + font-family: monospace; + text-align: center; +} +.usage .point { width: 150px; } +.usage .class { width: 250px; } + +/* Rules for sizing the icon.*/ +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-24 { font-size: 24px; } +.material-icons.md-36 { font-size: 36px; } +.material-icons.md-48 { font-size: 48px; } + +/* Rules for using icons as black on a light background.*/ +.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } +.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } + +/* Rules for using icons as white on a dark background.*/ +.material-icons.md-light { color: rgba(255, 255, 255, 1); } +.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } diff --git a/src/components/icon/iconDirective.js b/src/components/icon/iconDirective.js index 3d1c8cffed4..540fc386367 100644 --- a/src/components/icon/iconDirective.js +++ b/src/components/icon/iconDirective.js @@ -17,44 +17,146 @@ angular.module('material.components.icon', [ * @restrict E * * @description - * The `md-icon` directive is an markup element useful for showing an icon based on a font-face - * or a SVG. Both external SVGs (via URLs) or cached SVG from icon sets can be - * easily loaded and used. + * The `` directive is an markup element useful for showing an icon based on a font-icon + * or a SVG. Icons are view-only elements that should not be used directly as buttons; instead nest a `` + * inside a `md-button` to add hover and click features. * + * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be + * easily loaded and used.When use font-icons, developers must following three (3) simple steps: + * + *
    + *
  1. Load the font library. e.g.
    + * <link href="https://fonts.googleapis.com/icon?family=Material+Icons" + * rel="stylesheet"> + *
  2. + *
  3. Use either (a) font-icon class names or (b) font ligatures to render the font glyph by using its textual name
  4. + *
  5. Use <md-icon md-font-icon="classname" /> or
    + * use <md-icon md-font-library="library_style_name"> textual_name </md-icon> or
    + * use <md-icon md-font-library="library_style_name"> numerical_character_reference </md-icon> + *
  6. + *
+ * + * Full details for these steps can be found: + * + *
    + *
  • http://google.github.io/material-design-icons/
  • + *
  • http://google.github.io/material-design-icons/#icon-font-for-the-web
  • + *
+ * + * The Material Design icon style .material-icons and the icon font references are published in + * Material Design Icons: + * + *
    + *
  • http://www.google.com/design/icons/
  • + *
  • https://www.google.com/design/icons/#ic_accessibility
  • + *
+ * + *

Material Design Icons

+ * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and + * determine its textual name and character reference code. Click on any icon to see the slide-up information + * panel with details regarding a SVG download or information on the font-icon usage. + * + * + * Material Design Icon-Selector + * + * + * + * Click on the image above to link to the + * Material Design Icon-Selector. + * + * + * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used + * to render the icon. Requires the fonts and the named CSS styles to be preloaded. + * @param {string} md-font-library String name of CSS icon associated with the font-face will be used + * to render the icon. Requires the fonts and the named CSS styles to be preloaded. * @param {string} md-svg-src String URL [or expression ] used to load, cache, and display an external SVG. * @param {string} md-svg-icon String name used for lookup of the icon from the internal cache; interpolated strings or * expressions may also be used. Specific set names can be used with the syntax `:`.

* To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service. - * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used - * to render the icon. Requires the fonts and the named CSS styles to be preloaded. * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon * nor a label on the parent element, a warning will be logged to the console. * * @usage + * When using SVGs: * - * - * + * + * + * + * + * * * + * * + * + * Use the $mdIconProvider to configure your application with + * svg iconsets. + * + * + * angular.module('appSvgIconSets', ['ngMaterial']) + * .controller('DemoCtrl', function($scope) {}) + * .config(function($mdIconProvider) { + * $mdIconProvider + * .iconSet('social', 'img/icons/sets/social-icons.svg', 24) + * .defaultIconSet('img/icons/sets/core-icons.svg', 24); + * }); + * + * + * + * When using Font Icons with classnames: + * + * + * + * + * + * + * + * When using Font Icons with ligatures: + * + * + * face + * #xE87C; + * face + * + * + * + * */ -function mdIconDirective($mdIcon, $mdTheming, $mdAria ) { +function mdIconDirective($mdIcon, $mdTheming, $mdAria, $interpolate ) { + return { scope: { + fontLib: '@mdFontLibrary', fontIcon: '@mdFontIcon', svgIcon: '@mdSvgIcon', svgSrc: '@mdSvgSrc' }, restrict: 'E', + transclude:true, template: getTemplate, link: postLink }; function getTemplate(element, attr) { - return attr.mdFontIcon ? '' : ''; + var hasAttrValue = function(key) { return attr[key] && attr[key].length }; + var attrValue = function(key) { return hasAttrValue(key) ? attr[key] : '' }; + + // If using font-icons, transclude the ligature or NRCs + var tmpl = hasAttrValue('mdFontIcon') ? '' : + hasAttrValue('mdFontLibrary') ? '' : ''; + + // Transpose the mdFontLibrary name to the list of classnames + // For example, Material Icons expects classnames like `.material-icons.md-48` instead of `.material-icons .md-48` + + var names = (attrValue('mdFontLibrary') + ' ' + attrValue('class')).trim(); + element.attr('class',names); + + return $interpolate( tmpl )({ classNames: names }); } + /** * Directive postLink * Supports embedded SVGs, font-icons, & external SVGs @@ -62,15 +164,20 @@ function mdIconDirective($mdIcon, $mdTheming, $mdAria ) { function postLink(scope, element, attr) { $mdTheming(element); + // If using a font-icon, then the textual name of the icon itself + // provides the aria-label. + var ariaLabel = attr.alt || scope.fontIcon || scope.svgIcon; var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || ''); - if (attr.alt != '' && !parentsHaveText()) { - $mdAria.expect(element, 'aria-label', ariaLabel); - $mdAria.expect(element, 'role', 'img'); - } else { - // Hide from the accessibility layer. - $mdAria.expect(element, 'aria-hidden', 'true'); + if ( !attr.mdFontLibrary ) { + if (attr.alt != '' && !parentsHaveText() ) { + $mdAria.expect(element, 'aria-label', ariaLabel); + $mdAria.expect(element, 'role', 'img'); + } else { + // Hide from the accessibility layer. + $mdAria.expect(element, 'aria-hidden', 'true'); + } } if (attrName) { diff --git a/src/components/icon/iconDirective.spec.js b/src/components/icon/iconDirective.spec.js index 36350f507fb..1f20f669c08 100644 --- a/src/components/icon/iconDirective.spec.js +++ b/src/components/icon/iconDirective.spec.js @@ -2,37 +2,28 @@ describe('mdIcon directive', function() { var el; var $scope; var $compile; - var $q; beforeEach(module('material.core')); beforeEach(module('material.components.icon')); - - var mockIconSvc = function(id) { - var deferred = $q.defer(); - - function getIcon(id) { - switch(id) { - case 'android': return ''; - case 'cake': return ''; - case 'android.svg': return ''; - case 'cake.svg': return ''; - } - } - - deferred.resolve(getIcon(id)); - return deferred.promise; - } - - function make(html) { - var el; - el = $compile(html)($scope); - $scope.$digest(); - return el; - } - beforeEach(function() { + var $q; + module(function($provide) { - $provide.value('$mdIcon', mockIconSvc); + $provide.value('$mdIcon', function $mdIconMock(id) { + + function getIcon(id) { + switch(id) { + case 'android' : return ''; + case 'cake' : return ''; + case 'android.svg': return ''; + case 'cake.svg' : return ''; + } + } + + return $q(function(resolve){ + resolve(getIcon(id)); + }); + }); }); inject(function($rootScope, _$compile_, _$q_){ @@ -40,18 +31,54 @@ describe('mdIcon directive', function() { $compile = _$compile_; $q = _$q_; }); + }); - describe('using md-font-icon=""', function() { + describe('using font-icons with classnames: md-font-icon=""', function() { it('should render correct HTML with md-font-icon value as class', function() { el = make( ''); - expect(el.html()).toEqual(''); + expect(el.html()).toEqual(''); + }); + + it('should transclude class specifiers', function() { + el = make( ''); + expect(el.html()).toEqual(''); + }); + + it('should not render any inner content if the md-font-icon value is empty', function() { + el = make( '' ); + expect(el.html()).toEqual(''); }); }); + describe('using font-icons with ligatures: md-font-library=""', function() { + + it('should render correct HTML with ligature and md-font-library value as class', function() { + el = make( 'face'); + + expect(el.html()).toEqual('face'); + expect(el.attr('class').indexOf('material-icons md-48')).toBeGreaterThan(-1); + }); + + it('should render correct HTML without aria-label', function() { + el = make( 'face'); + expect(el.html().indexOf('aria-label')).toEqual(-1); + }); + + it('should render correct HTML without aria-hidden', function() { + el = make( 'face'); + expect(el.html().indexOf('aria-hidden')).toEqual(-1); + }); + + it('should render correct HTML without role attribute', function() { + el = make( 'face'); + expect(el.html().indexOf('role')).toEqual(-1); + }); + }); + describe('using md-svg-icon=""', function() { it('should update mdSvgIcon when attribute value changes', function() { @@ -122,4 +149,17 @@ describe('mdIcon directive', function() { }); + + // **************************************************** + // Internal utility methods + // **************************************************** + + function make(html) { + var el; + el = $compile(html)($scope); + $scope.$digest(); + return el; + } + + }); diff --git a/src/components/icon/iconService.js b/src/components/icon/iconService.js index f47b4167c9c..4cae6b7fc17 100644 --- a/src/components/icon/iconService.js +++ b/src/components/icon/iconService.js @@ -17,6 +17,7 @@ * the `$mdIcon` service searches its registry for the associated source URL; * that URL is used to on-demand load and parse the SVG dynamically. * + * @usage * * app.config(function($mdIconProvider) { * @@ -95,7 +96,7 @@ * @description * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition * has an icon id. Individual icons can be subsequently retrieved from this cached set using - * `$mdIcon( : )` + * `$mdIcon(:)` * * @param {string} id Icon name/id used to register the iconset * @param {string} url specifies the external location for the data file. Used internally by `$http` to load the @@ -125,7 +126,7 @@ * @description * Register a source URL for the default 'named' set of icons. Unless explicitly registered, * subsequent lookups of icons will failover to search this 'default' icon set. - * Icon can be retrieved from this cached, default set using `$mdIcon( )` + * Icon can be retrieved from this cached, default set using `$mdIcon()` * * @param {string} url specifies the external location for the data file. Used internally by `$http` to load the * data or as part of the lookup in `$templateCache` if pre-loading was configured. @@ -298,7 +299,7 @@ * }; * * - * NOTE: The `md-icon` directive internally uses the `$mdIcon` service to query, loaded, and instantiate + * NOTE: The ` ` directive internally uses the `$mdIcon` service to query, loaded, and instantiate * SVG DOM elements. */ function MdIconService(config, $http, $q, $log, $templateCache) { diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js index 051daebc3d6..28415ae8d9a 100644 --- a/src/components/tooltip/tooltip.js +++ b/src/components/tooltip/tooltip.js @@ -135,7 +135,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe }; var leaveHandler = function () { var autohide = scope.hasOwnProperty('autohide') ? scope.autohide : attr.hasOwnProperty('mdAutohide'); - if ($document[0].activeElement !== parent[0] || autohide || mouseActive) { + if (autohide || mouseActive || ($document[0].activeElement !== parent[0]) ) { setVisible(false); } mouseActive = false;