fix(overlays): prevent resize due to scrollbars on global overlay open (#746)
This commit is contained in:
parent
78dbfbed49
commit
c8da23ccb2
2 changed files with 71 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ async function preloadPopper() {
|
||||||
|
|
||||||
const GLOBAL_OVERLAYS_CONTAINER_CLASS = 'global-overlays__overlay-container';
|
const GLOBAL_OVERLAYS_CONTAINER_CLASS = 'global-overlays__overlay-container';
|
||||||
const GLOBAL_OVERLAYS_CLASS = 'global-overlays__overlay';
|
const GLOBAL_OVERLAYS_CLASS = 'global-overlays__overlay';
|
||||||
|
const supportsCSSTypedObject = window.CSS && CSS.number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc OverlayController is the fundament for every single type of overlay. With the right
|
* @desc OverlayController is the fundament for every single type of overlay. With the right
|
||||||
|
|
@ -383,7 +384,9 @@ export class OverlayController {
|
||||||
this.dispatchEvent(event);
|
this.dispatchEvent(event);
|
||||||
if (!event.defaultPrevented) {
|
if (!event.defaultPrevented) {
|
||||||
this._contentWrapperNode.style.display = '';
|
this._contentWrapperNode.style.display = '';
|
||||||
|
this._keepBodySize({ phase: 'before-show' });
|
||||||
await this._handleFeatures({ phase: 'show' });
|
await this._handleFeatures({ phase: 'show' });
|
||||||
|
this._keepBodySize({ phase: 'show' });
|
||||||
await this._handlePosition({ phase: 'show' });
|
await this._handlePosition({ phase: 'show' });
|
||||||
this.elementToFocusAfterHide = elementToFocusAfterHide;
|
this.elementToFocusAfterHide = elementToFocusAfterHide;
|
||||||
this.dispatchEvent(new Event('show'));
|
this.dispatchEvent(new Event('show'));
|
||||||
|
|
@ -410,6 +413,51 @@ export class OverlayController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_keepBodySize({ phase }) {
|
||||||
|
switch (phase) {
|
||||||
|
case 'before-show':
|
||||||
|
this.__bodyClientWidth = document.body.clientWidth;
|
||||||
|
this.__bodyClientHeight = document.body.clientHeight;
|
||||||
|
this.__bodyMarginRight = 0;
|
||||||
|
this.__bodyMarginBottom = 0;
|
||||||
|
break;
|
||||||
|
case 'show': {
|
||||||
|
if (supportsCSSTypedObject) {
|
||||||
|
this.__bodyMarginRight = document.body.computedStyleMap().get('margin-right').value;
|
||||||
|
this.__bodyMarginBottom = document.body.computedStyleMap().get('margin-bottom').value;
|
||||||
|
} else if (window.getComputedStyle) {
|
||||||
|
const bodyStyle = window.getComputedStyle(document.body);
|
||||||
|
if (bodyStyle) {
|
||||||
|
this.__bodyMarginRight = parseInt(bodyStyle.getPropertyValue('margin-right'), 10);
|
||||||
|
this.__bodyMarginBottom = parseInt(bodyStyle.getPropertyValue('margin-bottom'), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scrollbarWidth = document.body.clientWidth - this.__bodyClientWidth;
|
||||||
|
const scrollbarHeight = document.body.clientHeight - this.__bodyClientHeight;
|
||||||
|
const newMarginRight = this.__bodyMarginRight + scrollbarWidth;
|
||||||
|
const newMarginBottom = this.__bodyMarginBottom + scrollbarHeight;
|
||||||
|
if (supportsCSSTypedObject) {
|
||||||
|
document.body.attributeStyleMap.set('margin-right', CSS.px(newMarginRight));
|
||||||
|
document.body.attributeStyleMap.set('margin-bottom', CSS.px(newMarginBottom));
|
||||||
|
} else {
|
||||||
|
document.body.style.marginRight = `${newMarginRight}px`;
|
||||||
|
document.body.style.marginBottom = `${newMarginBottom}px`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hide':
|
||||||
|
if (supportsCSSTypedObject) {
|
||||||
|
document.body.attributeStyleMap.set('margin-right', CSS.px(this.__bodyMarginRight));
|
||||||
|
document.body.attributeStyleMap.set('margin-bottom', CSS.px(this.__bodyMarginBottom));
|
||||||
|
} else {
|
||||||
|
document.body.style.marginRight = `${this.__bodyMarginRight}px`;
|
||||||
|
document.body.style.marginBottom = `${this.__bodyMarginBottom}px`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
/* no default */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @event before-hide right before the overlay hides. Used for animations and switching overlays
|
* @event before-hide right before the overlay hides. Used for animations and switching overlays
|
||||||
* @event hide right after the overlay is hidden
|
* @event hide right after the overlay is hidden
|
||||||
|
|
@ -429,6 +477,7 @@ export class OverlayController {
|
||||||
// await this.transitionHide({ backdropNode: this.backdropNode, conentNode: this.contentNode });
|
// await this.transitionHide({ backdropNode: this.backdropNode, conentNode: this.contentNode });
|
||||||
this._contentWrapperNode.style.display = 'none';
|
this._contentWrapperNode.style.display = 'none';
|
||||||
this._handleFeatures({ phase: 'hide' });
|
this._handleFeatures({ phase: 'hide' });
|
||||||
|
this._keepBodySize({ phase: 'hide' });
|
||||||
this.dispatchEvent(new Event('hide'));
|
this.dispatchEvent(new Event('hide'));
|
||||||
this._restoreFocus();
|
this._restoreFocus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,28 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
|
||||||
expect(el.opened).to.be.false;
|
expect(el.opened).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not change the body size when opened', async () => {
|
||||||
|
const parentNode = document.createElement('div');
|
||||||
|
parentNode.setAttribute('style', 'height: 10000px; width: 10000px;');
|
||||||
|
const elWithBigParent = await fixture(
|
||||||
|
html`
|
||||||
|
<${tag}>
|
||||||
|
<div slot="content">content of the overlay</div>
|
||||||
|
<button slot="invoker">invoker button</button>
|
||||||
|
</${tag}>
|
||||||
|
`,
|
||||||
|
{ parentNode },
|
||||||
|
);
|
||||||
|
const { offsetWidth, offsetHeight } = elWithBigParent.offsetParent;
|
||||||
|
await elWithBigParent._overlayCtrl.show();
|
||||||
|
expect(elWithBigParent.opened).to.be.true;
|
||||||
|
expect(elWithBigParent.offsetParent.offsetWidth).to.equal(offsetWidth);
|
||||||
|
expect(elWithBigParent.offsetParent.offsetHeight).to.equal(offsetHeight);
|
||||||
|
await elWithBigParent._overlayCtrl.hide();
|
||||||
|
expect(elWithBigParent.offsetParent.offsetWidth).to.equal(offsetWidth);
|
||||||
|
expect(elWithBigParent.offsetParent.offsetHeight).to.equal(offsetHeight);
|
||||||
|
});
|
||||||
|
|
||||||
it('should respond to initially and dynamically setting the config', async () => {
|
it('should respond to initially and dynamically setting the config', async () => {
|
||||||
const itEl = await fixture(html`
|
const itEl = await fixture(html`
|
||||||
<${tag} .config=${{ trapsKeyboardFocus: false, viewportConfig: { placement: 'top' } }}>
|
<${tag} .config=${{ trapsKeyboardFocus: false, viewportConfig: { placement: 'top' } }}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue