| /** |
| * @license AngularJS v1.4.8 |
| * (c) 2010-2015 Google, Inc. http://angularjs.org |
| * License: MIT |
| */ |
| (function(window, angular, undefined) {'use strict'; |
| |
| /** |
| * @ngdoc module |
| * @name ngAria |
| * @description |
| * |
| * The `ngAria` module provides support for common |
| * [<abbr title="Accessible Rich Internet Applications">ARIA</abbr>](http://www.w3.org/TR/wai-aria/) |
| * attributes that convey state or semantic information about the application for users |
| * of assistive technologies, such as screen readers. |
| * |
| * <div doc-module-components="ngAria"></div> |
| * |
| * ## Usage |
| * |
| * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following |
| * directives are supported: |
| * `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`. |
| * |
| * Below is a more detailed breakdown of the attributes handled by ngAria: |
| * |
| * | Directive | Supported Attributes | |
| * |---------------------------------------------|----------------------------------------------------------------------------------------| |
| * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | |
| * | {@link ng.directive:ngShow ngShow} | aria-hidden | |
| * | {@link ng.directive:ngHide ngHide} | aria-hidden | |
| * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | |
| * | {@link module:ngMessages ngMessages} | aria-live | |
| * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | |
| * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role | |
| * |
| * Find out more information about each directive by reading the |
| * {@link guide/accessibility ngAria Developer Guide}. |
| * |
| * ##Example |
| * Using ngDisabled with ngAria: |
| * ```html |
| * <md-checkbox ng-disabled="disabled"> |
| * ``` |
| * Becomes: |
| * ```html |
| * <md-checkbox ng-disabled="disabled" aria-disabled="true"> |
| * ``` |
| * |
| * ##Disabling Attributes |
| * It's possible to disable individual attributes added by ngAria with the |
| * {@link ngAria.$ariaProvider#config config} method. For more details, see the |
| * {@link guide/accessibility Developer Guide}. |
| */ |
| /* global -ngAriaModule */ |
| var ngAriaModule = angular.module('ngAria', ['ng']). |
| provider('$aria', $AriaProvider); |
| |
| /** |
| * Internal Utilities |
| */ |
| var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; |
| |
| var isNodeOneOf = function(elem, nodeTypeArray) { |
| if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { |
| return true; |
| } |
| }; |
| /** |
| * @ngdoc provider |
| * @name $ariaProvider |
| * |
| * @description |
| * |
| * Used for configuring the ARIA attributes injected and managed by ngAria. |
| * |
| * ```js |
| * angular.module('myApp', ['ngAria'], function config($ariaProvider) { |
| * $ariaProvider.config({ |
| * ariaValue: true, |
| * tabindex: false |
| * }); |
| * }); |
| *``` |
| * |
| * ## Dependencies |
| * Requires the {@link ngAria} module to be installed. |
| * |
| */ |
| function $AriaProvider() { |
| var config = { |
| ariaHidden: true, |
| ariaChecked: true, |
| ariaDisabled: true, |
| ariaRequired: true, |
| ariaInvalid: true, |
| ariaMultiline: true, |
| ariaValue: true, |
| tabindex: true, |
| bindKeypress: true, |
| bindRoleForClick: true |
| }; |
| |
| /** |
| * @ngdoc method |
| * @name $ariaProvider#config |
| * |
| * @param {object} config object to enable/disable specific ARIA attributes |
| * |
| * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags |
| * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags |
| * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags |
| * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags |
| * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags |
| * - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags |
| * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags |
| * - **tabindex** – `{boolean}` – Enables/disables tabindex tags |
| * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and |
| * `<li>` elements with ng-click |
| * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div` |
| * using ng-click, making them more accessible to users of assistive technologies |
| * |
| * @description |
| * Enables/disables various ARIA attributes |
| */ |
| this.config = function(newConfig) { |
| config = angular.extend(config, newConfig); |
| }; |
| |
| function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { |
| return function(scope, elem, attr) { |
| var ariaCamelName = attr.$normalize(ariaAttr); |
| if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { |
| scope.$watch(attr[attrName], function(boolVal) { |
| // ensure boolean value |
| boolVal = negate ? !boolVal : !!boolVal; |
| elem.attr(ariaAttr, boolVal); |
| }); |
| } |
| }; |
| } |
| /** |
| * @ngdoc service |
| * @name $aria |
| * |
| * @description |
| * @priority 200 |
| * |
| * The $aria service contains helper methods for applying common |
| * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. |
| * |
| * ngAria injects common accessibility attributes that tell assistive technologies when HTML |
| * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, |
| * let's review a code snippet from ngAria itself: |
| * |
| *```js |
| * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { |
| * return $aria.$$watchExpr('ngDisabled', 'aria-disabled'); |
| * }]) |
| *``` |
| * Shown above, the ngAria module creates a directive with the same signature as the |
| * traditional `ng-disabled` directive. But this ngAria version is dedicated to |
| * solely managing accessibility attributes. The internal `$aria` service is used to watch the |
| * boolean attribute `ngDisabled`. If it has not been explicitly set by the developer, |
| * `aria-disabled` is injected as an attribute with its value synchronized to the value in |
| * `ngDisabled`. |
| * |
| * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do |
| * anything to enable this feature. The `aria-disabled` attribute is automatically managed |
| * simply as a silent side-effect of using `ng-disabled` with the ngAria module. |
| * |
| * The full list of directives that interface with ngAria: |
| * * **ngModel** |
| * * **ngShow** |
| * * **ngHide** |
| * * **ngClick** |
| * * **ngDblclick** |
| * * **ngMessages** |
| * * **ngDisabled** |
| * |
| * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each |
| * directive. |
| * |
| * |
| * ## Dependencies |
| * Requires the {@link ngAria} module to be installed. |
| */ |
| this.$get = function() { |
| return { |
| config: function(key) { |
| return config[key]; |
| }, |
| $$watchExpr: watchExpr |
| }; |
| }; |
| } |
| |
| |
| ngAriaModule.directive('ngShow', ['$aria', function($aria) { |
| return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); |
| }]) |
| .directive('ngHide', ['$aria', function($aria) { |
| return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); |
| }]) |
| .directive('ngModel', ['$aria', function($aria) { |
| |
| function shouldAttachAttr(attr, normalizedAttr, elem) { |
| return $aria.config(normalizedAttr) && !elem.attr(attr); |
| } |
| |
| function shouldAttachRole(role, elem) { |
| return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT'); |
| } |
| |
| function getShape(attr, elem) { |
| var type = attr.type, |
| role = attr.role; |
| |
| return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : |
| ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : |
| (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : |
| (type || role) === 'textbox' || elem[0].nodeName === 'TEXTAREA' ? 'multiline' : ''; |
| } |
| |
| return { |
| restrict: 'A', |
| require: '?ngModel', |
| priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value |
| compile: function(elem, attr) { |
| var shape = getShape(attr, elem); |
| |
| return { |
| pre: function(scope, elem, attr, ngModel) { |
| if (shape === 'checkbox' && attr.type !== 'checkbox') { |
| //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles |
| ngModel.$isEmpty = function(value) { |
| return value === false; |
| }; |
| } |
| }, |
| post: function(scope, elem, attr, ngModel) { |
| var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem) |
| && !isNodeOneOf(elem, nodeBlackList); |
| |
| function ngAriaWatchModelValue() { |
| return ngModel.$modelValue; |
| } |
| |
| function getRadioReaction() { |
| if (needsTabIndex) { |
| needsTabIndex = false; |
| return function ngAriaRadioReaction(newVal) { |
| var boolVal = (attr.value == ngModel.$viewValue); |
| elem.attr('aria-checked', boolVal); |
| elem.attr('tabindex', 0 - !boolVal); |
| }; |
| } else { |
| return function ngAriaRadioReaction(newVal) { |
| elem.attr('aria-checked', (attr.value == ngModel.$viewValue)); |
| }; |
| } |
| } |
| |
| function ngAriaCheckboxReaction() { |
| elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); |
| } |
| |
| switch (shape) { |
| case 'radio': |
| case 'checkbox': |
| if (shouldAttachRole(shape, elem)) { |
| elem.attr('role', shape); |
| } |
| if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) { |
| scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? |
| getRadioReaction() : ngAriaCheckboxReaction); |
| } |
| if (needsTabIndex) { |
| elem.attr('tabindex', 0); |
| } |
| break; |
| case 'range': |
| if (shouldAttachRole(shape, elem)) { |
| elem.attr('role', 'slider'); |
| } |
| if ($aria.config('ariaValue')) { |
| var needsAriaValuemin = !elem.attr('aria-valuemin') && |
| (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); |
| var needsAriaValuemax = !elem.attr('aria-valuemax') && |
| (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); |
| var needsAriaValuenow = !elem.attr('aria-valuenow'); |
| |
| if (needsAriaValuemin) { |
| attr.$observe('min', function ngAriaValueMinReaction(newVal) { |
| elem.attr('aria-valuemin', newVal); |
| }); |
| } |
| if (needsAriaValuemax) { |
| attr.$observe('max', function ngAriaValueMinReaction(newVal) { |
| elem.attr('aria-valuemax', newVal); |
| }); |
| } |
| if (needsAriaValuenow) { |
| scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { |
| elem.attr('aria-valuenow', newVal); |
| }); |
| } |
| } |
| if (needsTabIndex) { |
| elem.attr('tabindex', 0); |
| } |
| break; |
| case 'multiline': |
| if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) { |
| elem.attr('aria-multiline', true); |
| } |
| break; |
| } |
| |
| if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) { |
| scope.$watch(function ngAriaRequiredWatch() { |
| return ngModel.$error.required; |
| }, function ngAriaRequiredReaction(newVal) { |
| elem.attr('aria-required', !!newVal); |
| }); |
| } |
| |
| if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) { |
| scope.$watch(function ngAriaInvalidWatch() { |
| return ngModel.$invalid; |
| }, function ngAriaInvalidReaction(newVal) { |
| elem.attr('aria-invalid', !!newVal); |
| }); |
| } |
| } |
| }; |
| } |
| }; |
| }]) |
| .directive('ngDisabled', ['$aria', function($aria) { |
| return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []); |
| }]) |
| .directive('ngMessages', function() { |
| return { |
| restrict: 'A', |
| require: '?ngMessages', |
| link: function(scope, elem, attr, ngMessages) { |
| if (!elem.attr('aria-live')) { |
| elem.attr('aria-live', 'assertive'); |
| } |
| } |
| }; |
| }) |
| .directive('ngClick',['$aria', '$parse', function($aria, $parse) { |
| return { |
| restrict: 'A', |
| compile: function(elem, attr) { |
| var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); |
| return function(scope, elem, attr) { |
| |
| if (!isNodeOneOf(elem, nodeBlackList)) { |
| |
| if ($aria.config('bindRoleForClick') && !elem.attr('role')) { |
| elem.attr('role', 'button'); |
| } |
| |
| if ($aria.config('tabindex') && !elem.attr('tabindex')) { |
| elem.attr('tabindex', 0); |
| } |
| |
| if ($aria.config('bindKeypress') && !attr.ngKeypress) { |
| elem.on('keypress', function(event) { |
| var keyCode = event.which || event.keyCode; |
| if (keyCode === 32 || keyCode === 13) { |
| scope.$apply(callback); |
| } |
| |
| function callback() { |
| fn(scope, { $event: event }); |
| } |
| }); |
| } |
| } |
| }; |
| } |
| }; |
| }]) |
| .directive('ngDblclick', ['$aria', function($aria) { |
| return function(scope, elem, attr) { |
| if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { |
| elem.attr('tabindex', 0); |
| } |
| }; |
| }]); |
| |
| |
| })(window, window.angular); |