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

Commit 4efafcf

Browse files
bradrichThomasBurleson
authored andcommitted
fix(mdPanel): Propagation, CSS targeting, and dynamic position updating
Fixes #8968 by: - Adding the `propagateContainerEvents` option to the panel configuration that is defaulted to false. If true, the mdPanel API will allow all touch and pointer events to propagate through the mdPanel container, the 'outer-wrapper', to the elements behind it. Fixes #8980 by: - Adding a boolean parameter to the MdPanelRef `addClass`, `removeClass`, and `toggleClass` functions that defaults to false. When true, it will allow the function's primary class actions to be executed on the mdPanel element, instead of the container. - Adding a public `updatePosition` function that will take in a MdPanelPosition object parameter that it will overwrite the current configuration's position and then call the private `_updatePosition` function. Ping @ErinCoughlan Closes #8983
1 parent 1663341 commit 4efafcf

File tree

2 files changed

+268
-8
lines changed

2 files changed

+268
-8
lines changed

src/components/panel/panel.js

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ angular
108108
* - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
109109
* attach the panel to. Defaults to appending to the root element of the
110110
* application.
111+
* - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
112+
* events should be allowed to propagate 'go through' the container, aka the
113+
* wrapper, of the panel. Defaults to false.
111114
* - `panelClass` - `{string=}`: A css class to apply to the panel element.
112115
* This class should define any borders, box-shadow, etc. for the panel.
113116
* - `zIndex` - `{number=}`: The z-index to place the panel at.
@@ -308,6 +311,8 @@ angular
308311
* Adds a class to the panel. DO NOT use this to hide/show the panel.
309312
*
310313
* @param {string} newClass Class to be added.
314+
* @param {boolean} toElement Whether or not to add the class to the panel
315+
* element instead of the container.
311316
*/
312317

313318
/**
@@ -317,6 +322,8 @@ angular
317322
* Removes a class from the panel. DO NOT use this to hide/show the panel.
318323
*
319324
* @param {string} oldClass Class to be removed.
325+
* @param {boolean} fromElement Whether or not to remove the class from the
326+
* panel element instead of the container.
320327
*/
321328

322329
/**
@@ -326,6 +333,17 @@ angular
326333
* Toggles a class on the panel. DO NOT use this to hide/show the panel.
327334
*
328335
* @param {string} toggleClass Class to be toggled.
336+
* @param {boolean} onElement Whether or not to remove the class from the panel
337+
* element instead of the container.
338+
*/
339+
340+
/**
341+
* @ngdoc method
342+
* @name MdPanelRef#updatePosition
343+
* @description
344+
* Updates the position configuration of a panel. Use this to update the
345+
* position of a panel that is open, without having to close and re-open the
346+
* panel.
329347
*/
330348

