Skip to content

Commit

Permalink
Currently, the PopupModal is always positioned relatively to the body…
Browse files Browse the repository at this point in the history
… element.

This change adds the ability to render the PopupModal inside an arbitrary parent element and center it relative to the parent.
RELNOTES[INC]: Add the ability to center a dialog inside a parent parent element.

PiperOrigin-RevId: 530611906
Change-Id: If6fe8510c0d7ef7c2cb01619834c4d0f68c3626b
  • Loading branch information
Closure Team authored and Copybara-Service committed May 9, 2023
1 parent a6fa5b0 commit eaf55f3
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 20 deletions.
1 change: 1 addition & 0 deletions closure/goog/ui/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,7 @@ closure_js_library(
"//closure/goog/events:eventtype",
"//closure/goog/events:focushandler",
"//closure/goog/fx:transition",
"//closure/goog/math:size",
"//closure/goog/string",
"//closure/goog/style",
"//closure/goog/timer",
Expand Down
89 changes: 69 additions & 20 deletions closure/goog/ui/modalpopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.FocusHandler');
goog.require('goog.fx.Transition');
goog.require('goog.math.Size');
goog.require('goog.string');
goog.require('goog.style');
goog.require('goog.ui.Component');
Expand Down Expand Up @@ -138,6 +139,15 @@ goog.ui.ModalPopup.prototype.tabCatcherElement_ = null;
goog.ui.ModalPopup.prototype.backwardTabWrapInProgress_ = false;


/**
* Whether to center the popup and the background inside the parent element.
* Otherwise, the default is to center inside the document body.
* @type {boolean}
* @private
*/
goog.ui.ModalPopup.prototype.centerInsideParent_ = false;


/**
* Transition to show the popup.
* @type {goog.fx.Transition}
Expand Down Expand Up @@ -450,6 +460,21 @@ goog.ui.ModalPopup.prototype.setTransition = function(
};


/**
* Sets the parent element to center the popup and the background inside the
* parent element.
* @param {boolean} centerInsideParent
*/
goog.ui.ModalPopup.prototype.setCenterInsideParentElement = function(
centerInsideParent) {
if (this.isInDocument()) {
throw new Error(
'Can\'t set parent element after component is already in document.');
}
this.centerInsideParent_ = centerInsideParent;
};


/**
* Shows the popup.
* @private
Expand Down Expand Up @@ -646,20 +671,26 @@ goog.ui.ModalPopup.prototype.resizeBackground_ = function() {
goog.style.setElementShown(this.bgEl_, false);
}

var doc = this.getDomHelper().getDocument();
var win = goog.dom.getWindow(doc) || window;

// Take the max of document height and view height, in case the document does
// not fill the viewport. Read from both the body element and the html element
// to account for browser differences in treatment of absolutely-positioned
// content.
var viewSize = goog.dom.getViewportSize(win);
var w = Math.max(
viewSize.width,
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth));
var h = Math.max(
viewSize.height,
Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight));
// Take the max of document height and parent element height, in case the
// document does not fill the parent element. Read from both the body element
// and the html element to account for browser differences in treatment of
// absolutely-positioned content.
let w;
let h;
if (this.centerInsideParent_) {
const parentEl = this.getElement().parentElement;
w = parentEl.clientWidth;
h = parentEl.clientHeight;
} else {
const doc = this.getDomHelper().getDocument();
const viewportSize = this.getDocumentViewportSize_();
w = Math.max(
viewportSize.width,
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth));
h = Math.max(
viewportSize.height,
Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight));
}

if (this.bgIframeEl_) {
goog.style.setElementShown(this.bgIframeEl_, true);
Expand All @@ -680,8 +711,6 @@ goog.ui.ModalPopup.prototype.reposition = function() {
// TODO(chrishenry): Make this use goog.positioning as in goog.ui.PopupBase?

// Get the current viewport to obtain the scroll offset.
var doc = this.getDomHelper().getDocument();
var win = goog.dom.getWindow(doc) || window;
if (goog.style.getComputedPosition(this.getElement()) == 'fixed') {
var x = 0;
var y = 0;
Expand All @@ -691,12 +720,20 @@ goog.ui.ModalPopup.prototype.reposition = function() {
var y = scroll.y;
}

var popupSize = goog.style.getSize(this.getElement());
var viewSize = goog.dom.getViewportSize(win);
// The popupSize should get calculated before viewSize to avoid losing focus
// on the action button.
const popupSize = goog.style.getSize(this.getElement());
let viewSize;
if (this.centerInsideParent_) {
const parentEl = this.getElement().parentElement;
viewSize = new goog.math.Size(parentEl.clientWidth, parentEl.clientHeight);
} else {
viewSize = this.getDocumentViewportSize_();
}

// Make sure left and top are non-negatives.
var left = Math.max(x + viewSize.width / 2 - popupSize.width / 2, 0);
var top = Math.max(y + viewSize.height / 2 - popupSize.height / 2, 0);
const left = Math.max(x + viewSize.width / 2 - popupSize.width / 2, 0);
const top = Math.max(y + viewSize.height / 2 - popupSize.height / 2, 0);
goog.style.setPosition(this.getElement(), left, top);

// We place the tab catcher at the same position as the dialog to
Expand Down Expand Up @@ -756,6 +793,18 @@ goog.ui.ModalPopup.prototype.focusElement_ = function() {
};


/**
* Returns the size of the element containing the background and the modal.
* @return {!goog.math.Size}
* @private
*/
goog.ui.ModalPopup.prototype.getDocumentViewportSize_ = function() {
const doc = this.getDomHelper().getDocument();
const win = goog.dom.getWindow(doc) || window;
return goog.dom.getViewportSize(win);
};


/** @override */
goog.ui.ModalPopup.prototype.disposeInternal = function() {
'use strict';
Expand Down
110 changes: 110 additions & 0 deletions closure/goog/ui/modalpopup_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,20 @@ testSuite({
dom.getActiveElement(document) == popup.getElement());
},

testPopupGetsFocus_withOptionalParent() {
const parentEl = dom.createElement(TagName.DIV);
document.body.appendChild(parentEl);
popup = new ModalPopup();
popup.setCenterInsideParentElement(true);
popup.render(parentEl);

popup.setVisible(true);

assertTrue(
'Dialog must receive initial focus',
dom.getActiveElement(document) == popup.getElement());
},

testDecoratedPopupGetsFocus() {
const dialogElem = dom.createElement(TagName.DIV);
document.body.appendChild(dialogElem);
Expand All @@ -465,4 +479,100 @@ testSuite({
dom.getActiveElement(document) == popup.getElement());
dom.removeNode(dialogElem);
},

testBackgroundSize_withoutOptionalParent_inheritBodySize() {
popup = new ModalPopup();
popup.render();
// Because the test does not add css, the size of body element changes
// after showing the background.
const documentHeight = document.documentElement.scrollHeight;
const documentWidth = document.documentElement.scrollWidth;

popup.setVisible(true);

const backgroundSize = style.getSize(popup.getBackgroundElement());
assertTrue(backgroundSize.width === documentWidth);
assertTrue(backgroundSize.height === documentHeight);
},

testBackgroundSize_withOptionalParent_backgroundInheritParentSize() {
const parentEl = dom.createElement(TagName.DIV);
document.body.appendChild(parentEl);
style.setSize(parentEl, 99, 88);
// Reinforce the test by changing the parent element size and assert the
// modal popup receives the right parent dimensions.
parentEl.style.transform = 'scale(0.5)';
parentEl.style.zoom = '50%';
parentEl.style.border = '20px solid';
popup = new ModalPopup();
popup.setCenterInsideParentElement(true);
popup.render(parentEl);

popup.setVisible(true);

const backgroundSize = style.getSize(popup.getBackgroundElement());
assertTrue(backgroundSize.width === 99);
assertTrue(backgroundSize.height === 88);
dom.removeNode(parentEl);
},

testPopupPosition_withoutOptionalParent_relativeToBody() {
popup = new ModalPopup();
popup.render();
style.setSize(popup.getElement(), 20, 20);

popup.setVisible(true);

const viewportSize = dom.getViewportSize();
const modalEl = popup.getElement();
const modalSize = style.getSize(modalEl);
const top = style.getComputedStyle(modalEl, 'top');
const expectedTop = viewportSize.height / 2 - modalSize.height / 2;
assertTrue(top === expectedTop + 'px');
const left = style.getComputedStyle(modalEl, 'left');
const expectedLeft = viewportSize.width / 2 - modalSize.width / 2;
assertTrue(left === expectedLeft + 'px');
},

testPopupPosition_withOptionalParent_relativeToBody() {
const parentEl = dom.createElement(TagName.DIV);
document.body.appendChild(parentEl);
popup = new ModalPopup();
popup.setCenterInsideParentElement(true);
popup.render(parentEl);
style.setSize(popup.getElement(), 20, 20);
style.setSize(parentEl, 99, 88);
// Reinforce the test by changing the parent element size and assert the
// modal popup receives the right parent dimensions.
parentEl.style.transform = 'scale(0.5)';
parentEl.style.zoom = '50%';
const borderThickness = 20;
parentEl.style.border = `${borderThickness}px solid`;

popup.setVisible(true);

const modalEl = popup.getElement();
const modalSize = style.getSize(modalEl);
const parentSize = style.getSize(parentEl);
const parentInnerHeight =
parentSize.height - borderThickness - borderThickness;
const expectedTop = parentInnerHeight / 2 - modalSize.height / 2;
const top = style.getComputedStyle(modalEl, 'top');
assertTrue(top === expectedTop + 'px');
const left = style.getComputedStyle(modalEl, 'left');
const parentInnerWidth =
parentSize.width - borderThickness - borderThickness;
const expectedLeft = parentInnerWidth / 2 - modalSize.width / 2;
assertTrue(left === expectedLeft + 'px');
dom.removeNode(parentEl);
},

testSetCenterInsideParentElementAfterRender_throws() {
const parentEl = dom.createElement(TagName.DIV);
document.body.appendChild(parentEl);
popup = new ModalPopup();
popup.render();

assertThrows(() => popup.setCenterInsideParentElement(true));
}
});

0 comments on commit eaf55f3

Please sign in to comment.