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

Commit eaf55f3

Browse files
Closure Teamcopybara-github
Closure Team
authored andcommitted
Currently, the PopupModal is always positioned relatively to the body 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
1 parent a6fa5b0 commit eaf55f3

File tree

3 files changed

+180
-20
lines changed

3 files changed

+180
-20
lines changed

closure/goog/ui/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,7 @@ closure_js_library(
12861286
"//closure/goog/events:eventtype",
12871287
"//closure/goog/events:focushandler",
12881288
"//closure/goog/fx:transition",
1289+
"//closure/goog/math:size",
12891290
"//closure/goog/string",
12901291
"//closure/goog/style",
12911292
"//closure/goog/timer",

closure/goog/ui/modalpopup.js

+69-20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ goog.require('goog.events');
2222
goog.require('goog.events.EventType');
2323
goog.require('goog.events.FocusHandler');
2424
goog.require('goog.fx.Transition');
25+
goog.require('goog.math.Size');
2526
goog.require('goog.string');
2627
goog.require('goog.style');
2728
goog.require('goog.ui.Component');
@@ -138,6 +139,15 @@ goog.ui.ModalPopup.prototype.tabCatcherElement_ = null;
138139
goog.ui.ModalPopup.prototype.backwardTabWrapInProgress_ = false;
139140

140141

142+
/**
143+
* Whether to center the popup and the background inside the parent element.
144+
* Otherwise, the default is to center inside the document body.
145+
* @type {boolean}
146+
* @private
147+
*/
148+
goog.ui.ModalPopup.prototype.centerInsideParent_ = false;
149+
150+
141151
/**
142152
* Transition to show the popup.
143153
* @type {goog.fx.Transition}
@@ -450,6 +460,21 @@ goog.ui.ModalPopup.prototype.setTransition = function(
450460
};
451461

452462

463+
/**
464+
* Sets the parent element to center the popup and the background inside the
465+
* parent element.
466+
* @param {boolean} centerInsideParent
467+
*/
468+
goog.ui.ModalPopup.prototype.setCenterInsideParentElement = function(
469+
centerInsideParent) {
470+
if (this.isInDocument()) {
471+
throw new Error(
472+
'Can\'t set parent element after component is already in document.');
473+
}
474+
this.centerInsideParent_ = centerInsideParent;
475+
};
476+
477+
453478
/**
454479
* Shows the popup.
455480
* @private
@@ -646,20 +671,26 @@ goog.ui.ModalPopup.prototype.resizeBackground_ = function() {
646671
goog.style.setElementShown(this.bgEl_, false);
647672
}
648673

649-
var doc = this.getDomHelper().getDocument();
650-
var win = goog.dom.getWindow(doc) || window;
651-
652-
// Take the max of document height and view height, in case the document does
653-
// not fill the viewport. Read from both the body element and the html element
654-
// to account for browser differences in treatment of absolutely-positioned
655-
// content.
656-
var viewSize = goog.dom.getViewportSize(win);
657-
var w = Math.max(
658-
viewSize.width,
659-
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth));
660-
var h = Math.max(
661-
viewSize.height,
662-
Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight));
674+
// Take the max of document height and parent element height, in case the
675+
// document does not fill the parent element. Read from both the body element
676+
// and the html element to account for browser differences in treatment of
677+
// absolutely-positioned content.
678+
let w;
679+
let h;
680+
if (this.centerInsideParent_) {
681+
const parentEl = this.getElement().parentElement;
682+
w = parentEl.clientWidth;
683+
h = parentEl.clientHeight;
684+
} else {
685+
const doc = this.getDomHelper().getDocument();
686+
const viewportSize = this.getDocumentViewportSize_();
687+
w = Math.max(
688+
viewportSize.width,
689+
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth));
690+
h = Math.max(
691+
viewportSize.height,
692+
Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight));
693+
}
663694

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

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

694-
var popupSize = goog.style.getSize(this.getElement());
695-
var viewSize = goog.dom.getViewportSize(win);
723+
// The popupSize should get calculated before viewSize to avoid losing focus
724+
// on the action button.
725+
const popupSize = goog.style.getSize(this.getElement());
726+
let viewSize;
727+
if (this.centerInsideParent_) {
728+
const parentEl = this.getElement().parentElement;
729+
viewSize = new goog.math.Size(parentEl.clientWidth, parentEl.clientHeight);
730+
} else {
731+
viewSize = this.getDocumentViewportSize_();
732+
}
696733

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

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

758795

796+
/**
797+
* Returns the size of the element containing the background and the modal.
798+
* @return {!goog.math.Size}
799+
* @private
800+
*/
801+
goog.ui.ModalPopup.prototype.getDocumentViewportSize_ = function() {
802+
const doc = this.getDomHelper().getDocument();
803+
const win = goog.dom.getWindow(doc) || window;
804+
return goog.dom.getViewportSize(win);
805+
};
806+
807+
759808
/** @override */
760809
goog.ui.ModalPopup.prototype.disposeInternal = function() {
761810
'use strict';

closure/goog/ui/modalpopup_test.js

+110
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,20 @@ testSuite({
454454
dom.getActiveElement(document) == popup.getElement());
455455
},
456456

457+
testPopupGetsFocus_withOptionalParent() {
458+
const parentEl = dom.createElement(TagName.DIV);
459+
document.body.appendChild(parentEl);
460+
popup = new ModalPopup();
461+
popup.setCenterInsideParentElement(true);
462+
popup.render(parentEl);
463+
464+
popup.setVisible(true);
465+
466+
assertTrue(
467+
'Dialog must receive initial focus',
468+
dom.getActiveElement(document) == popup.getElement());
469+
},
470+
457471
testDecoratedPopupGetsFocus() {
458472
const dialogElem = dom.createElement(TagName.DIV);
459473
document.body.appendChild(dialogElem);
@@ -465,4 +479,100 @@ testSuite({
465479
dom.getActiveElement(document) == popup.getElement());
466480
dom.removeNode(dialogElem);
467481
},
482+
483+
testBackgroundSize_withoutOptionalParent_inheritBodySize() {
484+
popup = new ModalPopup();
485+
popup.render();
486+
// Because the test does not add css, the size of body element changes
487+
// after showing the background.
488+
const documentHeight = document.documentElement.scrollHeight;
489+
const documentWidth = document.documentElement.scrollWidth;
490+
491+
popup.setVisible(true);
492+
493+
const backgroundSize = style.getSize(popup.getBackgroundElement());
494+
assertTrue(backgroundSize.width === documentWidth);
495+
assertTrue(backgroundSize.height === documentHeight);
496+
},
497+
498+
testBackgroundSize_withOptionalParent_backgroundInheritParentSize() {
499+
const parentEl = dom.createElement(TagName.DIV);
500+
document.body.appendChild(parentEl);
501+
style.setSize(parentEl, 99, 88);
502+
// Reinforce the test by changing the parent element size and assert the
503+
// modal popup receives the right parent dimensions.
504+
parentEl.style.transform = 'scale(0.5)';
505+
parentEl.style.zoom = '50%';
506+
parentEl.style.border = '20px solid';
507+
popup = new ModalPopup();
508+
popup.setCenterInsideParentElement(true);
509+
popup.render(parentEl);
510+
511+
popup.setVisible(true);
512+
513+
const backgroundSize = style.getSize(popup.getBackgroundElement());
514+
assertTrue(backgroundSize.width === 99);
515+
assertTrue(backgroundSize.height === 88);
516+
dom.removeNode(parentEl);
517+
},
518+
519+
testPopupPosition_withoutOptionalParent_relativeToBody() {
520+
popup = new ModalPopup();
521+
popup.render();
522+
style.setSize(popup.getElement(), 20, 20);
523+
524+
popup.setVisible(true);
525+
526+
const viewportSize = dom.getViewportSize();
527+
const modalEl = popup.getElement();
528+
const modalSize = style.getSize(modalEl);
529+
const top = style.getComputedStyle(modalEl, 'top');
530+
const expectedTop = viewportSize.height / 2 - modalSize.height / 2;
531+
assertTrue(top === expectedTop + 'px');
532+
const left = style.getComputedStyle(modalEl, 'left');
533+
const expectedLeft = viewportSize.width / 2 - modalSize.width / 2;
534+
assertTrue(left === expectedLeft + 'px');
535+
},
536+
537+
testPopupPosition_withOptionalParent_relativeToBody() {
538+
const parentEl = dom.createElement(TagName.DIV);
539+
document.body.appendChild(parentEl);
540+
popup = new ModalPopup();
541+
popup.setCenterInsideParentElement(true);
542+
popup.render(parentEl);
543+
style.setSize(popup.getElement(), 20, 20);
544+
style.setSize(parentEl, 99, 88);
545+
// Reinforce the test by changing the parent element size and assert the
546+
// modal popup receives the right parent dimensions.
547+
parentEl.style.transform = 'scale(0.5)';
548+
parentEl.style.zoom = '50%';
549+
const borderThickness = 20;
550+
parentEl.style.border = `${borderThickness}px solid`;
551+
552+
popup.setVisible(true);
553+
554+
const modalEl = popup.getElement();
555+
const modalSize = style.getSize(modalEl);
556+
const parentSize = style.getSize(parentEl);
557+
const parentInnerHeight =
558+
parentSize.height - borderThickness - borderThickness;
559+
const expectedTop = parentInnerHeight / 2 - modalSize.height / 2;
560+
const top = style.getComputedStyle(modalEl, 'top');
561+
assertTrue(top === expectedTop + 'px');
562+
const left = style.getComputedStyle(modalEl, 'left');
563+
const parentInnerWidth =
564+
parentSize.width - borderThickness - borderThickness;
565+
const expectedLeft = parentInnerWidth / 2 - modalSize.width / 2;
566+
assertTrue(left === expectedLeft + 'px');
567+
dom.removeNode(parentEl);
568+
},
569+
570+
testSetCenterInsideParentElementAfterRender_throws() {
571+
const parentEl = dom.createElement(TagName.DIV);
572+
document.body.appendChild(parentEl);
573+
popup = new ModalPopup();
574+
popup.render();
575+
576+
assertThrows(() => popup.setCenterInsideParentElement(true));
577+
}
468578
});

0 commit comments

Comments
 (0)