331349
/**
@@ -628,6 +646,7 @@ function MdPanelService($rootElement, $rootScope, $injector, $window) {
628646
focusOnOpen: true,
629647
fullscreen: false,
630648
hasBackdrop: false,
649+
propagateContainerEvents: false,
631650
transformTemplate: angular.bind(this, this._wrapTemplate),
632651
trapFocus: false,
633652
zIndex: defaultZIndex
@@ -1057,14 +1076,18 @@ MdPanelRef.prototype.hide = function() {
10571076
* Add a class to the panel. DO NOT use this to hide/show the panel.
10581077
*
10591078
* @param {string} newClass Class to be added.
1079+
* @param {boolean} toElement Whether or not to add the class to the panel
1080+
* element instead of the container.
10601081
*/
1061-
MdPanelRef.prototype.addClass = function(newClass) {
1082+
MdPanelRef.prototype.addClass = function(newClass, toElement) {
10621083
if (!this._panelContainer) {
10631084
throw new Error('Panel does not exist yet. Call open() or attach().');
10641085
}
10651086

1066-
if (!this._panelContainer.hasClass(newClass)) {
1087+
if (!toElement && !this._panelContainer.hasClass(newClass)) {
10671088
this._panelContainer.addClass(newClass);
1089+
} else if (toElement && !this._panelEl.hasClass(newClass)) {
1090+
this._panelEl.addClass(newClass);
10681091
}
10691092
};
10701093

@@ -1073,14 +1096,18 @@ MdPanelRef.prototype.addClass = function(newClass) {
10731096
* Remove a class from the panel. DO NOT use this to hide/show the panel.
10741097
*
10751098
* @param {string} oldClass Class to be removed.
1099+
* @param {boolean} fromElement Whether or not to remove the class from the
1100+
* panel element instead of the container.
10761101
*/
1077-
MdPanelRef.prototype.removeClass = function(oldClass) {
1102+
MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
10781103
if (!this._panelContainer) {
10791104
throw new Error('Panel does not exist yet. Call open() or attach().');
10801105
}
10811106

1082-
if (this._panelContainer.hasClass(oldClass)) {
1107+
if (!fromElement && this._panelContainer.hasClass(oldClass)) {
10831108
this._panelContainer.removeClass(oldClass);
1109+
} else if (fromElement && this._panelEl.hasClass(oldClass)) {
1110+
this._panelEl.removeClass(oldClass);
10841111
}
10851112
};
10861113

@@ -1089,13 +1116,19 @@ MdPanelRef.prototype.removeClass = function(oldClass) {
10891116
* Toggle a class on the panel. DO NOT use this to hide/show the panel.
10901117
*
10911118
* @param {string} toggleClass The class to toggle.
1119+
* @param {boolean} onElement Whether or not to toggle the class on the panel
1120+
* element instead of the container.
10921121
*/
1093-
MdPanelRef.prototype.toggleClass = function(toggleClass) {
1122+
MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
10941123
if (!this._panelContainer) {
10951124
throw new Error('Panel does not exist yet. Call open() or attach().');
10961125
}
10971126

1098-
this._panelContainer.toggleClass(toggleClass);
1127+
if (!onElement) {
1128+
this._panelContainer.toggleClass(toggleClass);
1129+
} else {
1130+
this._panelEl.toggleClass(toggleClass);
1131+
}
10991132
};
11001133

11011134

@@ -1131,11 +1164,16 @@ MdPanelRef.prototype._createPanel = function() {
11311164
self._panelEl = angular.element(
11321165
self._panelContainer[0].querySelector('.md-panel'));
11331166

1134-
// Add a custom CSS class.
1167+
// Add a custom CSS class to the panel element.
11351168
if (self._config['panelClass']) {
11361169
self._panelEl.addClass(self._config['panelClass']);
11371170
}
11381171

1172+
// Handle click and touch events for the panel container.
1173+
if (self._config['propagateContainerEvents']) {
1174+
self._panelContainer.css('pointer-events', 'none');
1175+
}
1176+
11391177
// Panel may be outside the $rootElement, tell ngAnimate to animate
11401178
// regardless.
11411179
if (self._$animate.pin) {
@@ -1193,6 +1231,20 @@ MdPanelRef.prototype._addStyles = function() {
11931231
};
11941232

11951233

1234+
/**
1235+
* Updates the position configuration of a panel
1236+
* @param {MdPanelPosition} position
1237+
*/
1238+
MdPanelRef.prototype.updatePosition = function(position) {
1239+
if (!this._panelContainer) {
1240+
throw new Error('Panel does not exist yet. Call open() or attach().');
1241+
}
1242+
1243+
this._config['position'] = position;
1244+
this._updatePosition();
1245+
};
1246+
1247+
11961248
/**
11971249
* Calculates and updates the position of the panel.
11981250
* @param {boolean=} opt_init

src/components/panel/panel.spec.js

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,34 @@ describe('$mdPanel', function() {
409409
});
410410
});
411411

412+
describe('should cause the propagation of events', function() {
413+
var config, wrapper;
414+
415+
it('to be stopped when propagateContainerEvents=false', function() {
416+
config = {
417+
propagateContainerEvents: false,
418+
template: DEFAULT_TEMPLATE
419+
};
420+
421+
openPanel(config);
422+
423+
wrapper = angular.element(document.querySelector(PANEL_WRAPPER_CLASS));
424+
expect(wrapper.css('pointer-events')).not.toEqual('none');
425+
});
426+
427+
it('to NOT be stopped when propagateContainerEvents=true', function() {
428+
config = {
429+
propagateContainerEvents: true,
430+
template: DEFAULT_TEMPLATE
431+
};
432+
433+
openPanel(config);
434+
435+
wrapper = angular.element(document.querySelector(PANEL_WRAPPER_CLASS));
436+
expect(wrapper.css('pointer-events')).toEqual('none');
437+
});
438+
});
439+
412440
it('should apply a custom css class to the panel', function() {
413441
var customClass = 'custom-class';
414442

@@ -815,7 +843,6 @@ describe('$mdPanel', function() {
815843
expect(onRemovingCalled).toBe(true);
816844
});
817845

818-
819846
it('should call onDomRemoved if provided when removing the panel from ' +
820847
'the DOM', function() {
821848
var onDomRemovedCalled = false;
@@ -868,6 +895,87 @@ describe('$mdPanel', function() {
868895
});
869896
});
870897

898+
describe('CSS class logic:', function() {
899+
it('should add a class to the container/wrapper when toElement=false',
900+
function() {
901+
openPanel(DEFAULT_CONFIG);
902+
903+
panelRef.addClass('my-class');
904+
905+
expect(PANEL_WRAPPER_CLASS).toHaveClass('my-class');
906+
expect(PANEL_EL).not.toHaveClass('my-class');
907+
});
908+
909+
it('should add a class to the element when toElement=true', function() {
910+
openPanel(DEFAULT_CONFIG);
911+
912+
panelRef.addClass('my-class', true);
913+
914+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
915+
expect(PANEL_EL).toHaveClass('my-class');
916+
});
917+
918+
it('should remove a class from the container/wrapper when fromElement=false',
919+
function() {
920+
openPanel(DEFAULT_CONFIG);
921+
922+
panelRef.addClass('my-class');
923+
924+
expect(PANEL_WRAPPER_CLASS).toHaveClass('my-class');
925+
expect(PANEL_EL).not.toHaveClass('my-class');
926+
927+
panelRef.removeClass('my-class');
928+
929+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
930+
expect(PANEL_EL).not.toHaveClass('my-class');
931+
});
932+
933+
it('should remove a class from the element when fromElement=true',
934+
function() {
935+
openPanel(DEFAULT_CONFIG);
936+
937+
panelRef.addClass('my-class', true);
938+
939+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
940+
expect(PANEL_EL).toHaveClass('my-class');
941+
942+
panelRef.removeClass('my-class', true);
943+
944+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
945+
expect(PANEL_EL).not.toHaveClass('my-class');
946+
});
947+
948+
it('should toggle a class on the container/wrapper when onElement=false',
949+
function() {
950+
openPanel(DEFAULT_CONFIG);
951+
952+
panelRef.toggleClass('my-class');
953+
954+
expect(PANEL_WRAPPER_CLASS).toHaveClass('my-class');
955+
expect(PANEL_EL).not.toHaveClass('my-class');
956+
957+
panelRef.toggleClass('my-class');
958+
959+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
960+
expect(PANEL_EL).not.toHaveClass('my-class');
961+
});
962+
963+
it('should toggle a class on the element when onElement=true',
964+
function() {
965+
openPanel(DEFAULT_CONFIG);
966+
967+
panelRef.toggleClass('my-class', true);
968+
969+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
970+
expect(PANEL_EL).toHaveClass('my-class');
971+
972+
panelRef.toggleClass('my-class', true);
973+
974+
expect(PANEL_WRAPPER_CLASS).not.toHaveClass('my-class');
975+
expect(PANEL_EL).not.toHaveClass('n-class');
976+
});
977+
});
978+
871979
describe('should focus on the origin element on', function() {
872980
var myButton;
873981
var detachFocusConfig;
@@ -1115,6 +1223,106 @@ describe('$mdPanel', function() {
11151223
mdPanelPosition = $mdPanel.newPanelPosition();
11161224
});
11171225

1226+
describe('should update the position of an open panel', function() {
1227+
var xPosition, yPosition, myButton, myButtonRect;
1228+
1229+
beforeEach(function() {
1230+
xPosition = $mdPanel.xPosition;
1231+
yPosition = $mdPanel.yPosition;
1232+
1233+
myButton = '<button>myButton</button>';
1234+
attachToBody(myButton);
1235+
myButton = angular.element(document.querySelector('button'));
1236+
myButtonRect = myButton[0].getBoundingClientRect();
1237+
});
1238+
1239+
it('between two absolute positions', function() {
1240+
var top = '50px';
1241+
var left = '30px';
1242+
var position = $mdPanel.newPanelPosition()
1243+
.absolute()
1244+
.top(top)
1245+
.left(left);
1246+
1247+
config['position'] = position;
1248+
1249+
openPanel(config);
1250+
1251+
var panelRect = document.querySelector(PANEL_EL)
1252+
.getBoundingClientRect();
1253+
expect(panelRect.top).toBeApproximately(parseInt(top));
1254+
expect(panelRect.left).toBeApproximately(parseInt(left));
1255+
1256+
var newTop = '500px';
1257+
var newLeft = '300px';
1258+
var newPosition = $mdPanel.newPanelPosition()
1259+
.absolute()
1260+
.top(newTop)
1261+
.left(newLeft);
1262+
1263+
panelRef.updatePosition(newPosition);
1264+
1265+
var newPanelRect = document.querySelector(PANEL_EL)
1266+
.getBoundingClientRect();
1267+
expect(newPanelRect.top).toBeApproximately(parseInt(newTop));
1268+
expect(newPanelRect.left).toBeApproximately(parseInt(newLeft));
1269+
});
1270+
1271+
it('between two relative positions', function() {
1272+
var position = $mdPanel.newPanelPosition()
1273+
.relativeTo(myButton[0])
1274+
.addPanelPosition(xPosition.ALIGN_START, yPosition.ALIGN_TOPS);
1275+
1276+
config['position'] = position;
1277+
1278+
openPanel(config);
1279+
1280+
var panelRect = document.querySelector(PANEL_EL)
1281+
.getBoundingClientRect();
1282+
expect(panelRect.top).toBeApproximately(myButtonRect.top);
1283+
expect(panelRect.left).toBeApproximately(myButtonRect.left);
1284+
1285+
var newPosition = $mdPanel.newPanelPosition()
1286+
.relativeTo(myButton)
1287+
.addPanelPosition(null, yPosition.ABOVE);
1288+
1289+
panelRef.updatePosition(newPosition);
1290+
1291+
var newPanelRect = document.querySelector(PANEL_EL)
1292+
.getBoundingClientRect();
1293+
expect(newPanelRect.bottom).toBeApproximately(myButtonRect.top);
1294+
});
1295+
1296+
it('from an absolute to a relative position', function() {
1297+
var top = '250px';
1298+
var left = '400px';
1299+
var position = $mdPanel.newPanelPosition()
1300+
.absolute()
1301+
.top(top)
1302+
.left(left);
1303+
1304+
config['position'] = position;
1305+
1306+
openPanel(config);
1307+
1308+
var panelRect = document.querySelector(PANEL_EL)
1309+
.getBoundingClientRect();
1310+
expect(panelRect.top).toBeApproximately(parseInt(top));
1311+
expect(panelRect.left).toBeApproximately(parseInt(left));
1312+
1313+
var newPosition = $mdPanel.newPanelPosition()
1314+
.relativeTo(myButton[0])
1315+
.addPanelPosition(xPosition.ALIGN_START, yPosition.ALIGN_TOPS);
1316+
1317+
panelRef.updatePosition(newPosition);
1318+
1319+
var newPanelRect = document.querySelector(PANEL_EL)
1320+
.getBoundingClientRect();
1321+
expect(newPanelRect.top).toBeApproximately(myButtonRect.top);
1322+
expect(newPanelRect.left).toBeApproximately(myButtonRect.left);
1323+
});
1324+
});
1325+
11181326
describe('should offset the panel', function() {
11191327
it('horizontally', function() {
11201328
var left = '50px';

0 commit comments

Comments
 (0)