Skip to content

Commit

Permalink
Merge branch 'stage' into MWPW-161563
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulgupta999 committed Nov 8, 2024
2 parents d92598a + df454db commit cb97774
Show file tree
Hide file tree
Showing 26 changed files with 2,024 additions and 770 deletions.
7 changes: 1 addition & 6 deletions libs/blocks/text/text.css
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
position: relative;
}

.text-block .icon-list-item .icon.margin-right:not(.margin-left) { /* target first node only */
.text-block .icon-list-item .icon.node-index-first {
position: absolute;
inset: 0 100% auto auto;
}
Expand All @@ -122,7 +122,6 @@

.text-block .icon-area {
display: flex;
column-gap: var(--spacing-xs);
}

.text-block p.icon-area { /* NOT <a/> tags with icons in them */
Expand Down Expand Up @@ -218,10 +217,6 @@
max-width: unset;
}

.text-block .icon-area.con-button {
column-gap: unset;
}

.text-block .icon-area picture {
line-height: 0em;
height: inherit; /* Safari + FF bug fix */
Expand Down
4 changes: 2 additions & 2 deletions libs/deps/mas/commerce.js

Large diffs are not rendered by default.

88 changes: 46 additions & 42 deletions libs/deps/mas/mas.js

Large diffs are not rendered by default.

200 changes: 102 additions & 98 deletions libs/deps/mas/merch-card.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions libs/features/georoutingv2/georoutingv2.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
}

.dialog-modal.locale-modal-v2 span.icon {
display: inline;
vertical-align: middle;
}

Expand Down
2 changes: 1 addition & 1 deletion libs/features/georoutingv2/georoutingv2.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function buildContent(currentPage, locale, geoData, locales) {
{ once: true },
);
img.src = `${config.miloLibs || config.codeRoot}/img/georouting/${flagFile}`;
const span = createTag('span', { class: 'icon margin-inline-end' }, img);
const span = createTag('span', { class: 'icon node-index-first' }, img);
const mainAction = createTag('a', {
class: 'con-button blue button-l', lang, role: 'button', 'aria-haspopup': !!locales, 'aria-expanded': false, href: '#',
}, span);
Expand Down
118 changes: 97 additions & 21 deletions libs/features/icons/icons.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getFederatedContentRoot } from '../../utils/federated.js';
import { loadLink, loadStyle } from '../../utils/utils.js';

let fetchedIcons;
let fetched = false;
const federalIcons = {};

