Skip to content
This repository was 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
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
@@ -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.
@@ -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
@@ -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;
};


@@ -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
@@ -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;

@@ -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);
@@ -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()
);
@@ -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) &&
@@ -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) {
@@ -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) {
@@ -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
);
@@ -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();

26 changes: 15 additions & 11 deletions src/components/panel/panel.scss
Original file line number Diff line number Diff line change
@@ -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.
@@ -31,7 +26,7 @@

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

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

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

@@ -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