Skip to content
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
1 change: 1 addition & 0 deletions libs/blocks/action-scroller/action-scroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default function init(el) {
el.replaceChildren(items, ...buttons);
if (hasNav) {
handleBtnState(items, buttons);
if (!items.querySelectorAll('a').length) items.setAttribute('tabindex', 0);
allActionScrollers.push({ scroller: items, buttons });
import('../../utils/action.js').then(({ debounce }) => {
items.addEventListener('scroll', debounce(() => handleBtnState(items, buttons), 50));
Expand Down
109 changes: 107 additions & 2 deletions libs/blocks/graybox/graybox.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* stylelint-disable selector-class-pattern */
:root {
--base-z-index: 100;
--gb-container-bg: white;
Expand Down Expand Up @@ -80,7 +81,7 @@
background-color: var(--gb-overlay-color);
content: "";
display: block;
height: 100%;
height: 100vh;
left: 0;
pointer-events: none;
position: fixed;
Expand All @@ -105,7 +106,7 @@
/* The elements that should appear above the overlay */
.gb-graybox-body .gb-changed {
position: relative;
z-index: calc(var(--base-z-index) - 1);
z-index: calc(var(--base-z-index) + 1);
}

.graybox-container {
Expand Down Expand Up @@ -322,3 +323,107 @@
border-radius: 5px;
}
}

/* Spectrum Switch */
.spectrum-Switch {
display: inline-flex;
align-items: flex-start;
position: relative;
margin: auto;
min-block-size: var(--mod-switch-height, var(--spectrum-switch-min-height));
max-inline-size: 100%;
vertical-align: top;
}

.spectrum-Switch.gb-toggle-disabled {
pointer-events: none;
opacity: 0.5;
}

/* .gb-toggle-disabled .spectrum-Switch {
pointer-events: none;
} */

.spectrum-Switch-input {
margin: 0;
box-sizing: border-box;
padding: 0;
position: absolute;
inline-size: 100%;
block-size: 100%;
inset-block-start: 0;
inset-inline-start: 0;
opacity: 0;
z-index: 1;
cursor: pointer;
}

.spectrum-Switch-switch {
display: inline-block;
box-sizing: border-box;
position: relative;
inline-size: 26px;
margin-block: 9px;
margin-inline: 0;
flex-grow: 0;
flex-shrink: 0;
vertical-align: middle;
transition: background 0.16s ease-in-out, border 0.16s ease-in-out;
block-size: 14px;
inset-inline-start: 0;
inset-inline-end: 0;
border-radius: 7px;
background-color: rgb(225 225 225);
}

.spectrum-Switch-switch::after, .spectrum-Switch-switch::before {
display: block;
position: absolute;
content: "";
inset-block-start: 0;
inset-inline-start: 0;
}

.spectrum-Switch-switch::before {
box-sizing: border-box;
transition: background 0.16s ease-in-out, border 0.16s ease-in-out, transform 0.13s ease-in-out, box-shadow 0.16s ease-in-out;
inline-size: 14px;
block-size: 14px;
border-width: 2px;
border-radius: 7px;
border-style: solid;
background-color: rgb(248 248 248);
border-color: rgb(113 113 113);
}

.spectrum-Switch-switch::after {
border-radius: 11px;
inset-inline-end: 0;
inset-block-end: 0;
margin: 0;
transition: opacity 0.16s ease-out, margin 0.16s ease-out;
}

.spectrum-Switch-label {
color: rgb(41 41 41);
margin-inline: 10px;
margin-block-start: 6px;
margin-block-end: 0;
font-size: 14px;
line-height: 1.3;
transition: color 0.16s ease-in-out;
}

.spectrum-Switch-input:checked+.spectrum-Switch-switch::before {
transform: translate(calc(26px - 100%));
border-color: rgb(80 80 80);
}

.spectrum-Switch:hover .spectrum-Switch-input+.spectrum-Switch-switch::before {
border-color: rgb(80 80 80);
box-shadow: none;
}

.spectrum-Switch-input:checked+.spectrum-Switch-switch {
background-color: rgb(41 41 41);
}
122 changes: 103 additions & 19 deletions libs/blocks/graybox/graybox.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ const CLASS = {
NO_BORDER: 'gb-no-border',
NO_CHANGE: 'gb-no-change',
NO_CLICK: 'gb-no-click',
OVERLAY_OFF: 'gb-overlay-off',
PAGE_OVERLAY: 'gb-page-overlay',
PHONE_PREVIEW: 'gb-phone-preview',
TABLET_PREVIEW: 'gb-tablet-preview',
SELECTED_BUTTON: 'gb-selected-button',
};

const DISABLED = {
CHANGED: 'gb-disabled-changed',
NO_CHANGE: 'gb-disabled-no-change',
NO_CLICK: 'gb-disabled-no-click',
PAGE_OVERLAY: 'gb-disabled-page-overlay',
};

const METADATA = {
DESC: 'gb-desc',
FOOTER: 'gb-footer',
Expand All @@ -40,6 +48,7 @@ const USER_AGENT = {
const DEFAULT_TITLE = '';

let deviceModal;
let deviceIframe;

const setMetadata = (metadata) => {
const { selector, val } = metadata;
Expand Down Expand Up @@ -168,45 +177,68 @@ function setUserAgent(window, userAgent) {
// eslint-disable-next-line no-promise-executor-return
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));

const injectCSSIntoIframe = (iframe, cssRules) => {
iframe.addEventListener('load', () => {
const toggleGrayboxOverlay = (isChecked) => {
if (!isChecked) {
document.body.classList.add(CLASS.OVERLAY_OFF);
} else {
document.body.classList.remove(CLASS.OVERLAY_OFF);
}

const toggleMap = [
[CLASS.CHANGED, DISABLED.CHANGED],
[CLASS.NO_CHANGE, DISABLED.NO_CHANGE],
[CLASS.NO_CLICK, DISABLED.NO_CLICK],
[CLASS.PAGE_OVERLAY, DISABLED.PAGE_OVERLAY],
];

toggleMap.forEach(([enabled, disabled]) => {
const from = isChecked ? disabled : enabled;
const to = isChecked ? enabled : disabled;
document.querySelectorAll(`.${from}`).forEach((el) => {
el.classList.replace(from, to);
});
});
};

const injectCSSIntoIframe = (cssRules) => {
deviceIframe.addEventListener('load', () => {
try {
const style = document.createElement('style');
style.textContent = cssRules;
iframe.contentWindow.document.head.appendChild(style);
deviceIframe.contentWindow.document.head.appendChild(style);
} catch (error) {
// eslint-disable-next-line no-console
console.warn('Could not inject CSS into iframe. It might be cross-origin.', error);
}
});
};

const setIframeUA = (iFrame, isMobile, isTablet) => {
const setIframeUA = (isMobile, isTablet) => {
if (isMobile) {
document.body.classList.add(CLASS.PHONE_PREVIEW);
deviceModal.classList.add('mobile');
setUserAgent(iFrame.contentWindow, USER_AGENT.iPhone);
setUserAgent(deviceIframe.contentWindow, USER_AGENT.iPhone);
} else if (isTablet) {
document.body.classList.add(CLASS.TABLET_PREVIEW);
deviceModal.classList.add('tablet');
setUserAgent(iFrame.contentWindow, USER_AGENT.iPad);
setUserAgent(deviceIframe.contentWindow, USER_AGENT.iPad);
}
};

const setupIframe = (iFrame, isMobile, isTablet) => {
const setupIframe = (isMobile, isTablet) => {
const cssRules = `
body > .mep-preview-overlay {
display: none !important;
}
`;
injectCSSIntoIframe(iFrame, cssRules);
injectCSSIntoIframe(cssRules);

iFrame.style.height = '';
deviceIframe.style.height = '';

setIframeUA(iFrame, isMobile, isTablet);
setIframeUA(isMobile, isTablet);

// Spoof iFrame dimensions as screen size for MEP
iFrame.contentWindow.screen = {
deviceIframe.contentWindow.screen = {
width: isMobile ? 350 : 768,
height: isMobile ? 800 : 1024,
};
Expand Down Expand Up @@ -239,10 +271,15 @@ const openDeviceModal = async (e) => {
const docFrag = new DocumentFragment();
const iFrameUrl = new URL(window.location.href);
iFrameUrl.searchParams.set('graybox', 'menu-off');
if (document.body.classList.contains(CLASS.OVERLAY_OFF)) {
iFrameUrl.searchParams.set('graybox-overlay', 'off');
} else {
iFrameUrl.searchParams.delete('graybox-overlay');
}
const deviceBorder = createTag('img', { class: 'graybox-device-border', src: isMobile ? iphoneFrame : ipadFrame });
const iFrame = createTag('iframe', { src: iFrameUrl.href, width: '100%', height: '100%' });
deviceIframe = createTag('iframe', { src: iFrameUrl.href, width: '100%', height: '100%' });

const modal = createTag('div', null, [deviceBorder, iFrame]);
const modal = createTag('div', null, [deviceBorder, deviceIframe]);
docFrag.append(modal);

deviceModal = await getModal(
Expand All @@ -253,14 +290,14 @@ const openDeviceModal = async (e) => {
const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
if (isSafari) {
deviceModal.style.display = 'none';
setupIframe(iFrame, isMobile, isTablet);
iFrame.addEventListener('load', () => {
setupIframe(isMobile, isTablet);
deviceIframe.addEventListener('load', () => {
// safari needs to wait for the iframe to load before setting the user agent
setIframeUA(iFrame, isMobile, isTablet);
setIframeUA(isMobile, isTablet);
deviceModal.style.display = '';
});
} else {
setupIframe(iFrame, isMobile, isTablet);
setupIframe(isMobile, isTablet);
}

const onDeviceModalClose = () => {
Expand All @@ -275,6 +312,8 @@ const openDeviceModal = async (e) => {
CLASS.PHONE_PREVIEW,
CLASS.TABLET_PREVIEW,
);

deviceIframe = null;
};

window.addEventListener('milo:modal:closed', onDeviceModalClose, { once: true });
Expand All @@ -283,6 +322,30 @@ const openDeviceModal = async (e) => {
curtain.classList.add('graybox-curtain');
};

const createGrayboxOverlayToggle = (grayboxMenu) => {
const switchDiv = createTag('div', { class: 'spectrum-Switch' }, null, { parent: grayboxMenu });
const input = createTag('input', { type: 'checkbox', class: 'spectrum-Switch-input', id: 'gb-overlay-toggle' }, null, { parent: switchDiv });
createTag('span', { class: 'spectrum-Switch-switch' }, null, { parent: switchDiv });
createTag('label', { class: 'spectrum-Switch-label', for: 'gb-overlay-toggle' }, 'Graybox Overlay', { parent: switchDiv });
input.checked = true;
input.addEventListener('change', (e) => {
const isChecked = e.target.checked;
toggleGrayboxOverlay(isChecked);
if (deviceIframe) {
deviceIframe.contentWindow.postMessage(`gb-overlay-${isChecked ? 'on' : 'off'}`, '*');
}
});

if (!document.querySelector(`.${CLASS.CHANGED}, .${CLASS.NO_CHANGE}, .${CLASS.NO_CLICK}, .${CLASS.PAGE_OVERLAY}`)) {
switchDiv.classList.add('gb-toggle-disabled');
}

const url = new URL(window.location.href);
if (url.searchParams.get('graybox-overlay') === 'off') {
setTimeout(() => input.click(), 10);
}
};

const createGrayboxMenu = (options, { isOpen = false } = {}) => {
const grayboxContainer = createTag('div', { class: 'graybox-container' });
const grayboxMenu = createTag('div', { class: 'graybox-menu' }, null, { parent: grayboxContainer });
Expand All @@ -306,6 +369,8 @@ const createGrayboxMenu = (options, { isOpen = false } = {}) => {
button.addEventListener('click', openDeviceModal);
});

createGrayboxOverlayToggle(grayboxMenu);

const toggleBtn = document.createElement('button');
toggleBtn.className = 'gb-toggle';
toggleBtn.addEventListener('click', () => {
Expand All @@ -322,7 +387,10 @@ const createGrayboxMenu = (options, { isOpen = false } = {}) => {

const addPageOverlayDiv = () => {
const overlayDiv = createTag('div', { class: CLASS.PAGE_OVERLAY });
document.body.insertBefore(overlayDiv, document.body.firstChild);
const main = document.querySelector('main');
if (main) {
main.insertBefore(overlayDiv, main.firstChild);
}
};

const setupChangedEls = (globalNoClick) => {
Expand Down Expand Up @@ -420,6 +488,10 @@ const grayboxThePage = (grayboxEl, grayboxMenuOff) => {
/* c8 ignore next 3 */
if (grayboxMenuOff) {
document.body.classList.add(CLASS.NO_BORDER);
const url = new URL(window.location.href);
if (url.searchParams.get('graybox-overlay') === 'off') {
toggleGrayboxOverlay(false);
}
} else {
createGrayboxMenu(options, { isOpen: true });
}
Expand All @@ -440,6 +512,15 @@ const grayboxThePage = (grayboxEl, grayboxMenuOff) => {
childList: true,
subtree: true,
});

// Listen for iframe messages to toggle graybox overlay in device views
window.addEventListener('message', (event) => {
if (event.data === 'gb-overlay-on') {
toggleGrayboxOverlay(true);
} else if (event.data === 'gb-overlay-off') {
toggleGrayboxOverlay(false);
}
});
};

export default function init(grayboxEl) {
Expand All @@ -461,5 +542,8 @@ export default function init(grayboxEl) {
setMetadata({ selector: 'georouting', val: 'off' });
const grayboxMenuOff = url.searchParams.get('graybox') === 'menu-off';

window.milo.deferredPromise.then(() => grayboxThePage(grayboxEl, grayboxMenuOff));
window.milo.deferredPromise.then(() => grayboxThePage(
grayboxEl,
grayboxMenuOff,
));
}
Loading
Loading