async function getSVGsfromFile(path) {
/* c8 ignore next */
Expand All @@ -22,6 +26,7 @@ async function getSVGsfromFile(path) {
return miloIcons;
}

// TODO: remove after all consumers have stopped calling this method
// eslint-disable-next-line no-async-promise-executor
export const fetchIcons = (config) => new Promise(async (resolve) => {
/* c8 ignore next */
Expand All @@ -34,41 +39,112 @@ export const fetchIcons = (config) => new Promise(async (resolve) => {
resolve(fetchedIcons);
});

function decorateToolTip(icon) {
async function decorateToolTip(icon) {
const wrapper = icon.closest('em');
wrapper.className = 'tooltip-wrapper';
if (!wrapper) return;
wrapper.className = 'tooltip-wrapper';
const conf = wrapper.textContent.split('|');
// Text is the last part of a tooltip
const content = conf.pop().trim();
if (!content) return;
icon.dataset.tooltip = content;
// Position is the next to last part of a tooltip
const place = conf.pop()?.trim().toLowerCase() || 'right';
icon.className = `icon icon-info milo-tooltip ${place}`;
const defaultIcon = 'info-outline';
icon.className = `icon icon-${defaultIcon} milo-tooltip ${place}`;
icon.dataset.name = defaultIcon;
wrapper.parentElement.replaceChild(icon, wrapper);
}

export default async function loadIcons(icons, config) {
const iconSVGs = await fetchIcons(config);
if (!iconSVGs) return;
export function getIconData(icon) {
const fedRoot = getFederatedContentRoot();
const name = [...icon.classList].find((c) => c.startsWith('icon-'))?.substring(5);
const path = `${fedRoot}/federal/assets/icons/svgs/${name}.svg`;
return { path, name };
}

function preloadInViewIconResources(config) {
const { base } = config;
loadStyle(`${base}/features/icons/icons.css`);
}

const preloadInViewIcons = async (icons = []) => icons.forEach((icon) => {
const { path } = getIconData(icon);
loadLink(path, { rel: 'preload', as: 'fetch', crossorigin: 'anonymous' });
});

function filterDuplicatedIcons(icons) {
if (!icons.length) return [];
const uniqueIconKeys = new Set();
const uniqueIcons = [];
for (const icon of icons) {
const key = [...icon.classList].find((c) => c.startsWith('icon-'))?.substring(5);
if (!uniqueIconKeys.has(key)) {
uniqueIconKeys.add(key);
uniqueIcons.push(icon);
}
}
return uniqueIcons;
}

export async function decorateIcons(area, icons, config) {
if (!icons.length) return;
const uniqueIcons = filterDuplicatedIcons(icons);
if (!uniqueIcons.length) return;
preloadInViewIcons(uniqueIcons);
preloadInViewIconResources(config);
icons.forEach((icon) => {
const iconName = [...icon.classList].find((c) => c.startsWith('icon-'))?.substring(5);
if (!iconName) return;
icon.dataset.name = iconName;
});
}

export default async function loadIcons(icons) {
const fedRoot = getFederatedContentRoot();
const iconRequests = [];
const iconsToFetch = new Map();

icons.forEach(async (icon) => {
const { classList } = icon;
if (classList.contains('icon-tooltip')) decorateToolTip(icon);
const iconName = icon.classList[1].replace('icon-', '');
const existingIcon = icon.querySelector('svg');
if (!iconSVGs[iconName] || existingIcon) return;
const isToolTip = icon.classList.contains('icon-tooltip');
if (isToolTip) decorateToolTip(icon);
const iconName = icon.dataset.name;
if (icon.dataset.svgInjected || !iconName) return;
if (!federalIcons[iconName] && !iconsToFetch.has(iconName)) {
const url = `${fedRoot}/federal/assets/icons/svgs/${iconName}.svg`;
iconsToFetch.set(iconName, fetch(url)
.then(async (res) => {
if (!res.ok) throw new Error(`Failed to fetch SVG for ${iconName}: ${res.statusText}`);
const text = await res.text();
const parser = new DOMParser();
const svgDoc = parser.parseFromString(text, 'image/svg+xml');
const svgElement = svgDoc.querySelector('svg');
if (!svgElement) {
window.lana?.log(`No SVG element found in fetched content for ${iconName}`);
return;
}
const svgClone = svgElement.cloneNode(true);
svgClone.classList.add('icon-milo', `icon-milo-${iconName}`);
federalIcons[iconName] = svgClone;
})
/* c8 ignore next 3 */
.catch((error) => {
window.lana?.log(`Error fetching SVG for ${iconName}:`, error);
}));
}
iconRequests.push(iconsToFetch.get(iconName));
const parent = icon.parentElement;
if (parent.childNodes.length > 1) {
if (parent.lastChild === icon) {
icon.classList.add('margin-inline-start');
} else if (parent.firstChild === icon) {
icon.classList.add('margin-inline-end');
if (parent.parentElement.tagName === 'LI') parent.parentElement.classList.add('icon-list-item');
} else {
icon.classList.add('margin-inline-start', 'margin-inline-end');
}
if (parent && parent.parentElement.tagName === 'LI') parent.parentElement.classList.add('icon-list-item');
});

await Promise.all(iconRequests);

icons.forEach((icon) => {
const iconName = icon.dataset.name;
if (iconName && federalIcons[iconName] && !icon.dataset.svgInjected) {
const svgClone = federalIcons[iconName].cloneNode(true);
icon.appendChild(svgClone);
icon.dataset.svgInjected = 'true';
}
icon.insertAdjacentHTML('afterbegin', iconSVGs[iconName].outerHTML);
});
}
35 changes: 27 additions & 8 deletions libs/features/mas/commerce/src/checkout-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class CheckoutLink extends HTMLAnchorElement {
static is = 'checkout-link';
static tag = 'a';

/* c8 ignore next 1 */
#checkoutActionHandler;

masElement = new MasElement(this);
Expand All @@ -23,10 +24,12 @@ export class CheckoutLink extends HTMLAnchorElement {

connectedCallback() {
this.masElement.connectedCallback();
this.addEventListener('click', this.handleClick);
}

disconnectedCallback() {
this.masElement.disconnectedCallback();
this.removeEventListener('click', this.handleClick);
}

onceSettled() {
Expand All @@ -37,13 +40,17 @@ export class CheckoutLink extends HTMLAnchorElement {
return this.masElement.value;
}

get options() {
return this.masElement.options;
}

requestUpdate(force = false) {
return this.masElement.requestUpdate(force);
}

constructor() {
super();
this.addEventListener('click', this.clickHandler);
this.handleClick = this.handleClick.bind(this);
}

static get observedAttributes() {
Expand Down Expand Up @@ -107,8 +114,20 @@ export class CheckoutLink extends HTMLAnchorElement {
* Triggers checkout action handler, if provided.
* @param {*} event
*/
clickHandler(event) {
this.#checkoutActionHandler?.(event);
handleClick(event) {
if (event.target !== this) {
event.preventDefault();
event.stopImmediatePropagation();
this.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
}),
);
return;
}
this.#checkoutActionHandler?.(event);
}

async render(overrides = {}) {
Expand All @@ -126,8 +145,8 @@ export class CheckoutLink extends HTMLAnchorElement {
let extraOptions;
try {
extraOptions = JSON.parse(options.extraOptions ?? '{}');
/* c8 ignore next 3 */
} catch (e) {
/* c8 ignore next 2 */
this.masElement.log?.error('cannot parse exta checkout options', e);
}
const version = this.masElement.togglePending(options);
Expand All @@ -139,7 +158,7 @@ export class CheckoutLink extends HTMLAnchorElement {
const checkoutAction = await service.buildCheckoutAction?.(
offers.flat(),
{ ...extraOptions, ...options },
this
this,
);
return this.renderOffers(
offers.flat(),
Expand Down Expand Up @@ -173,6 +192,7 @@ export class CheckoutLink extends HTMLAnchorElement {
options = { ...extraOptions, ...options, ...overrides };
version ??= this.masElement.togglePending(options);
if (this.#checkoutActionHandler) {
/* c8 ignore next 2 */
this.#checkoutActionHandler = undefined;
}
if (checkoutAction) {
Expand All @@ -183,8 +203,8 @@ export class CheckoutLink extends HTMLAnchorElement {
if (text) this.firstElementChild.innerHTML = text;
if (className) this.classList.add(...className.split(' '));
if (handler) {
this.setAttribute('href', '#');
this.#checkoutActionHandler = handler.bind(this);
this.setAttribute('href', '#');
this.#checkoutActionHandler = handler.bind(this);
}
return true;
} else if (offers.length) {
Expand All @@ -200,7 +220,6 @@ export class CheckoutLink extends HTMLAnchorElement {
return true;
}
}
return false;
}

updateOptions(options = {}) {
Expand Down
21 changes: 13 additions & 8 deletions libs/features/mas/commerce/src/inline-price.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,22 @@ export class InlinePrice extends HTMLSpanElement {

disconnectedCallback() {
this.masElement.disconnectedCallback();
this.removeEventListener('click', this.handleClick.bind(this));
this.removeEventListener('click', this.handleClick);
}


handleClick(event) {
/* c8 ignore next 4 */
if (event.target === this) return;
// re-dispatch click event from the price element
event.stopImmediatePropagation();
this.dispatchEvent(new CustomEvent('click', { bubbles: true }));
}
/* c8 ignore next 4 */
if (event.target === this) return;
// re-dispatch click event from the price element
event.stopImmediatePropagation();
this.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
}),
);
}

onceSettled() {
return this.masElement.onceSettled();
Expand Down
25 changes: 25 additions & 0 deletions libs/features/mas/commerce/test/checkout.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,31 @@ describe('class "CheckoutLink"', () => {
'https://commerce.adobe.com/store/email?items%5B0%5D%5Bid%5D=632B3ADD940A7FBB7864AA5AD19B8D28&cli=adobe_com&ctx=fp&co=US&lang=en',
);
expect(checkoutLink.value).to.be.not.empty;
expect(checkoutLink.options).to.be.not.empty;
});

it('re-dispatches click event', async () => {
await initMasCommerceService();
const checkoutLink = mockCheckoutLink('abm');
let targetIsCheckoutlink = false;
checkoutLink.addEventListener(
'click',
(event) => {
event.preventDefault();
event.stopImmediatePropagation();
targetIsCheckoutlink = event.target === checkoutLink;
},
{ once: true },
);
await checkoutLink.onceSettled();
checkoutLink.firstElementChild.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
}),
);
expect(targetIsCheckoutlink).to.be.true;
});

it('renders link with workflow step from settings', async () => {
Expand Down
Loading

0 comments on commit cb97774

Please sign in to comment.