Skip to content

Commit

Permalink
fixed height auto adjustment and iframe styles in modal
Browse files Browse the repository at this point in the history
  • Loading branch information
mirafedas committed Mar 11, 2024
1 parent 2f212d1 commit f533f3d
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 173 deletions.
20 changes: 12 additions & 8 deletions libs/blocks/modal/modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@
z-index: 103;
}

.dialog-modal.commerce-frame .fragment,
.dialog-modal.commerce-frame .section {
height: 100vh;
}

.dialog-modal.upgrade-flow-modal {
height: 100%;
width: 100%;
Expand Down Expand Up @@ -193,7 +188,7 @@
}
}

@media (max-width: 1200px) {
@media (max-width: 1199px) {
.dialog-modal.commerce-frame {
width: 100%;
max-width: 100%;
Expand All @@ -203,7 +198,11 @@
.dialog-modal.upgrade-flow-modal {
border-radius: 0;
}


.dialog-modal.commerce-frame .fragment,
.dialog-modal.commerce-frame .section {
height: 100vh;
}
}

@media (min-width: 1200px) {
Expand All @@ -221,6 +220,11 @@
height: 850px;
}

.dialog-modal.commerce-frame .fragment,
.dialog-modal.commerce-frame .section {
height: 100%;
}

.dialog-modal.commerce-frame .milo-iframe {
padding-bottom: 0;
height: 100%;
Expand All @@ -237,7 +241,7 @@
max-height: 845px;
}

.dialog-modal.commerce-frame.height-fit-content .milo-iframe iframe {
.dialog-modal.commerce-frame .milo-iframe iframe {
height: 0%;
}

Expand Down
69 changes: 12 additions & 57 deletions libs/blocks/modal/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ const CLOSE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="2
<line x1="8" y1="8" transform="translate(10506 -3397)" fill="none" stroke="#fff" stroke-width="2"/>
</g>
</svg>`;
const MOBILE_MAX = 599;
const TABLET_MAX = 1199;
let messageAbortController;
let resizeAbortController;

export function findDetails(hash, el) {
const id = hash.replace('#', '');
Expand All @@ -39,9 +35,6 @@ export function closeModal(modal) {
const localeModal = id?.includes('locale-modal') ? 'localeModal' : 'milo';
const analyticsEventName = window.location.hash ? window.location.hash.replace('#', '') : localeModal;
const closeEventAnalytics = new Event(`${analyticsEventName}:modalClose:buttonClose`);
// removing the 'message' and 'resize' event listener set for commerce modals
messageAbortController?.abort();
resizeAbortController?.abort();

sendAnalytics(closeEventAnalytics);

Expand Down Expand Up @@ -99,44 +92,6 @@ async function getPathModal(path, dialog) {
await getFragment(block);
}

function sendViewportDimensionsToiFrame(source) {
const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
source.postMessage({ mobileMax: MOBILE_MAX, tabletMax: TABLET_MAX, viewportWidth }, '*');
}

export function sendViewportDimensionsOnRequest({ messageInfo, debounce }) {
const { data, source } = messageInfo || {};
if (data !== 'viewportWidth' || !source || !debounce) return;
resizeAbortController = new AbortController();
sendViewportDimensionsToiFrame(source);
window.addEventListener('resize', debounce(() => sendViewportDimensionsToiFrame(source), 10), { signal: resizeAbortController.signal });
}

/** For the modal height adjustment to work the following conditions must be met:
* 1. The modal must have classes 'commerce-frame height-fit-content';
* 2. The iframe inside must send a postMessage with the contentHeight (a number of px or '100%);
*/
function adjustModalHeight({ contentHeight, dialog }) {
const iframe = dialog?.querySelector('iframe');
const iframeWrapper = dialog?.querySelector('.milo-iframe');
if (!contentHeight || !iframe || !iframeWrapper) return;
if (contentHeight === '100%') {
// the initial iframe height was set to 0 in CSS for the content height to be measured properly
iframe.style.height = '100%';
iframeWrapper.style.height = contentHeight;
dialog.style.height = contentHeight;
} else {
const verticalMargins = 20;
const clientHeight = document.documentElement.clientHeight - verticalMargins;
if (clientHeight <= 0) return;
const newHeight = contentHeight > clientHeight ? clientHeight : contentHeight;
// the initial iframe height was set to 0 in CSS for the content height to be measured properly
iframe.style.height = '100%';
iframeWrapper.style.height = `${newHeight}px`;
dialog.style.height = `${newHeight}px`;
}
}

export async function getModal(details, custom) {
if (!(details?.path || custom)) return null;
const { id } = details || custom;
Expand Down Expand Up @@ -205,19 +160,19 @@ export async function getModal(details, custom) {
[...document.querySelectorAll('header, main, footer')]
.forEach((element) => element.setAttribute('aria-disabled', 'true'));
}
if (dialog.classList.contains('commerce-frame')) {
const { debounce } = await import('../../utils/action.js');
messageAbortController = new AbortController();
window.addEventListener('message', (messageInfo) => {
if (dialog.classList.contains('height-fit-content')) {
adjustModalHeight({ contentHeight: messageInfo?.data?.contentHeight, dialog });
}
/* If the page inside iFrame comes from another domain, it won't be able to retrieve
the viewport dimensions, so it sends a request to receive the viewport dimensions
from the parent window. */
sendViewportDimensionsOnRequest({ debounce, messageInfo });
}, { signal: messageAbortController.signal });

const iframe = dialog.querySelector('iframe');
if (iframe) {
if (dialog.classList.contains('commerce-frame')) {
const { default: enableCommerceFrameFeatures } = await import('./modal.merch.js');
await enableCommerceFrameFeatures({ dialog, iframe });
} else {
/* Initially iframe height is set to 0% in CSS for the height auto adjustment feature.
For modals without the 'commerce-frame' class height auto adjustment is not applicable */
iframe.style.height = '100%';
}
}

return dialog;
}

Expand Down
71 changes: 71 additions & 0 deletions libs/blocks/modal/modal.merch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { debounce } from '../../utils/action.js';

export const MOBILE_MAX = 599;
export const TABLET_MAX = 1199;

export function adjustModalHeight(contentHeight) {
if (!window.location.hash) return;
const dialog = document.querySelector(window.location.hash);
const iframe = dialog?.querySelector('iframe');
const iframeWrapper = dialog?.querySelector('.milo-iframe');
if (!contentHeight || !iframe || !iframeWrapper) return;
if (contentHeight === '100%') {
iframe.style.height = '100%';
iframeWrapper.style.removeProperty('height');
dialog.style.removeProperty('height');
} else {
const verticalMargins = 20;
const clientHeight = document.documentElement.clientHeight - verticalMargins;
if (clientHeight <= 0) return;
const newHeight = contentHeight > clientHeight ? clientHeight : contentHeight;
iframe.style.height = '100%';
iframeWrapper.style.height = `${newHeight}px`;
dialog.style.height = `${newHeight}px`;
}
}

export function sendViewportDimensionsToIframe(source) {
const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
source.postMessage({ mobileMax: MOBILE_MAX, tabletMax: TABLET_MAX, viewportWidth }, '*');
}

export function sendViewportDimensionsOnRequest(source) {
sendViewportDimensionsToIframe(source);
window.addEventListener('resize', debounce(() => sendViewportDimensionsToIframe(source), 10));
}

function reactToMessage({ data, source }) {
if (data === 'viewportWidth' && source) {
/* If the page inside iframe comes from another domain, it won't be able to retrieve
the viewport dimensions, so it sends a request to receive the viewport dimensions
from the parent window. */
sendViewportDimensionsOnRequest(source);
}

if (data?.contentHeight) {
/* If the page inside iframe sends the postMessage with its content height,
we activate the height auto adjustment to eliminate the blank space at the bottom of the modal.
For this we set the iframe height to 0% in CSS to let the page inside iframe
to measure its content height properly.
Then we set the modal height to be the same as the content height we received.
For the modal height adjustment to work the following conditions must be met:
1. The modal must have the class 'commerce-frame';
2. The page inside iframe must send a postMessage with the contentHeight (in px, or '100%); */
adjustModalHeight(data?.contentHeight);
}
}

export function adjustStyles({ dialog, iframe }) {
const isAutoHeightAdjustment = /\/mini-plans\/.*mid=ft.*web=1/.test(iframe.src); // matches e.g. https://www.adobe.com/mini-plans/photoshop.html?mid=ft&web=1
if (isAutoHeightAdjustment) {
dialog.classList.add('height-fit-content');
} else {
iframe.style.height = '100%';
}
}

export default async function enableCommerceFrameFeatures({ dialog, iframe }) {
if (!dialog || !iframe) return;
adjustStyles({ dialog, iframe });
window.addEventListener('message', reactToMessage);
}
19 changes: 19 additions & 0 deletions test/blocks/modals/mocks/iframe.plain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="dialog-modal commerce-frame" id="first-dialog">
<div class="fragment">
<div class="section">
<div class="milo-iframe modal">
<iframe src="https://www.adobe.com/mini-plans/photoshop.html?mid=ft&amp;web=1&amp;pint=Photoshop" allowfullscreen="true"></iframe>
</div>
</div>
</div>
</div>

<div class="dialog-modal commerce-frame" id="second-dialog">
<div class="fragment">
<div class="section">
<div class="milo-iframe modal">
<iframe src="https://www.adobe.com/mini-plans/illustrator.html?mid=ft&amp;web=1&amp;pint=Illustrator" allowfullscreen="true"></iframe>
</div>
</div>
</div>
</div>
114 changes: 114 additions & 0 deletions test/blocks/modals/modal.merch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { readFile, setViewport } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import sinon from 'sinon';
import enableCommerceFrameFeatures, { MOBILE_MAX, TABLET_MAX } from '../../../libs/blocks/modal/modal.merch.js';
import { delay } from '../../helpers/waitfor.js';

document.body.innerHTML = await readFile({ path: './mocks/iframe.plain.html' });
const dialog = document.querySelector('#first-dialog');
const iframeWrapper = dialog.querySelector('.milo-iframe');
const iframe = dialog.querySelector('iframe');
const secondDialog = document.querySelector('#second-dialog');
const secondIframeWrapper = secondDialog.querySelector('.milo-iframe');
const secondIframe = secondDialog.querySelector('iframe');

describe('Modal dialog with a `commerce-frame` class', () => {
it('adjustStyles: sets the iframe height to 100% if height adjustment is not applicable', () => {
const originalSrc = iframe.getAttribute('src');
iframe.setAttribute('src', 'https://www.adobe.com/somepage.html');
enableCommerceFrameFeatures({ dialog, iframe });
expect(dialog.classList.contains('height-fit-content')).to.be.false;
expect(iframe.style.height).to.equal('100%');
iframe.style.removeProperty('height');
iframe.setAttribute('src', originalSrc);
});

it('adjustStyles: sets the `height-fit-content` class if height adjustment is applicable', () => {
enableCommerceFrameFeatures({ dialog, iframe });
expect(dialog.classList.contains('height-fit-content')).to.be.true;
expect(iframe.style.height).to.equal('');
});

it('sends viewport dimensions upon request, and then on every resize', async () => {
sinon.spy(window, 'postMessage');
enableCommerceFrameFeatures({ dialog, iframe });
window.postMessage('viewportWidth');
await delay(10);
expect(window.postMessage.calledWith({
mobileMax: MOBILE_MAX,
tabletMax: TABLET_MAX,
viewportWidth: 800,
})).to.be.true;

document.documentElement.setAttribute('style', 'width: 1200px');
window.dispatchEvent(new Event('resize'));
await delay(10);
expect(window.postMessage.calledWith({
mobileMax: MOBILE_MAX,
tabletMax: TABLET_MAX,
viewportWidth: 1200,
})).to.be.true;
document.documentElement.removeAttribute('style');
});

it('adjusts modal height if height auto adjustment is applicable', async () => {
const contentHeight = {
desktop: 714,
mobile: '100%',
};
await setViewport({ width: 1200, height: 1000 });
window.location.hash = '#first-dialog';
window.postMessage({ contentHeight: contentHeight.desktop }, '*');
await delay(50);
expect(iframe.clientHeight).to.equal(contentHeight.desktop);
expect(iframeWrapper.clientHeight).to.equal(contentHeight.desktop);
expect(dialog.clientHeight).to.equal(contentHeight.desktop);

await setViewport({ width: 320, height: 600 });
window.postMessage({ contentHeight: contentHeight.mobile }, '*');
await delay(50);
expect(iframe.style.height).to.equal(contentHeight.mobile);
expect(iframeWrapper.style.height).to.equal('');
expect(dialog.style.height).to.equal('');
window.location.hash = '';
});

it('properly adjusts the modal height when there are two modals on the page', async () => {
const contentHeight = {
desktop1: 714,
desktop2: 600,
mobile: '100%',
};
await setViewport({ width: 1200, height: 1000 });
window.location.hash = '#first-dialog';
window.postMessage({ contentHeight: contentHeight.desktop1 }, '*');
await delay(50);
expect(iframe.clientHeight).to.equal(contentHeight.desktop1);
expect(iframeWrapper.clientHeight).to.equal(contentHeight.desktop1);
expect(dialog.clientHeight).to.equal(contentHeight.desktop1);

await setViewport({ width: 320, height: 600 });
window.postMessage({ contentHeight: contentHeight.mobile }, '*');
await delay(50);
expect(iframe.style.height).to.equal(contentHeight.mobile);
expect(iframeWrapper.style.height).to.equal('');
expect(dialog.style.height).to.equal('');
window.location.hash = '';

await setViewport({ width: 1200, height: 1000 });
window.location.hash = '#second-dialog';
window.postMessage({ contentHeight: contentHeight.desktop2 }, '*');
await delay(50);
expect(secondIframe.clientHeight).to.equal(contentHeight.desktop2);
expect(secondIframeWrapper.clientHeight).to.equal(contentHeight.desktop2);
expect(secondDialog.clientHeight).to.equal(contentHeight.desktop2);

await setViewport({ width: 320, height: 600 });
window.postMessage({ contentHeight: contentHeight.mobile }, '*');
await delay(50);
expect(iframe.style.height).to.equal(contentHeight.mobile);
expect(iframeWrapper.style.height).to.equal('');
expect(dialog.style.height).to.equal('');
window.location.hash = '';
});
});
Loading

0 comments on commit f533f3d

Please sign in to comment.