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

fix(panel): allow transform to be animated on an offset panel #11389

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 66 additions & 49 deletions src/components/panel/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ angular
* - `locals` - `{Object=}`: An object containing key/value pairs. The keys
* will 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 3. 'mdPanelRef' is a reserved key, and will always
* with the value 3. 'mdPanelRef' is a reserved key, and will always
* be set to the created MdPanelRef instance.
* - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
* values. The panel will not open until all of the promises resolve.
Expand Down Expand Up @@ -1296,10 +1296,10 @@ MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {


/**
* Wraps the users template in two elements, md-panel-outer-wrapper, which
* covers the entire attachTo element, and md-panel, which contains only the
* template. This allows the panel control over positioning, animations,
* and similar properties.
* Wraps the user's template in three elements:
* - md-panel-outer-wrapper - covers the entire `attachTo` element.
* - md-panel-inner-wrapper - handles the positioning.
* - md-panel - contains the user's content and deals with the animations.
* @param {string} origTemplate The original template.
* @returns {string} The wrapped template.
* @private
Expand All @@ -1311,26 +1311,32 @@ MdPanelService.prototype._wrapTemplate = function(origTemplate) {
// height and width for positioning.
return '' +
'<div class="md-panel-outer-wrapper">' +
' <div class="md-panel _md-panel-offscreen">' + template + '</div>' +
'<div class="md-panel-inner-wrapper" style="left: -9999px;">' +
'<div class="md-panel _md-panel-offscreen">' + template + '</div>' +
'</div>' +
'</div>';
};


/**
* Wraps a content element in a md-panel-outer wrapper and
* positions it off-screen. Allows for proper control over positoning
* and animations.
* Wraps a content element in a `md-panel-outer-wrapper`, as well as
* a `md-panel-inner-wrapper`, and positions it off-screen. Allows for
* proper control over positoning and animations.
* @param {!angular.JQLite} contentElement Element to be wrapped.
* @return {!angular.JQLite} Wrapper element.
* @private
*/
MdPanelService.prototype._wrapContentElement = function(contentElement) {
var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
var outerWrapper = angular.element(
'<div class="md-panel-outer-wrapper">' +
'<div class="md-panel-inner-wrapper" style="left: -9999px;"></div>' +
'</div>'
);

contentElement.addClass('md-panel _md-panel-offscreen');
wrapper.append(contentElement);
outerWrapper.children().eq(0).append(contentElement);

return wrapper;
return outerWrapper;
};


Expand Down Expand Up @@ -1397,6 +1403,9 @@ function MdPanelRef(config, $injector) {
/** @type {!angular.JQLite|undefined} */
this.panelEl;

/** @type {!angular.JQLite|undefined} */
this.innerWrapper;

/**
* Whether the panel is attached. This is synchronous. When attach is called,
* isAttached is set to true. When detach is called, isAttached is set to
Expand Down Expand Up @@ -1839,6 +1848,11 @@ MdPanelRef.prototype._compile = function() {
);
}

// Save a reference to the inner wrapper.
self.innerWrapper = angular.element(
self.panelContainer[0].querySelector('.md-panel-inner-wrapper')
);

// Save a reference to the cleanup function from the compiler.
self._compilerCleanup = compileData.cleanup;

Expand Down Expand Up @@ -1915,14 +1929,17 @@ MdPanelRef.prototype._addStyles = function() {
var self = this;
return this._$q(function(resolve) {
self.panelContainer.css('z-index', self.config['zIndex']);
self.panelEl.css('z-index', self.config['zIndex'] + 1);
self.innerWrapper.css('z-index', self.config['zIndex'] + 1);

var hideAndResolve = function() {
// Theme the element and container.
self._setTheming();

// Remove offscreen class and add hidden class.
self.panelEl.removeClass('_md-panel-offscreen');

// Remove left: -9999px and add hidden class.
self.innerWrapper.css('left', '');
self.panelContainer.addClass(MD_PANEL_HIDDEN);

resolve(self);
Expand Down Expand Up @@ -1989,27 +2006,27 @@ MdPanelRef.prototype._updatePosition = function(init) {
var positionConfig = this.config['position'];

if (positionConfig) {
positionConfig._setPanelPosition(this.panelEl);
positionConfig._setPanelPosition(this.innerWrapper);

// Hide the panel now that position is known.
if (init) {
this.panelEl.removeClass('_md-panel-offscreen');
this.panelContainer.addClass(MD_PANEL_HIDDEN);
}

this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.TOP,
positionConfig.getTop()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.BOTTOM,
positionConfig.getBottom()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.LEFT,
positionConfig.getLeft()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.RIGHT,
positionConfig.getRight()
);
Expand Down Expand Up @@ -2916,38 +2933,38 @@ MdPanelPosition.prototype.getTransform = function() {


/**
* Sets the `transform` value for a panel element.
* @param {!angular.JQLite} panelEl
* Sets the `transform` value for an element.
* @param {!angular.JQLite} el
* @returns {!angular.JQLite}
* @private
*/
MdPanelPosition.prototype._setTransform = function(panelEl) {
return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
MdPanelPosition.prototype._setTransform = function(el) {
return el.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
};


/**
* True if the panel is completely on-screen with this positioning; false
* otherwise.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @return {boolean}
* @private
*/
MdPanelPosition.prototype._isOnscreen = function(panelEl) {
MdPanelPosition.prototype._isOnscreen = function(el) {
// this works because we always use fixed positioning for the panel,
// which is relative to the viewport.
var left = parseInt(this.getLeft());
var top = parseInt(this.getTop());

if (this._translateX.length || this._translateY.length) {
var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
var offsets = getComputedTranslations(panelEl, prefixedTransform);
var offsets = getComputedTranslations(el, prefixedTransform);
left += offsets.x;
top += offsets.y;
}

var right = left + panelEl[0].offsetWidth;
var bottom = top + panelEl[0].offsetHeight;
var right = left + el[0].offsetWidth;
var bottom = top + el[0].offsetHeight;

return (left >= 0) &&
(top >= 0) &&
Expand Down Expand Up @@ -2987,53 +3004,53 @@ MdPanelPosition.prototype._reduceTranslateValues =
/**
* Sets the panel position based on the created panel element and best x/y
* positioning.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @private
*/
MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
// Remove the "position adjusted" class in case it has been added before.
panelEl.removeClass('_md-panel-position-adjusted');
MdPanelPosition.prototype._setPanelPosition = function(el) {
// Remove the class in case it has been added before.
el.removeClass('_md-panel-position-adjusted');

// Only calculate the position if necessary.
if (this._absolute) {
this._setTransform(panelEl);
this._setTransform(el);
return;
}

if (this._actualPosition) {
this._calculatePanelPosition(panelEl, this._actualPosition);
this._setTransform(panelEl);
this._constrainToViewport(panelEl);
this._calculatePanelPosition(el, this._actualPosition);
this._setTransform(el);
this._constrainToViewport(el);
return;
}

for (var i = 0; i < this._positions.length; i++) {
this._actualPosition = this._positions[i];
this._calculatePanelPosition(panelEl, this._actualPosition);
this._setTransform(panelEl);
this._calculatePanelPosition(el, this._actualPosition);
this._setTransform(el);

if (this._isOnscreen(panelEl)) {
if (this._isOnscreen(el)) {
return;
}
}

this._constrainToViewport(panelEl);
this._constrainToViewport(el);
};


/**
* Constrains a panel's position to the viewport.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @private
*/
MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
MdPanelPosition.prototype._constrainToViewport = function(el) {
var margin = MdPanelPosition.viewportMargin;
var initialTop = this._top;
var initialLeft = this._left;

if (this.getTop()) {
var top = parseInt(this.getTop());
var bottom = panelEl[0].offsetHeight + top;
var bottom = el[0].offsetHeight + top;
var viewportHeight = this._$window.innerHeight;

if (top < margin) {
Expand All @@ -3045,7 +3062,7 @@ MdPanelPosition.prototype._constrainToViewport = function(panelEl) {

if (this.getLeft()) {
var left = parseInt(this.getLeft());
var right = panelEl[0].offsetWidth + left;
var right = el[0].offsetWidth + left;
var viewportWidth = this._$window.innerWidth;

if (left < margin) {
Expand All @@ -3056,7 +3073,7 @@ MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
}

// Class that can be used to re-style the panel if it was repositioned.
panelEl.toggleClass(
el.toggleClass(
'_md-panel-position-adjusted',
this._top !== initialTop || this._left !== initialLeft
);
Expand Down Expand Up @@ -3095,15 +3112,15 @@ MdPanelPosition.prototype._bidi = function(position) {
/**
* Calculates the panel position based on the created panel element and the
* provided positioning.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @param {!{x:string, y:string}} position
* @private
*/
MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
MdPanelPosition.prototype._calculatePanelPosition = function(el, position) {

var panelBounds = panelEl[0].getBoundingClientRect();
var panelWidth = Math.max(panelBounds.width, panelEl[0].clientWidth);
var panelHeight = Math.max(panelBounds.height, panelEl[0].clientHeight);
var panelBounds = el[0].getBoundingClientRect();
var panelWidth = Math.max(panelBounds.width, el[0].clientWidth);
var panelHeight = Math.max(panelBounds.height, el[0].clientHeight);

var targetBounds = this._relativeToEl[0].getBoundingClientRect();

Expand Down
26 changes: 15 additions & 11 deletions src/components/panel/panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,16 @@
width: 100%;
}

._md-panel-hidden {
display: none;
.md-panel-inner-wrapper {
position: fixed;
}

._md-panel-offscreen {
left: -9999px;
}

._md-panel-fullscreen {
border-radius: 0;
left: 0;
min-height: 100%;
min-width: 100%;
position: fixed;
top: 0;
._md-panel-hidden {
display: none;
}

// Only used when no animations are present.
Expand All @@ -31,7 +26,7 @@

.md-panel {
opacity: 0;
position: fixed;
position: relative;

&._md-panel-shown {
// Only used when custom animations are present.
Expand All @@ -57,7 +52,7 @@

&._md-panel-backdrop {
height: 100%;
position: absolute;
position: fixed;
width: 100%;
}

Expand All @@ -70,3 +65,12 @@
transition: opacity $material-leave-duration $material-leave-timing-function;
}
}

._md-panel-fullscreen {
border-radius: 0;
left: 0;
min-height: 100%;
min-width: 100%;
position: fixed;
top: 0;
}
Loading