Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

update(panel): constrain panel to viewport boundries #9651

Merged
merged 1 commit into from
Oct 16, 2016
Merged
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
48 changes: 47 additions & 1 deletion src/components/panel/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,12 @@ MdPanelPosition.absPosition = {
LEFT: 'left'
};

/**
* Margin between the edges of a panel and the viewport.
* @const {number}
*/
MdPanelPosition.viewportMargin = 8;


/**
* Sets absolute positioning for the panel.
Expand Down Expand Up @@ -2525,6 +2531,9 @@ MdPanelPosition.prototype._reduceTranslateValues =
* @private
*/
MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
// Remove the class in case it has been added before.
panelEl.removeClass('_md-panel-position-adjusted');

// Only calculate the position if necessary.
if (this._absolute) {
return;
Expand All @@ -2539,12 +2548,49 @@ MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
this._actualPosition = this._positions[i];
this._calculatePanelPosition(panelEl, this._actualPosition);
if (this._isOnscreen(panelEl)) {
break;
return;
}
}

// Class that can be used to re-style the panel if it was repositioned.
panelEl.addClass('_md-panel-position-adjusted');
this._constrainToViewport(panelEl);
};


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

if (this.getTop()) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The md-datepicker code has a comment regarding "disabled body scrolling" which I think is applicable here as well. I need to understand the code more, but I think this may be a gap in the constrainToViewport implementation.

Comment from: https://github.com/angular/material/pull/9641/files#diff-31bf126782bc06608d149fe72237bf4eL640

If ng-material has disabled body scrolling (for example, if a dialog is open),
then it's possible that the already-scrolled body has a negative top/left. In this case,
we want to treat the "real" top as (0 - bodyRect.top). In a normal scrolling situation,
though, the top of the viewport should just be the body's scroll position.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt it, because the panel is always positioned relatively to the viewport (position: fixed). The current datepicker implementation positions the calendar relatively to the body (via position: absolute).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thank you.

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

if (top < margin) {
this._top = margin + 'px';
} else if (bottom > viewportHeight) {
this._top = top - (bottom - viewportHeight + margin) + 'px';
}
}

if (this.getLeft()) {
Copy link

@henrymana henrymana Oct 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one case when the panel width is 100% in which there is no right side margin. Should the intent of this issue cover that scenario as well? Or is that something to handle as a separate issue or by the user of the md-panel? I am able to adjust using "withXPosition('align-left')"... but I am just wondering if you think this should be performed by the constrainToViewport method.

Copy link
Member Author

@crisbeto crisbeto Oct 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. IMO the constrainToViewport method is a last-ditch effort to keep the panel in the viewport and it may not be successful (e.g. the element is wider than the viewport). Before mdPanel resorts to it, it tries all of the available positions to see which one stays in the viewport. If you want a bit more control, you can add extra positions via addPanelPosition and it'll try those first. Here's an example:

$mdPanel.newPanelPosition()
  .relativeTo('something')
  .addPanelPosition(this._mdPanel.xPosition.ALIGN_START, this._mdPanel.yPosition.BELOW)
  .addPanelPosition(this._mdPanel.xPosition.ALIGN_END, this._mdPanel.yPosition.BELOW)
  .addPanelPosition(this._mdPanel.xPosition.ALIGN_START, this._mdPanel.yPosition.ABOVE);

This will try to position it start/below, end/below, start/above before resorting to constraining it.

Also I think you asked yesterday about the "position adjusted" class. This is useful in the cases where the panel was constrained, because it allows you to do some specific styling (e.g. we use it in the datepicker to hide a part of the overlay).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do use multiple positions. The panel does not really go off the screen, but it touches the right edge of the viewport with no margin (so I set a negative offsetX). Thinking about it more, that could probably be handled by css instead. Thank you for this change. I am already trying out your changes.

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

if (left < margin) {
this._left = margin + 'px';
} else if (right > viewportWidth) {
this._left = left - (right - viewportWidth + margin) + 'px';
}
}
};

/**
* Switches between 'start' and 'end'.
* @param {string} position Horizontal position of the panel
Expand Down
Loading