diff --git a/src/components/toast/demoBasicUsage/script.js b/src/components/toast/demoBasicUsage/script.js
index 0f39479932c..8fdffe5ac17 100644
--- a/src/components/toast/demoBasicUsage/script.js
+++ b/src/components/toast/demoBasicUsage/script.js
@@ -1,5 +1,4 @@
-
-angular.module('toastDemo1', ['ngMaterial'])
+angular.module('toastBasicDemo', ['ngMaterial'])
.controller('AppCtrl', function($scope, $mdToast) {
var last = {
@@ -36,7 +35,7 @@ angular.module('toastDemo1', ['ngMaterial'])
$mdToast.show(
$mdToast.simple()
.textContent('Simple Toast!')
- .position(pinTo )
+ .position(pinTo)
.hideDelay(3000)
);
};
@@ -45,14 +44,18 @@ angular.module('toastDemo1', ['ngMaterial'])
var pinTo = $scope.getToastPosition();
var toast = $mdToast.simple()
.textContent('Marked as read')
+ .actionKey('z')
+ .actionHint('Press the Control-"z" key combination to ')
.action('UNDO')
+ .dismissHint('Activate the Escape key to dismiss this toast.')
.highlightAction(true)
- .highlightClass('md-accent')// Accent is used by default, this just demonstrates the usage.
- .position(pinTo);
+ .highlightClass('md-accent') // Accent is used by default, this just demonstrates the usage.
+ .position(pinTo)
+ .hideDelay(0);
$mdToast.show(toast).then(function(response) {
- if ( response == 'ok' ) {
- alert('You clicked the \'UNDO\' action.');
+ if (response === 'ok') {
+ alert('You selected the \'UNDO\' action.');
}
});
};
diff --git a/src/components/toast/demoCustomUsage/index.html b/src/components/toast/demoCustomUsage/index.html
index f0f76b19c78..0990c8eb9ec 100644
--- a/src/components/toast/demoCustomUsage/index.html
+++ b/src/components/toast/demoCustomUsage/index.html
@@ -1,13 +1,6 @@
-
-
-
-
- Toast can have multiple actions:
-
-
-
- Show Custom Toast
-
-
-
+
+ Toast can have multiple actions:
+
+ Show Custom Toast
+
diff --git a/src/components/toast/demoCustomUsage/script.js b/src/components/toast/demoCustomUsage/script.js
index 4ac7cd5a192..73cf23756f5 100644
--- a/src/components/toast/demoCustomUsage/script.js
+++ b/src/components/toast/demoCustomUsage/script.js
@@ -1,48 +1,100 @@
(function() {
-
var isDlgOpen;
+ var ACTION_RESOLVE = 'undo';
+ var UNDO_KEY = 'z';
+ var DIALOG_KEY = 'd';
+
+ angular.module('toastCustomDemo', ['ngMaterial'])
+ .controller('AppCtrl', AppCtrl)
+ .controller('ToastCtrl', ToastCtrl);
+
+ function AppCtrl($mdToast, $log) {
+ var ctrl = this;
+
+ ctrl.showCustomToast = function() {
+ $mdToast.show({
+ hideDelay: 0,
+ position: 'top right',
+ controller: 'ToastCtrl',
+ controllerAs: 'ctrl',
+ templateUrl: 'toast-template.html'
+ }).then(function(result) {
+ if (result === ACTION_RESOLVE) {
+ $log.log('Undo action triggered by button.');
+ } else if (result === 'key') {
+ $log.log('Undo action triggered by hot key: Control-' + UNDO_KEY + '.');
+ } else if (result === false) {
+ $log.log('Custom toast dismissed by Escape key.');
+ } else {
+ $log.log('Custom toast hidden automatically.');
+ }
+ }).catch(function(error) {
+ $log.error('Custom toast failure:', error);
+ });
+ };
+ }
+
+ function ToastCtrl($mdToast, $mdDialog, $document) {
+ var ctrl = this;
+ ctrl.keyListenerConfigured = false;
+ ctrl.undoKey = UNDO_KEY;
+ ctrl.dialogKey = DIALOG_KEY;
+ setupActionKeyListener();
+
+ ctrl.closeToast = function() {
+ if (isDlgOpen) {
+ return;
+ }
+
+ $mdToast.hide(ACTION_RESOLVE).then(function() {
+ isDlgOpen = false;
+ });
+ };
+
+ ctrl.openMoreInfo = function(e) {
+ if (isDlgOpen) {
+ return;
+ }
+ isDlgOpen = true;
+
+ $mdDialog.show(
+ $mdDialog.alert()
+ .title('More info goes here.')
+ .textContent('Something witty.')
+ .ariaLabel('More info')
+ .ok('Got it')
+ .targetEvent(e)
+ ).then(function() {
+ isDlgOpen = false;
+ });
+ };
+
+ /**
+ * @param {KeyboardEvent} event
+ */
+ function handleKeyDown(event) {
+ if (event.key === 'Escape') {
+ $mdToast.hide(false);
+ }
+ if (event.key === UNDO_KEY && event.ctrlKey) {
+ $mdToast.hide('key');
+ }
+ if (event.key === DIALOG_KEY && event.ctrlKey) {
+ ctrl.openMoreInfo(event);
+ }
+ }
+
+ function setupActionKeyListener() {
+ if (!ctrl.keyListenerConfigured) {
+ $document.on('keydown', handleKeyDown);
+ ctrl.keyListenerConfigured = true;
+ }
+ }
- angular
- .module('toastDemo2', ['ngMaterial'])
- .controller('AppCtrl', function($scope, $mdToast) {
- $scope.showCustomToast = function() {
- $mdToast.show({
- hideDelay : 3000,
- position : 'top right',
- controller : 'ToastCtrl',
- templateUrl : 'toast-template.html'
- });
- };
- })
- .controller('ToastCtrl', function($scope, $mdToast, $mdDialog) {
-
- $scope.closeToast = function() {
- if (isDlgOpen) return;
-
- $mdToast
- .hide()
- .then(function() {
- isDlgOpen = false;
- });
- };
-
- $scope.openMoreInfo = function(e) {
- if ( isDlgOpen ) return;
- isDlgOpen = true;
-
- $mdDialog
- .show($mdDialog
- .alert()
- .title('More info goes here.')
- .textContent('Something witty.')
- .ariaLabel('More info')
- .ok('Got it')
- .targetEvent(e)
- )
- .then(function() {
- isDlgOpen = false;
- });
- };
- });
+ function removeActionKeyListener() {
+ $document.off('keydown');
+ ctrl.keyListenerConfigured = false;
+ }
+ }
})();
diff --git a/src/components/toast/demoCustomUsage/style.scss b/src/components/toast/demoCustomUsage/style.scss
new file mode 100644
index 00000000000..75131efa74d
--- /dev/null
+++ b/src/components/toast/demoCustomUsage/style.scss
@@ -0,0 +1,9 @@
+#custom-toast-container {
+ height: 300px;
+ padding: 25px;
+
+ .md-button.md-raised {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+}
diff --git a/src/components/toast/demoCustomUsage/toast-template.html b/src/components/toast/demoCustomUsage/toast-template.html
index a729aee3074..7959e1aa748 100644
--- a/src/components/toast/demoCustomUsage/toast-template.html
+++ b/src/components/toast/demoCustomUsage/toast-template.html
@@ -1,9 +1,15 @@
-
- Custom toast!
-
+
+ Custom toast
+
+ Press Escape to dismiss. Press Control-"{{ctrl.dialogKey}}" for
+
+
More info
-
- Close
+
+ Press Control-"{{ctrl.undoKey}}" to
+
+
+ Undo
diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js
index ff8006d2550..56cf19a0c53 100644
--- a/src/components/toast/toast.js
+++ b/src/components/toast/toast.js
@@ -156,9 +156,30 @@ function MdToastDirective($mdToast) {
* `.action(string)` |
*
* Adds an action button.
- * If clicked, the promise (returned from `show()`)
- * will resolve with the value `'ok'`; otherwise, it is resolved with `true` after a `hideDelay`
- * timeout
+ * If clicked, the promise (returned from `show()`) will resolve with the value `'ok'`;
+ * otherwise, it is resolved with `true` after a `hideDelay` timeout.
+ * |
+ *
+ *
+ * `.actionKey(string)` |
+ *
+ * Adds a hotkey for the action button.
+ * If the `actionKey` and Control are pressed, the toast's action will be triggered.
+ * Defaults to the first character of the action if not defined.
+ * |
+ *
+ *
+ * `.actionHint(string)` |
+ *
+ * Text that a screen reader will announce to let the user know how to activate the
+ * action. Defaults to: "Press Control-"`actionKey`" to " followed by the action.
+ * |
+ *
+ *
+ * `.dismissHint(string)` |
+ *
+ * Text that a screen reader will announce to let the user know how to dismiss the toast.
+ * Defaults to: "Press Escape to dismiss."
* |
*
*
@@ -232,9 +253,11 @@ function MdToastDirective($mdToast) {
* - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
* This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
* - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
- * - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
- * active before automatically closing. Set to 0 or false to have the toast stay open until
- * closed manually. Default: 3000.
+ * - `hideDelay` - `{number=}`: The number of milliseconds the toast should stay active before
+ * automatically closing. Set to `0` or `false` to have the toast stay open until closed
+ * manually via an action in the toast, a hotkey, or a swipe gesture. For accessibility, toasts
+ * should not automatically close when they contain an action.
+ * Defaults to: `3000`.
* - `position` - `{string=}`: Sets the position of the toast.
* Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.
* The properties `'end'` and `'start'` are dynamic and can be used for RTL support.
@@ -247,7 +270,7 @@ function MdToastDirective($mdToast) {
* be used as names of values to inject into the controller. For example,
* `locals: {three: 3}` would inject `three` into the controller with the value
* of 3.
- * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
+ * - `bindToController` - `{boolean=}`: bind the locals to the controller, instead of passing them in.
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
* and the toast will not open until the promises resolve.
* - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
@@ -295,7 +318,8 @@ function MdToastDirective($mdToast) {
*/
function MdToastProvider($$interimElementProvider) {
- // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).
+ // Differentiate promise resolves: hide timeout (value == true) and hide action clicks
+ // (value == ok).
var ACTION_RESOLVE = 'ok';
var activeToastContent;
@@ -306,17 +330,22 @@ function MdToastProvider($$interimElementProvider) {
})
.addPreset('simple', {
argOption: 'textContent',
- methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],
+ methods: ['textContent', 'content', 'action', 'actionKey', 'actionHint', 'highlightAction',
+ 'highlightClass', 'theme', 'parent', 'dismissHint' ],
options: /* @ngInject */ function($mdToast, $mdTheming) {
return {
template:
'' +
- ' ' +
- '
' +
+ ' ' +
+ ' ' +
' {{ toast.content }}' +
' ' +
+ ' {{ toast.dismissHint }}' +
+ ' ' +
+ ' {{ toast.actionHint }}' +
+ ' ' +
' ' +
+ ' ng-class="highlightClasses">' +
' {{ toast.action }}' +
' ' +
'
' +
@@ -329,6 +358,8 @@ function MdToastProvider($$interimElementProvider) {
}
})
.addMethod('updateTextContent', updateTextContent)
+ // updateContent is deprecated. Use updateTextContent instead.
+ // TODO remove this in 1.2.
.addMethod('updateContent', updateTextContent);
function updateTextContent(newContent) {
@@ -354,18 +385,31 @@ function MdToastProvider($$interimElementProvider) {
];
}
+ // If no actionKey is defined, use the first char of the action name.
+ if (self.action && !self.actionKey) {
+ self.actionKey = self.action.charAt(0).toLocaleLowerCase();
+ }
+
+ if (self.actionKey && !self.actionHint) {
+ self.actionHint = 'Press Control-"' + self.actionKey + '" to ';
+ }
+
+ if (!self.dismissHint) {
+ self.dismissHint = 'Press Escape to dismiss.';
+ }
+
$scope.$watch(function() { return activeToastContent; }, function() {
self.content = activeToastContent;
});
this.resolve = function() {
- $mdToast.hide( ACTION_RESOLVE );
+ $mdToast.hide(ACTION_RESOLVE);
};
};
}
/* @ngInject */
- function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {
+ function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia, $document) {
var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';
return {
onShow: onShow,
@@ -409,7 +453,9 @@ function MdToastProvider($$interimElementProvider) {
};
function onShow(scope, element, options) {
- activeToastContent = options.textContent || options.content; // support deprecated #content method
+ // support deprecated #content method
+ // TODO remove support for content in 1.2.
+ activeToastContent = options.textContent || options.content;
var isSmScreen = !$mdMedia('gt-sm');
@@ -423,8 +469,8 @@ function MdToastProvider($$interimElementProvider) {
// If the swipe direction is down/up but the toast came from top/bottom don't fade away
// Unless the screen is small, then the toast always on bottom
- if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||
- (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen))) {
+ if ((direction === 'down' && options.position.indexOf('top') !== -1 && !isSmScreen) ||
+ (direction === 'up' && (options.position.indexOf('bottom') !== -1 || isSmScreen))) {
return;
}
@@ -447,23 +493,31 @@ function MdToastProvider($$interimElementProvider) {
options.parent.css('position', 'relative');
}
+ setupActionKeyListener(scope.toast ? scope.toast.actionKey : undefined);
element.on(SWIPE_EVENTS, options.onSwipe);
element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {
return 'md-' + pos;
}).join(' '));
- if (options.parent) options.parent.addClass('md-toast-animating');
+ if (options.parent) {
+ options.parent.addClass('md-toast-animating');
+ }
return $animate.enter(element, options.parent).then(function() {
- if (options.parent) options.parent.removeClass('md-toast-animating');
+ if (options.parent) {
+ options.parent.removeClass('md-toast-animating');
+ }
});
}
function onRemove(scope, element, options) {
+ if (scope.toast && scope.toast.actionKey) {
+ removeActionKeyListener();
+ }
element.off(SWIPE_EVENTS, options.onSwipe);
if (options.parent) options.parent.addClass('md-toast-animating');
if (options.openClass) options.parent.removeClass(options.openClass);
- return ((options.$destroy == true) ? element.remove() : $animate.leave(element))
+ return ((options.$destroy === true) ? element.remove() : $animate.leave(element))
.then(function () {
if (options.parent) options.parent.removeClass('md-toast-animating');
if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
@@ -478,9 +532,26 @@ function MdToastProvider($$interimElementProvider) {
return 'md-toast-open-bottom';
}
- return 'md-toast-open-' +
- (position.indexOf('top') > -1 ? 'top' : 'bottom');
+ return 'md-toast-open-' + (position.indexOf('top') > -1 ? 'top' : 'bottom');
}
- }
+ function setupActionKeyListener(actionKey) {
+ /**
+ * @param {KeyboardEvent} event
+ */
+ var handleKeyDown = function(event) {
+ if (event.key === 'Escape') {
+ $mdToast.hide(false);
+ }
+ if (actionKey && event.key === actionKey && event.ctrlKey) {
+ $mdToast.hide(ACTION_RESOLVE);
+ }
+ };
+ $document.on('keydown', handleKeyDown);
+ }
+
+ function removeActionKeyListener() {
+ $document.off('keydown');
+ }
+ }
}
diff --git a/src/components/toast/toast.spec.js b/src/components/toast/toast.spec.js
index 46d8c5c0e58..bc8284f4b0e 100644
--- a/src/components/toast/toast.spec.js
+++ b/src/components/toast/toast.spec.js
@@ -59,7 +59,7 @@ describe('$mdToast service', function() {
$material.flushOutstandingAnimations();
- expect(parent.find('span').text().trim()).toBe('Do something');
+ expect(parent.find('span').text().trim()).toContain('Do something');
expect(parent.find('span')).toHaveClass('md-toast-text');
expect(parent.find('md-toast')).toHaveClass('md-capsule');
expect(parent.find('md-toast').attr('md-theme')).toBe('some-theme');
@@ -69,13 +69,13 @@ describe('$mdToast service', function() {
expect(openAndclosed).toBe(true);
}));
- it('supports dynamicly updating the content', inject(function($mdToast, $rootScope, $rootElement) {
+ it('supports dynamically updating the content', inject(function($mdToast, $rootScope, $rootElement) {
var parent = angular.element('');
$mdToast.showSimple('Hello world');
$rootScope.$digest();
- $mdToast.updateContent('Goodbye world');
+ $mdToast.updateTextContent('Goodbye world');
$rootScope.$digest();
- expect($rootElement.find('span').text().trim()).toBe('Goodbye world');
+ expect($rootElement.find('span').text().trim()).toContain('Goodbye world');
}));
it('supports an action toast', inject(function($mdToast, $rootScope, $material) {