Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit a38e607

Browse files
committed
fix(dialog): remove focus trap focus listeners onRemove
- improve JSDoc and Closure types - fix typos - simplify `isNodeOneOf()` and ensure it returns a `boolean` - fix location of comments in dialog demo Relates to #11207
1 parent 0d431e0 commit a38e607

File tree

3 files changed

+102
-81
lines changed

3 files changed

+102
-81
lines changed

src/components/dialog/demoBasicUsage/script.js

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
angular.module('dialogDemo1', ['ngMaterial'])
2-
32
.controller('AppCtrl', function($scope, $mdDialog) {
43
$scope.status = ' ';
54
$scope.customFullscreen = false;
65

7-
$scope.showAlert = function(ev) {
8-
// Appending dialog to document.body to cover sidenav in docs app
9-
// Modal dialogs should fully cover application
10-
// to prevent interaction outside of dialog
6+
$scope.showAlert = function (ev) {
117
$mdDialog.show(
128
$mdDialog.alert()
139
.parent(angular.element(document.querySelector('#popupContainer')))
@@ -21,23 +17,22 @@ angular.module('dialogDemo1', ['ngMaterial'])
2117
};
2218

2319
$scope.showConfirm = function(ev) {
24-
// Appending dialog to document.body to cover sidenav in docs app
2520
var confirm = $mdDialog.confirm()
26-
.title('Would you like to delete your debt?')
27-
.textContent('All of the banks have agreed to forgive you your debts.')
28-
.ariaLabel('Lucky day')
29-
.targetEvent(ev)
30-
.ok('Please do it!')
31-
.cancel('Sounds like a scam');
21+
.title('Would you like to delete your debt?')
22+
.textContent('All of the banks have agreed to forgive you your debts.')
23+
.ariaLabel('Lucky day')
24+
.targetEvent(ev)
25+
.ok('Please do it!')
26+
.cancel('Sounds like a scam');
3227

33-
$mdDialog.show(confirm).then(function() {
28+
$mdDialog.show(confirm).then(function () {
3429
$scope.status = 'You decided to get rid of your debt.';
35-
}, function() {
30+
}, function () {
3631
$scope.status = 'You decided to keep your debt.';
3732
});
3833
};
3934

40-
$scope.showPrompt = function(ev) {
35+
$scope.showPrompt = function (ev) {
4136
// Appending dialog to document.body to cover sidenav in docs app
4237
var confirm = $mdDialog.prompt()
4338
.title('What would you name your dog?')
@@ -50,63 +45,67 @@ angular.module('dialogDemo1', ['ngMaterial'])
5045
.ok('Okay!')
5146
.cancel('I\'m a cat person');
5247

53-
$mdDialog.show(confirm).then(function(result) {
48+
$mdDialog.show(confirm).then(function (result) {
5449
$scope.status = 'You decided to name your dog ' + result + '.';
55-
}, function() {
50+
}, function () {
5651
$scope.status = 'You didn\'t name your dog.';
5752
});
5853
};
5954

60-
$scope.showAdvanced = function(ev) {
55+
$scope.showAdvanced = function (ev) {
6156
$mdDialog.show({
6257
controller: DialogController,
6358
templateUrl: 'dialog1.tmpl.html',
59+
// Appending dialog to document.body to cover sidenav in docs app
60+
// Modal dialogs should fully cover application to prevent interaction outside of dialog
6461
parent: angular.element(document.body),
6562
targetEvent: ev,
66-
clickOutsideToClose:true,
63+
clickOutsideToClose: true,
6764
fullscreen: $scope.customFullscreen // Only for -xs, -sm breakpoints.
68-
})
69-
.then(function(answer) {
65+
}).then(function (answer) {
7066
$scope.status = 'You said the information was "' + answer + '".';
71-
}, function() {
67+
}, function () {
7268
$scope.status = 'You cancelled the dialog.';
7369
});
7470
};
7571

76-
$scope.showTabDialog = function(ev) {
72+
$scope.showTabDialog = function (ev) {
7773
$mdDialog.show({
7874
controller: DialogController,
7975
templateUrl: 'tabDialog.tmpl.html',
76+
// Appending dialog to document.body to cover sidenav in docs app
77+
// Modal dialogs should fully cover application to prevent interaction outside of dialog
8078
parent: angular.element(document.body),
8179
targetEvent: ev,
8280
clickOutsideToClose: true
83-
})
84-
.then(function(answer) {
85-
$scope.status = 'You said the information was "' + answer + '".';
86-
}, function() {
87-
$scope.status = 'You cancelled the dialog.';
88-
});
81+
}).then(function (answer) {
82+
$scope.status = 'You said the information was "' + answer + '".';
83+
}, function () {
84+
$scope.status = 'You cancelled the dialog.';
85+
});
8986
};
9087

91-
$scope.showPrerenderedDialog = function(ev) {
88+
$scope.showPrerenderedDialog = function (ev) {
9289
$mdDialog.show({
9390
contentElement: '#myDialog',
91+
// Appending dialog to document.body to cover sidenav in docs app
92+
// Modal dialogs should fully cover application to prevent interaction outside of dialog
9493
parent: angular.element(document.body),
9594
targetEvent: ev,
9695
clickOutsideToClose: true
9796
});
9897
};
9998

10099
function DialogController($scope, $mdDialog) {
101-
$scope.hide = function() {
100+
$scope.hide = function () {
102101
$mdDialog.hide();
103102
};
104103

105-
$scope.cancel = function() {
104+
$scope.cancel = function () {
106105
$mdDialog.cancel();
107106
};
108107

109-
$scope.answer = function(answer) {
108+
$scope.answer = function (answer) {
110109
$mdDialog.hide(answer);
111110
};
112111
}

src/components/dialog/dialog.js

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
552552
function MdDialogProvider($$interimElementProvider) {
553553
// Elements to capture and redirect focus when the user presses tab at the dialog boundary.
554554
var topFocusTrap, bottomFocusTrap;
555+
var removeFocusTrap;
555556

556557
return $$interimElementProvider('$mdDialog')
557558
.setDefaults({
@@ -641,9 +642,9 @@ function MdDialogProvider($$interimElementProvider) {
641642
}
642643

643644
/* @ngInject */
644-
function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
645-
$log, $injector, $mdTheming, $interpolate, $mdInteraction) {
646-
645+
function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document,
646+
$window, $rootElement, $log, $injector, $mdTheming, $interpolate,
647+
$mdInteraction) {
647648
return {
648649
hasBackdrop: true,
649650
isolateScope: true,
@@ -768,12 +769,9 @@ function MdDialogProvider($$interimElementProvider) {
768769
options.hideBackdrop(options.$destroy);
769770

770771
// Remove the focus traps that we added earlier for keeping focus within the dialog.
771-
if (topFocusTrap && topFocusTrap.parentNode) {
772-
topFocusTrap.parentNode.removeChild(topFocusTrap);
773-
}
774-
775-
if (bottomFocusTrap && bottomFocusTrap.parentNode) {
776-
bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
772+
if (removeFocusTrap) {
773+
removeFocusTrap();
774+
removeFocusTrap = null;
777775
}
778776

779777
// For navigation $destroy events, do a quick, non-animated removal,
@@ -936,10 +934,8 @@ function MdDialogProvider($$interimElementProvider) {
936934

937935
// Queue remove listeners function
938936
removeListeners.push(function() {
939-
940937
element.off('keydown', keyHandlerFn);
941938
parentTarget.off('keydown', keyHandlerFn);
942-
943939
});
944940
}
945941

@@ -1015,11 +1011,13 @@ function MdDialogProvider($$interimElementProvider) {
10151011
*/
10161012
options.hideBackdrop = function hideBackdrop($destroy) {
10171013
if (options.backdrop) {
1018-
if ($destroy) options.backdrop.remove();
1019-
else $animate.leave(options.backdrop);
1014+
if ($destroy) {
1015+
options.backdrop.remove();
1016+
} else {
1017+
$animate.leave(options.backdrop);
1018+
}
10201019
}
10211020

1022-
10231021
if (options.disableParentScroll) {
10241022
options.restoreScroll && options.restoreScroll();
10251023
delete options.restoreScroll;
@@ -1088,15 +1086,27 @@ function MdDialogProvider($$interimElementProvider) {
10881086
topFocusTrap.addEventListener('focus', focusHandler);
10891087
bottomFocusTrap.addEventListener('focus', focusHandler);
10901088

1091-
// The top focus trap inserted immeidately before the md-dialog element (as a sibling).
1089+
removeFocusTrap = function () {
1090+
topFocusTrap.removeEventListener('focus', focusHandler);
1091+
bottomFocusTrap.removeEventListener('focus', focusHandler);
1092+
1093+
if (topFocusTrap && topFocusTrap.parentNode) {
1094+
topFocusTrap.parentNode.removeChild(topFocusTrap);
1095+
}
1096+
1097+
if (bottomFocusTrap && bottomFocusTrap.parentNode) {
1098+
bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
1099+
}
1100+
};
1101+
1102+
// The top focus trap inserted immediately before the md-dialog element (as a sibling).
10921103
// The bottom focus trap is inserted at the very end of the md-dialog element (as a child).
10931104
element[0].parentNode.insertBefore(topFocusTrap, element[0]);
10941105
element.after(bottomFocusTrap);
10951106
}
10961107

10971108
/**
1098-
* Prevents screen reader interaction behind modal window
1099-
* on swipe interfaces
1109+
* Prevents screen reader interaction behind modal window on swipe interfaces.
11001110
*/
11011111
function lockScreenReader(element, options) {
11021112
var isHidden = true;
@@ -1112,8 +1122,9 @@ function MdDialogProvider($$interimElementProvider) {
11121122
};
11131123

11141124
/**
1115-
* Get all of an element's parent elements up the DOM tree
1116-
* @return {Array} The parent elements
1125+
* Get all of an element's parent elements up the DOM tree.
1126+
* @param {Node & ParentNode} element the element to start from
1127+
* @return {Element[]} The parent elements
11171128
*/
11181129
function getParents(element) {
11191130
var parents = [];
@@ -1137,8 +1148,9 @@ function MdDialogProvider($$interimElementProvider) {
11371148
}
11381149

11391150
/**
1140-
* Walk DOM to apply or remove aria-hidden on sibling nodes
1141-
* and parent sibling nodes
1151+
* Walk DOM to apply or remove aria-hidden on sibling nodes and parent sibling nodes.
1152+
* @param {Element} element the element to start from when walking up the DOM
1153+
* @returns {void}
11421154
*/
11431155
function walkDOM(element) {
11441156
var elements = getParents(element);
@@ -1149,12 +1161,17 @@ function MdDialogProvider($$interimElementProvider) {
11491161
}
11501162

11511163
/**
1152-
* Ensure the dialog container fill-stretches to the viewport
1164+
* Ensure the dialog container fill-stretches to the viewport.
1165+
* @param {JQLite} container dialog container
1166+
* @param {Object} options
1167+
* @returns {function(): void} function that reverts the modified styles
11531168
*/
11541169
function stretchDialogContainerToViewport(container, options) {
11551170
var isFixed = $window.getComputedStyle($document[0].body).position === 'fixed';
11561171
var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
1157-
var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0;
1172+
var height = backdrop ?
1173+
Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10))))
1174+
: 0;
11581175

11591176
var previousStyles = {
11601177
top: container.css('top'),
@@ -1178,7 +1195,10 @@ function MdDialogProvider($$interimElementProvider) {
11781195
}
11791196

11801197
/**
1181-
* Dialog open and pop-in animation
1198+
* Dialog open and pop-in animation.
1199+
* @param {JQLite} container dialog container
1200+
* @param {Object} options
1201+
* @returns {*}
11821202
*/
11831203
function dialogPopIn(container, options) {
11841204
// Add the `md-dialog-container` to the DOM
@@ -1219,7 +1239,6 @@ function MdDialogProvider($$interimElementProvider) {
12191239
buildTranslateToOrigin(dialogEl, options.origin)
12201240
)
12211241
);
1222-
12231242
};
12241243

12251244
// Function to revert the generated animation styles on the dialog element.
@@ -1243,7 +1262,10 @@ function MdDialogProvider($$interimElementProvider) {
12431262
}
12441263

12451264
/**
1246-
* Dialog close and pop-out animation
1265+
* Dialog close and pop-out animation.
1266+
* @param {JQLite} container dialog container
1267+
* @param {Object} options
1268+
* @returns {*}
12471269
*/
12481270
function dialogPopOut(container, options) {
12491271
return options.reverseAnimate().then(function() {
@@ -1256,13 +1278,13 @@ function MdDialogProvider($$interimElementProvider) {
12561278
}
12571279

12581280
/**
1259-
* Utility function to filter out raw DOM nodes
1281+
* Utility function to filter out raw DOM nodes.
1282+
* @param {Node} elem
1283+
* @param {string[]} nodeTypeArray
1284+
* @returns {boolean}
12601285
*/
12611286
function isNodeOneOf(elem, nodeTypeArray) {
1262-
if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
1263-
return true;
1264-
}
1287+
return nodeTypeArray.indexOf(elem.nodeName) !== -1;
12651288
}
1266-
12671289
}
12681290
}

0 commit comments

Comments
 (0)