From e5c69e9bae64839e75131a0df89b7480a626a9ab Mon Sep 17 00:00:00 2001 From: Axel Cureno Basurto Date: Wed, 15 Jan 2025 01:15:32 -0800 Subject: [PATCH 1/8] MWPW-164107: Mnemonic list fixed (#3372) * MWPW-164107: Mnemonic list fixed * ignored line * a * Revert "a" This reverts commit 9d18f6bfc4c1397db2f80b34afc5ccad9f804cbd. --- libs/blocks/merch-card/merch-card.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/blocks/merch-card/merch-card.js b/libs/blocks/merch-card/merch-card.js index f9f488cfd1..c6db91be91 100644 --- a/libs/blocks/merch-card/merch-card.js +++ b/libs/blocks/merch-card/merch-card.js @@ -77,7 +77,8 @@ export async function loadMnemonicList(foreground) { try { const { base } = getConfig(); const stylePromise = new Promise((resolve) => { - loadStyle(`${base}/blocks/mnemonic-list/merch-mnemonic-list.css`, resolve); + /* c8 ignore next */ + loadStyle(`${base}/blocks/mnemonic-list/mnemonic-list.css`, resolve); }); const loadModule = import(`${base}/blocks/mnemonic-list/mnemonic-list.js`) .then(({ decorateMnemonicList }) => decorateMnemonicList(foreground)); From 3d79cc13fb60ab43eb3a769bd17d67a3009e7684 Mon Sep 17 00:00:00 2001 From: Bandana Laishram Date: Wed, 15 Jan 2025 14:45:38 +0530 Subject: [PATCH 2/8] New Mobile gnav 1.2 redesign (#3374) * MWPW-161082 [Base Implementation] Mobile Global Nav Redesign Rollout (#3092) * basic functionality of mobile gnav * Made the localnav sticky, and the gnav not sticky when there is a local nav * Local nav now resides outside header to make it sticky correctly * fixed a few small css details when opening and closing the mega menu * render the mainmenu on non local nav pages and the brand icon on local nav pages on the mega menu * don't show local nav unless it's in the right place * Make mobile styles the default * Mobile Gnav Redesign for Adobe Home (#3151) * fix the popup showing up for a split second when the hamburger menu is first opened * Undid the previous commit because it didn't work * switched out animations to transitions for fedspopup * Made section buttons have a margin while regular navlinks occupy the whole space * Fixed section menu spacing * Spacing of section after navlink on adobe home * Like the previous commit but actually works * divider line * changed last of kind to last of type * fixed divider spacing * link spacing * we need to pad the last nav link as well * nav link font size * rules apply only to the links not the sections * fixed the selector for the previous commit * I sure hope it works this time * Fixed the navLinks in light mode * Mobile gnav breadcrumbs new design (#3155) * Breadcrumbs design change * Hide breadcrumbs on first sidebar * Fixing no breadcrumbs page * Fixing css * Fixing css for no breadcrumbs page * Adding main menu placeholder (#3157) * Adding main menu placeholder * Avoiding replaceText if localNav * Lint fix * Keyboard navigation for mobile gnav redesign (#3158) * Keyboard navigation for mobile gnav redesign * close main menu on escape * fixed lint issue * Localnav design and event listeners (#3164) * Localnav design and event listeners * Lint fix * Adding Overview title support * Handling CLS issue for localnav - mobile gnav redesign (#3198) * Handling CLS issue by preserving space for localnav * Adding localnav config * Adding overview text from placeholder * Adding meta data check for new mobile gnav feature flag * Lint fix * Adding localnav only when mobile gnav v2 is true * Adding image support for large menu and medium menus on gnav (#3235) * Adding image support on gnav * Decorating localnav only if it has one section * Lint fix * Fixing dropdown promise issue * Handle CLS issue on localnav: mobile redesign (#3253) * Check for localnav * Moving getGnanSource to utils * Awaiting gnav source * Adding lanalog when there is mismatch with content and name * Lint fixes * Changing height of localnav * Temp window url for testinf * Temp window url for testing * Added Analytics Attributes to Tabs and Tabpanels (#3275) * Added analytics attributes to tabs and tabpanels * Handle cases where we don't find daa attributes * [MWPW-159665] - Gnav redesign animations (#3234) * MWPW-161082 [Base Implementation] Mobile Global Nav Redesign Rollout (#3092) * basic functionality of mobile gnav * Made the localnav sticky, and the gnav not sticky when there is a local nav * Local nav now resides outside header to make it sticky correctly * fixed a few small css details when opening and closing the mega menu * render the mainmenu on non local nav pages and the brand icon on local nav pages on the mega menu * don't show local nav unless it's in the right place * Make mobile styles the default * Mobile Gnav Redesign for Adobe Home (#3151) * fix the popup showing up for a split second when the hamburger menu is first opened * Undid the previous commit because it didn't work * switched out animations to transitions for fedspopup * Made section buttons have a margin while regular navlinks occupy the whole space * Fixed section menu spacing * Spacing of section after navlink on adobe home * Like the previous commit but actually works * divider line * changed last of kind to last of type * fixed divider spacing * link spacing * we need to pad the last nav link as well * nav link font size * rules apply only to the links not the sections * fixed the selector for the previous commit * I sure hope it works this time * Fixed the navLinks in light mode * Mobile gnav breadcrumbs new design (#3155) * Breadcrumbs design change * Hide breadcrumbs on first sidebar * Fixing no breadcrumbs page * Fixing css * Fixing css for no breadcrumbs page * Adding main menu placeholder (#3157) * Adding main menu placeholder * Avoiding replaceText if localNav * Lint fix * Keyboard navigation for mobile gnav redesign (#3158) * Keyboard navigation for mobile gnav redesign * close main menu on escape * fixed lint issue * Localnav design and event listeners (#3164) * Localnav design and event listeners * Lint fix * Adding Overview title support * Added animations for mobile redesign * Polish animations * Polish animations * polish animations * Resolved comment --------- Co-authored-by: Raghav Sharma <118168183+sharmrj@users.noreply.github.com> Co-authored-by: Raghav Sharma Co-authored-by: Bandana Laishram * Adding active column logic in a large menu (#3276) * Adding active column logic in a large menu * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [MWPW-159667] - Add dark mode fixes (#3285) * dark mode changes * lint-fix * small fix for animation * Fix spacing and dark mode things for localnav * Set page overlay for local nav * Added some pending fix for keyboard navigation * Added some pending fix for keyboard navigation * Added some pending fix for keyboard navigation * [MWPW-163735] - Added Local Nav keyboard navigation for mobile GNAV redesign (#3321) * Added localnav keyboard navigation for mobile gnav redesign * Added fixes for Local nav along with keyboard navigation * lint fix * Gnav bugs (#3346) * Fixing mega menu CTA height * Localnav z index and removing default makeActiveTab call * Localnav z index to 9 * Fix: georouting issue * [MWPW-164088] - UT for mobile GNAV redesign (#3359) * UT for mobile GNAV redesign * lint fix * lint fix * lint fix * lint fix * Added More UT's * Making localnav auto dismiss on click outside (#3368) * Making localnav auto dismiss on click outside * making active link in localnav bold * Fix for multi level localnav click * UT fixes * Making localnav close on curtain click * Fix: String check update for localnav * Adding disable-scroll when localnav is opened * Adding scroll for tabs * Update libs/blocks/global-navigation/utilities/utilities.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Added fix for UT and bug MWPW-163859 * Making the feature flag value to on * Lint Fix * Decorating localnav before adding events * MWPW-163912 * MWPW-163912 bug fix * MWPW-163912 bug fix * [mobile gnav] view plans and pricing CTA added is not seen visible in iphone 15 in landscape and portrait mode (#3390) * changed vh to dvh (for iphones) * made localnav curtain height lvh instead of dvh * gave localnav curtain a min height of webkit fill available * missed a semicolon * padded localnav curtain with safe-area-inset-bottom * made local nav curtain slightly larger than the view port to prevent a small space that allows clicking on the bottom * Changing the flag for new nav to off by default for standalone gnav * Removing list style * Fix for bugs MWPW-163914 and MWPW-163907 * MWPW-163912 bug fix * MWPW-164385 [Mobile Gnav]when the megamenu is opened and scrolled seen the overlapping issue on the content and screen is freezed (#3410) * the mobile mega menu on localnav pages now correctly takes scroll into account when opening * Extra pixel * MWPW-163515: add daa-ll attributes for new mobile gnav elements (#3380) * MWPW-163515: add daa-ll attributes for new mobile gnav elements * update comment * Fix: String check update for localnav * Adding disable-scroll when localnav is opened * Adding scroll for tabs * Update libs/blocks/global-navigation/utilities/utilities.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Added fix for UT and bug MWPW-163859 * Making the feature flag value to on * Lint Fix * Decorating localnav before adding events * MWPW-163912 * MWPW-163912 bug fix * MWPW-163912 bug fix * [mobile gnav] view plans and pricing CTA added is not seen visible in iphone 15 in landscape and portrait mode (#3390) * changed vh to dvh (for iphones) * made localnav curtain height lvh instead of dvh * gave localnav curtain a min height of webkit fill available * missed a semicolon * padded localnav curtain with safe-area-inset-bottom * made local nav curtain slightly larger than the view port to prevent a small space that allows clicking on the bottom * Changing the flag for new nav to off by default for standalone gnav * Removing list style * Fix for bugs MWPW-163914 and MWPW-163907 * MWPW-163515-156410: fix analytics issues * MWPW-163515: add daa-lh for localnav --------- Co-authored-by: Bandana Laishram Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dev Ashish Sardana Co-authored-by: Raghav Sharma <118168183+sharmrj@users.noreply.github.com> * [MWPW-160542] - LNAV accessibility * Rempoving bold from multi level dropdown * Making color of feds headline for localnav to link color * [MWPW-164088] - UT fixes * Lint fixes * [MWPW-164443] - Bug fix * [MWPW-160542] - Accessibility Fixes * UT and Lint Fixes * [MWPW-160542] - Accessibility Fixes * [MWPW-160542] - Accessibility Fix for breadcrumb * [MWPW-164648] & [MWPW-164649] Bug fix * Fix: Checking for viewport before decorating localnav * [MWPW-164649] - accesibility fix for custom links * Fix: Adding checks for valid localNav * Fix: font fix for button * Lint fix * Decorating localnav * Hiding localnav on desktop view * MWPW-163916 [Mobile GNAV] | Flickering issue while scrolling when LNAV is opened (#3419) * Add overflow hidden to html if the body has it for iOS safari * Added position fixed to disable scroll for ios safari * use position: relative instead of fixed * undo previous changes * removed position absolute from the feds localnav curtain * removed the different height when the localnav curtain is active * trying position: absolute f=to prevent scrolling on iOS * add overflow hidden to html tag as when the body has it * Undo previous change * Used event.preventDefault on a scroll event on the window to prevent scrolling in iOS * tried event.preventDefault to stop body from scrolling on iOS Safari * Set the scroll to the current number on scroll when scroll is disabled * Change scroll to touchmove * iOS safari defaults event listener passive to true * Small refactor * disabled scroll for the non localnav mobile gnav as well * remove scroll on body instead of window * Disable body scroll when local nav or mobile menu is open * corrected the import * fixed a typo * Use position fixed to prevent scrolling in iOS * eslint * more eslint problems * Fixed an issue where swtiching from mobile to desktop made the dekstop mega menu popup have the wrong position * gave ios-disable-scroll the correct behavior for non localnavs as well * Fixed a couple of bugs * fixed an issue where firefox treats position fixed as position absolute * [MWPW-164624] - Bug fix * MWPW-164817 [Mobile GNAV] | The close button is not visible when the hamburger menu is clicked, and the screen shifts when clicking on the Adobe Creative Cloud drop-down (#3432) Fixed an issue on ios Safari where position fixed was changing the dimensions of the body element * Adding support for no megamenu localnav (#3421) * Mobile gnav tests (#3433) * Fixed unit tests for gnav promo * Prevent other tests from breaking * Added a test case for safari * Added an escape test for localnav * Mobile Gnav Standalone Localnav curtain was missing some styles (#3435) * Added styles necessary for the standalone localnav to function * missed a css variable * Css fix for Megamenu title * Css fix for standalone localnav * Css fix for standalone localnav * Making gnav promo sticky (#3434) * Moving Promo out of Header * Moving Promo out of Header * Cls fix for promo * Handling desktop gnav sticky * Removing has promo if no promo * Css fix * Test case fix * Test case fix * Test case fix * Test case fix * Fixed popup with promo (#3436) * Font size fix for tabs * Fixed the popup position for when there is no promo (#3439) add a condition for when the nav promo offset is subtracted from the top of the popup * [MWPW-164444] and [MWPW-164624] bug fixes * Adding shadow to localnav bottom * Popup position fix for iPhone (#3440) * add a condition for when the nav promo offset is subtracted from the top of the popup * fixed popup height for iphone * moved the popup 1px up * Added a style for non sticky lnav with a promo on ios (#3441) * [MWPW-164949] Bug Fix * Fixed more issues specific to iOS (#3448) * MWPW-161082 [Base Implementation] Mobile Global Nav Redesign Rollout (#3092) * basic functionality of mobile gnav * Made the localnav sticky, and the gnav not sticky when there is a local nav * Local nav now resides outside header to make it sticky correctly * fixed a few small css details when opening and closing the mega menu * render the mainmenu on non local nav pages and the brand icon on local nav pages on the mega menu * don't show local nav unless it's in the right place * Make mobile styles the default * Mobile Gnav Redesign for Adobe Home (#3151) * fix the popup showing up for a split second when the hamburger menu is first opened * Undid the previous commit because it didn't work * switched out animations to transitions for fedspopup * Made section buttons have a margin while regular navlinks occupy the whole space * Fixed section menu spacing * Spacing of section after navlink on adobe home * Like the previous commit but actually works * divider line * changed last of kind to last of type * fixed divider spacing * link spacing * we need to pad the last nav link as well * nav link font size * rules apply only to the links not the sections * fixed the selector for the previous commit * I sure hope it works this time * Fixed the navLinks in light mode * Mobile gnav breadcrumbs new design (#3155) * Breadcrumbs design change * Hide breadcrumbs on first sidebar * Fixing no breadcrumbs page * Fixing css * Fixing css for no breadcrumbs page * Adding main menu placeholder (#3157) * Adding main menu placeholder * Avoiding replaceText if localNav * Lint fix * Keyboard navigation for mobile gnav redesign (#3158) * Keyboard navigation for mobile gnav redesign * close main menu on escape * fixed lint issue * Localnav design and event listeners (#3164) * Localnav design and event listeners * Lint fix * Adding Overview title support * Handling CLS issue for localnav - mobile gnav redesign (#3198) * Handling CLS issue by preserving space for localnav * Adding localnav config * Adding overview text from placeholder * Adding meta data check for new mobile gnav feature flag * Lint fix * Adding localnav only when mobile gnav v2 is true * Adding image support for large menu and medium menus on gnav (#3235) * Adding image support on gnav * Decorating localnav only if it has one section * Lint fix * Fixing dropdown promise issue * Handle CLS issue on localnav: mobile redesign (#3253) * Check for localnav * Moving getGnanSource to utils * Awaiting gnav source * Adding lanalog when there is mismatch with content and name * Lint fixes * Changing height of localnav * Temp window url for testinf * Temp window url for testing * Added Analytics Attributes to Tabs and Tabpanels (#3275) * Added analytics attributes to tabs and tabpanels * Handle cases where we don't find daa attributes * [MWPW-159665] - Gnav redesign animations (#3234) * MWPW-161082 [Base Implementation] Mobile Global Nav Redesign Rollout (#3092) * basic functionality of mobile gnav * Made the localnav sticky, and the gnav not sticky when there is a local nav * Local nav now resides outside header to make it sticky correctly * fixed a few small css details when opening and closing the mega menu * render the mainmenu on non local nav pages and the brand icon on local nav pages on the mega menu * don't show local nav unless it's in the right place * Make mobile styles the default * Mobile Gnav Redesign for Adobe Home (#3151) * fix the popup showing up for a split second when the hamburger menu is first opened * Undid the previous commit because it didn't work * switched out animations to transitions for fedspopup * Made section buttons have a margin while regular navlinks occupy the whole space * Fixed section menu spacing * Spacing of section after navlink on adobe home * Like the previous commit but actually works * divider line * changed last of kind to last of type * fixed divider spacing * link spacing * we need to pad the last nav link as well * nav link font size * rules apply only to the links not the sections * fixed the selector for the previous commit * I sure hope it works this time * Fixed the navLinks in light mode * Mobile gnav breadcrumbs new design (#3155) * Breadcrumbs design change * Hide breadcrumbs on first sidebar * Fixing no breadcrumbs page * Fixing css * Fixing css for no breadcrumbs page * Adding main menu placeholder (#3157) * Adding main menu placeholder * Avoiding replaceText if localNav * Lint fix * Keyboard navigation for mobile gnav redesign (#3158) * Keyboard navigation for mobile gnav redesign * close main menu on escape * fixed lint issue * Localnav design and event listeners (#3164) * Localnav design and event listeners * Lint fix * Adding Overview title support * Added animations for mobile redesign * Polish animations * Polish animations * polish animations * Resolved comment --------- Co-authored-by: Raghav Sharma <118168183+sharmrj@users.noreply.github.com> Co-authored-by: Raghav Sharma Co-authored-by: Bandana Laishram * Adding active column logic in a large menu (#3276) * Adding active column logic in a large menu * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [MWPW-159667] - Add dark mode fixes (#3285) * dark mode changes * lint-fix * small fix for animation * Fix spacing and dark mode things for localnav * Set page overlay for local nav * Added some pending fix for keyboard navigation * Added some pending fix for keyboard navigation * Added some pending fix for keyboard navigation * [MWPW-163735] - Added Local Nav keyboard navigation for mobile GNAV redesign (#3321) * Added localnav keyboard navigation for mobile gnav redesign * Added fixes for Local nav along with keyboard navigation * lint fix * Gnav bugs (#3346) * Fixing mega menu CTA height * Localnav z index and removing default makeActiveTab call * Localnav z index to 9 * Fix: georouting issue * [MWPW-164088] - UT for mobile GNAV redesign (#3359) * UT for mobile GNAV redesign * lint fix * lint fix * lint fix * lint fix * Added More UT's * Making localnav auto dismiss on click outside (#3368) * Making localnav auto dismiss on click outside * making active link in localnav bold * Fix for multi level localnav click * UT fixes * Making localnav close on curtain click * Fix: String check update for localnav * Adding disable-scroll when localnav is opened * Adding scroll for tabs * Update libs/blocks/global-navigation/utilities/utilities.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Added fix for UT and bug MWPW-163859 * Making the feature flag value to on * Lint Fix * Decorating localnav before adding events * MWPW-163912 * MWPW-163912 bug fix * MWPW-163912 bug fix * [mobile gnav] view plans and pricing CTA added is not seen visible in iphone 15 in landscape and portrait mode (#3390) * changed vh to dvh (for iphones) * made localnav curtain height lvh instead of dvh * gave localnav curtain a min height of webkit fill available * missed a semicolon * padded localnav curtain with safe-area-inset-bottom * made local nav curtain slightly larger than the view port to prevent a small space that allows clicking on the bottom * Changing the flag for new nav to off by default for standalone gnav * Removing list style * Fix for bugs MWPW-163914 and MWPW-163907 * MWPW-163912 bug fix * MWPW-164385 [Mobile Gnav]when the megamenu is opened and scrolled seen the overlapping issue on the content and screen is freezed (#3410) * the mobile mega menu on localnav pages now correctly takes scroll into account when opening * Extra pixel * MWPW-163515: add daa-ll attributes for new mobile gnav elements (#3380) * MWPW-163515: add daa-ll attributes for new mobile gnav elements * update comment * Fix: String check update for localnav * Adding disable-scroll when localnav is opened * Adding scroll for tabs * Update libs/blocks/global-navigation/utilities/utilities.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Added fix for UT and bug MWPW-163859 * Making the feature flag value to on * Lint Fix * Decorating localnav before adding events * MWPW-163912 * MWPW-163912 bug fix * MWPW-163912 bug fix * [mobile gnav] view plans and pricing CTA added is not seen visible in iphone 15 in landscape and portrait mode (#3390) * changed vh to dvh (for iphones) * made localnav curtain height lvh instead of dvh * gave localnav curtain a min height of webkit fill available * missed a semicolon * padded localnav curtain with safe-area-inset-bottom * made local nav curtain slightly larger than the view port to prevent a small space that allows clicking on the bottom * Changing the flag for new nav to off by default for standalone gnav * Removing list style * Fix for bugs MWPW-163914 and MWPW-163907 * MWPW-163515-156410: fix analytics issues * MWPW-163515: add daa-lh for localnav --------- Co-authored-by: Bandana Laishram Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dev Ashish Sardana Co-authored-by: Raghav Sharma <118168183+sharmrj@users.noreply.github.com> * [MWPW-160542] - LNAV accessibility * Rempoving bold from multi level dropdown * Making color of feds headline for localnav to link color * [MWPW-164088] - UT fixes * Lint fixes * [MWPW-164443] - Bug fix * [MWPW-160542] - Accessibility Fixes * UT and Lint Fixes * [MWPW-160542] - Accessibility Fixes * [MWPW-160542] - Accessibility Fix for breadcrumb * [MWPW-164648] & [MWPW-164649] Bug fix * Fix: Checking for viewport before decorating localnav * [MWPW-164649] - accesibility fix for custom links * Fix: Adding checks for valid localNav * Fix: font fix for button * Lint fix * Decorating localnav * Hiding localnav on desktop view * MWPW-163916 [Mobile GNAV] | Flickering issue while scrolling when LNAV is opened (#3419) * Add overflow hidden to html if the body has it for iOS safari * Added position fixed to disable scroll for ios safari * use position: relative instead of fixed * undo previous changes * removed position absolute from the feds localnav curtain * removed the different height when the localnav curtain is active * trying position: absolute f=to prevent scrolling on iOS * add overflow hidden to html tag as when the body has it * Undo previous change * Used event.preventDefault on a scroll event on the window to prevent scrolling in iOS * tried event.preventDefault to stop body from scrolling on iOS Safari * Set the scroll to the current number on scroll when scroll is disabled * Change scroll to touchmove * iOS safari defaults event listener passive to true * Small refactor * disabled scroll for the non localnav mobile gnav as well * remove scroll on body instead of window * Disable body scroll when local nav or mobile menu is open * corrected the import * fixed a typo * Use position fixed to prevent scrolling in iOS * eslint * more eslint problems * Fixed an issue where swtiching from mobile to desktop made the dekstop mega menu popup have the wrong position * gave ios-disable-scroll the correct behavior for non localnavs as well * Fixed a couple of bugs * fixed an issue where firefox treats position fixed as position absolute * [MWPW-164624] - Bug fix * MWPW-164817 [Mobile GNAV] | The close button is not visible when the hamburger menu is clicked, and the screen shifts when clicking on the Adobe Creative Cloud drop-down (#3432) Fixed an issue on ios Safari where position fixed was changing the dimensions of the body element * Adding support for no megamenu localnav (#3421) * Mobile gnav tests (#3433) * Fixed unit tests for gnav promo * Prevent other tests from breaking * Added a test case for safari * Added an escape test for localnav * Mobile Gnav Standalone Localnav curtain was missing some styles (#3435) * Added styles necessary for the standalone localnav to function * missed a css variable * Css fix for Megamenu title * Css fix for standalone localnav * Css fix for standalone localnav * Making gnav promo sticky (#3434) * Moving Promo out of Header * Moving Promo out of Header * Cls fix for promo * Handling desktop gnav sticky * Removing has promo if no promo * Css fix * Test case fix * Test case fix * Test case fix * Test case fix * Fixed popup with promo (#3436) * Font size fix for tabs * Fixed the popup position for when there is no promo (#3439) add a condition for when the nav promo offset is subtracted from the top of the popup * [MWPW-164444] and [MWPW-164624] bug fixes * Adding shadow to localnav bottom * Popup position fix for iPhone (#3440) * add a condition for when the nav promo offset is subtracted from the top of the popup * fixed popup height for iphone * moved the popup 1px up * Fixed popup not showing properly on safari * added padding-bottom: env(safe-area-inset-bottom) for our popup * min height 100dvh * removed min height and moved back to the old height * ios html height must be 100dvh * + should be has --------- Co-authored-by: Bandana Laishram Co-authored-by: Dev Ashish Saradhana <41122199+Deva309@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dev Ashish Sardana Co-authored-by: nishantka <126539566+nishantka@users.noreply.github.com> * Fixing promo on a page with a main menu (#3449) * quick fix for popup in older ios versions * Fixing promo on a page with a main menu * Fix mainmenu color in ios and promo texxst dissappearing under certain circumstances. (#3450) * quick fix for popup in older ios versions * Fixing promo on a page with a main menu * changed the selector for changing the header z index to more accurately target a dropdown * explicitly set main menu button color * Mobile gnav main promo (#3451) * corrected a selector * [MWPW-164949] Bug Fix * Mobile gnav bugfixes (#3452) * fixed popup on page with promo and mainmenu * Gave the body a min-height * Reserve space for the gnav promo on mobile (#3453) * Mobile gnav bugfixes (#3464) * Reserve space for the gnav promo on mobile * popup.style.top should only be set for localnav * Gave the globalnav with promo the correct height and position * scroll behavior set to instant * MWPW-164737: add daa-ll for new gnav-image element (#3466) * Changing the override character to :: for localnav title (#3470) * MWPW-165391 [Mobile-GNav] when the page is opened and navigated till center and clicked on tabs the screen is taken up (#3467) Changed tabs scrollStackedMobile to work correctly with a sticky localnav * Adding check for new nav before checking for mega menu to render hamburger * Enable Scroll in Lnav while disabling it in gnav (#3472) * enable scrolling on localnav but not on popup * fixed an issue where there was a 1 px gap between the popup and the top of the page * Removed commented lines * Removed a unit test that was no longer necessary * disable ios scroll only on localnav * disable mobile scroll always for feds popup * disable scroll in the right place * enable mobile scroll when clicking main menu * guarded agains disable mobile scroll being called twice * don't disable scroll when lnav curtain is clickerd * Unit tests for enable/disable scroll * Fixed screen freeze when clicking on a localnav item * disable scroll only on megamenu * Fix for popup title on specific DC pages * box-sizing inheritances * Adding check for localnav before setting the top for sticky top section metadata (#3484) Adding localnav check for style top * Adding promo check for style top * Adding promo check for style top * Lint fix * Fix for a unit test --------- Co-authored-by: Raghav Sharma <118168183+sharmrj@users.noreply.github.com> Co-authored-by: Raghav Sharma Co-authored-by: Dev Ashish Saradhana <41122199+Deva309@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dev Ashish Sardana Co-authored-by: nishantka <126539566+nishantka@users.noreply.github.com> --- libs/blocks/fallback/fallback.js | 1 + libs/blocks/global-navigation/base.css | 9 + libs/blocks/global-navigation/dark-nav.css | 42 ++ .../global-navigation/features/aside/aside.js | 3 +- .../global-navigation/global-navigation.css | 599 +++++++++++++++++- .../global-navigation/global-navigation.js | 285 +++++++-- .../utilities/keyboard/index.js | 48 +- .../utilities/keyboard/localNav.js | 44 ++ .../utilities/keyboard/mainNav.js | 26 + .../utilities/keyboard/mobilePopup.js | 65 +- .../utilities/keyboard/utils.js | 16 + .../global-navigation/utilities/menu/menu.css | 13 + .../global-navigation/utilities/menu/menu.js | 17 + .../global-navigation/utilities/utilities.js | 168 ++++- .../blocks/section-metadata/sticky-section.js | 13 +- libs/blocks/tabs/tabs.js | 3 +- .../section-metadata/sticky-section.js | 13 +- libs/navigation/bootstrapper.js | 5 + libs/navigation/navigation.css | 11 + libs/navigation/navigation.js | 18 +- libs/styles/styles.css | 34 +- libs/utils/federated.js | 2 +- libs/utils/utils.js | 24 +- .../global-navigation.test.js | 94 ++- .../gnav-cross-cloud.test.js | 2 + .../gnav-main-nav-popup.test.js | 3 + .../global-navigation/gnav-main-nav.test.js | 5 + .../global-navigation/gnav-promo.test.js | 16 +- .../keyboard/keyboard.test.js | 156 ++++- .../mocks/global-new-nav-mobile-localnav.html | 176 +++++ .../keyboard/mocks/global-new-nav-mobile.html | 518 +++++++++++++++ .../keyboard/mocks/localnav-mock.html | 457 +++++++++++++ .../mocks/gnav-with-localnav.plain.js | 70 ++ .../mocks/mock-cc-column-1.plain.js | 49 ++ .../mocks/mock-cc-column-2.plain.js | 68 ++ .../mocks/mock-megamenu.plain.js | 361 +++++++++++ .../global-navigation/test-utilities.js | 24 +- .../utilities/utilities.test.js | 89 +++ test/navigation/bootstrapper.test.js | 9 + 39 files changed, 3426 insertions(+), 130 deletions(-) create mode 100644 libs/blocks/global-navigation/utilities/keyboard/localNav.js create mode 100644 test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile-localnav.html create mode 100644 test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile.html create mode 100644 test/blocks/global-navigation/keyboard/mocks/localnav-mock.html create mode 100644 test/blocks/global-navigation/mocks/gnav-with-localnav.plain.js create mode 100644 test/blocks/global-navigation/mocks/mock-cc-column-1.plain.js create mode 100644 test/blocks/global-navigation/mocks/mock-cc-column-2.plain.js create mode 100644 test/blocks/global-navigation/mocks/mock-megamenu.plain.js diff --git a/libs/blocks/fallback/fallback.js b/libs/blocks/fallback/fallback.js index fc04eb71f3..33e650829e 100644 --- a/libs/blocks/fallback/fallback.js +++ b/libs/blocks/fallback/fallback.js @@ -18,6 +18,7 @@ const SYNTHETIC_BLOCKS = [ 'search', 'social', 'product-entry-cta', + 'gnav-image', ]; // eslint-disable-next-line import/prefer-default-export diff --git a/libs/blocks/global-navigation/base.css b/libs/blocks/global-navigation/base.css index 15a7925834..4f21bd9296 100644 --- a/libs/blocks/global-navigation/base.css +++ b/libs/blocks/global-navigation/base.css @@ -35,6 +35,15 @@ --feds-background-footer: #fafafa; --feds-borderColor-featuredProducts: #999; --feds-gutter-footer: 32px; + + /* mobile gnav redesign */ + --feds-color-white-v2: #fff; + --feds-color-black-v2: #000; + --feds-background-cloudmenu-v2: #111; + --feds-borderColor-link-v2: #eaeaea; + --feds-backgroundColor-tabs-v2: #f8f8f8; + --feds-backgroundColor-tabContent-v2: #f3f3f3; + --feds-borderColor-localnav-v2: #eee; } /* Nav Link styles */ diff --git a/libs/blocks/global-navigation/dark-nav.css b/libs/blocks/global-navigation/dark-nav.css index 22585dc606..1191d2f676 100644 --- a/libs/blocks/global-navigation/dark-nav.css +++ b/libs/blocks/global-navigation/dark-nav.css @@ -40,6 +40,10 @@ --feds-color-profile--dark: #dbdbdb; --feds-color-profile--emphasis--dark: #f2f2f2; --feds-border-profile--dark: 1px solid var(--feds-borderColor); + + /* mobile gnav redesign */ + --feds-borderColor-link-v2: #323232; + --feds-borderColor-localnav-v2: #323232; } .feds--dark .feds-navLink--hoverCaret:hover, @@ -84,6 +88,44 @@ color: var(--feds-color-link--dark); } +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .top-bar { + background-color: var(--feds-background-nav--desktop); + border-bottom: 1px solid var(--feds-background-nav--desktop); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .title { + background-color: var(--background-color); + border-bottom: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tabs { + background-color: var(--background-color); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tab-content { + background-color: var(--background-color); + border-left: 1px solid var(--feds-borderColor-link-v2); +} + +[dir = "rtl"] header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tab-content { + border-left: none; + border-right: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tabs button[aria-selected="true"] { + color: var(--background-color); + background-color: var(--feds-borderColor-navLink); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .sticky-cta { + background-color: var(--background-color); + border-top: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu { + color: var(--text-color); +} + /* Popup */ .feds--dark .feds-popup { background-color: #151515; diff --git a/libs/blocks/global-navigation/features/aside/aside.js b/libs/blocks/global-navigation/features/aside/aside.js index 2abbf96eaa..0d956460ee 100644 --- a/libs/blocks/global-navigation/features/aside/aside.js +++ b/libs/blocks/global-navigation/features/aside/aside.js @@ -2,8 +2,9 @@ import { loadBlock, decorateAutoBlock } from '../../../../utils/utils.js'; import { toFragment, lanaLog } from '../../utilities/utilities.js'; import { processTrackingLabels } from '../../../../martech/attributes.js'; -export default async function decorateAside({ headerElem, promoPath } = {}) { +export default async function decorateAside({ headerElem, fedsPromoWrapper, promoPath } = {}) { const onError = () => { + fedsPromoWrapper?.remove(); headerElem?.classList.remove('has-promo'); lanaLog({ message: 'Gnav Promo fragment not replaced, potential CLS' }); return ''; diff --git a/libs/blocks/global-navigation/global-navigation.css b/libs/blocks/global-navigation/global-navigation.css index f361fa0066..21dab7dff8 100644 --- a/libs/blocks/global-navigation/global-navigation.css +++ b/libs/blocks/global-navigation/global-navigation.css @@ -61,11 +61,12 @@ header.global-navigation { right: 20px; /* hamburger menu gutter */ display: none; flex-direction: column; - height: calc(100vh - 100% - 1px); + height: calc(100dvh - 100% - 1px); border-top: 1px solid var(--feds-borderColor); background-color: var(--feds-background-nav); } + [dir = "rtl"] .feds-nav-wrapper { left: 20px; right: 0; @@ -119,6 +120,15 @@ header.global-navigation { flex-shrink: 0; } +.feds-brand-container > .feds-brand:first-child { + margin-left: 12px; +} + +[dir = "rtl"] .feds-brand-container > .feds-brand:first-child { + margin-left: 0; + margin-right: 12px; +} + .feds-brand, .feds-logo { align-items: center; @@ -594,13 +604,13 @@ header.global-navigation { .feds-navItem--megaMenu .feds-popup { right: 0; padding: 40px 0 0; - max-height: calc(100vh - 100%); + max-height: calc(100dvh - 100%); overflow: auto; box-sizing: border-box; } .global-navigation.has-promo .feds-navItem--megaMenu .feds-popup { - max-height: calc(100vh - 100% - var(--global-height-navPromo)); + max-height: calc(100dvh - 100% - var(--global-height-navPromo)); } [dir = "rtl"] .feds-navItem--megaMenu .feds-popup { @@ -716,6 +726,9 @@ header.global-navigation { .feds-brand-image + .feds-brand-label { display: none; } + .feds-localnav { + display: none; + } } /* Desktop styles */ @@ -735,4 +748,584 @@ header.global-navigation { .feds-navItem--megaMenu .feds-popup { align-items: center; } + + .feds-localnav { + display: none; + } +} + +/* + * + * + * mobile gnav redesign + * + * + */ + +/* new mobile gnav is not sticky when localnav */ +header.new-nav.local-nav { + position: relative; +} + +/* Don't blur the background when opening the mega menu on a local nav page */ +header.new-nav.local-nav .feds-curtain--open { + display: none; +} + +header.new-nav:has(.feds-dropdown--active) { + z-index: 12; +} + +/* main-menu screen */ + +header.new-nav .feds-nav-wrapper { + width: 100%; + background-color: var(--feds-background-cloudmenu-v2); + translate: -200vw 0; + opacity: 0; + transition: translate 0.4s ease-out, opacity 0.4s ease, visibility 0s linear 0.5s; + display: flex; + visibility: hidden; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper { + translate: 200vw 0; +} + +header.new-nav.local-nav .feds-nav-wrapper { + visibility: hidden; /* we don't want to see the main menu on a local nav page */ +} + +header.new-nav .feds-nav-wrapper--expanded { + translate: 0; + opacity: 1; + z-index: 2; + transition: translate 0.4s ease-out, opacity 0.4s ease, visibility 0s linear 0s; + visibility: visible; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper--expanded { + translate: 0; +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem:first-child { + padding-top: 25px; +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > .feds-navItem.feds-navItem--mobile-only:last-of-type { + margin-bottom: 10px; + border-bottom: 2px solid var(--feds-color-white-v2); +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem > button.feds-navLink { + color: var(--feds-color-white-v2); + font-size: 20px; + font-weight: 400; + line-height: 25px; + border: none; + opacity:0; + translate: -100px 0; + animation: slideleft 0.4s ease, fadein 0.2s ease; + animation-fill-mode: forwards; + padding: 15px 20px; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem > button.feds-navLink { + translate: 100px 0; + animation: rtlslideleft 0.4s ease, fadein 0.2s ease; + animation-fill-mode: forwards; +} + +header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink { + opacity: 0; + border: none; + animation: slideright 0.4s ease, fadeout 0.2s ease; + animation-fill-mode: forwards; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink { + animation: rtlslideright 0.4s ease, fadeout 0.2s ease; + animation-fill-mode: forwards; +} + +header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink::after { + content: url(''); + width: 9.271px; + height: 9.179px; + transform: rotate(-45deg); + border-color: var(--feds-color-white-v2); + right: 20px; + top: calc(50% - 4px); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink::after { + transform: rotate(135deg); + right: unset; + left: 20px; +} + +header.new-nav .feds-nav > .feds-navItem > a.feds-navLink { + border-bottom: 1px solid #2c2c2c; + padding: 20px; + font-size: 20px; + color: var(--feds-color-white-v2); +} + +/* + +/* Mega Menu */ + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + position: fixed; + transform: translate3d(0,0,0); /* render correctly on iOS Safari */ + -webkit-transform: translate3d(0,0,0); /* older iOS versions */ + top: calc(-1 * var(--feds-height-nav)); + visibility: hidden; + width: 100%; + height: calc(var(--feds-height-nav) + 100%); + background-color: var(--feds-background-nav); + transition: translate 0.4s ease-out, opacity 0.2s ease, visibility 0s linear 0.5s; + translate: 300vw 0; + display: grid; + grid-template-rows: 56px max-content auto 80px; + grid-template-columns: 111px 1fr; + grid-template-areas: + "top-bar top-bar" + "title title" + "tabs tab-panel" + "cta cta"; + box-sizing: content-box; /* Some DC pages use non default box-sizing globally */ +} + +.feds-promo-aside-wrapper + header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + top: calc(0px - var(--feds-height-nav) - var(--global-height-navPromo)); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + translate: -300vw 0; +} + +header.new-nav.local-nav .feds-nav > section.feds-navItem > .feds-popup { + translate: 0; +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active::before { + width: 0; +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup { + translate: 0; + opacity: 1; + z-index: 2; + transition: translate 0.4s ease-out, opacity 0.2s ease, visibility 0s linear 0s; + visibility: visible; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup { + translate: 0; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar { + grid-area: top-bar; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--feds-borderColor-link-v2); + padding: 0 20px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .feds-brand { + padding-left: 0; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .close-icon { + width:14px; + height:14px; + padding: 0; + border: 0; + font-family: var(--body-font-family); + margin-top: 2px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu { + font-size: 14px; + font-weight: 700; + line-height: 16px; + padding: 1px 0; + border: 0; + font-family: var(--body-font-family); + color: var(--text-color); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu svg { + translate: 0 2px; + margin-right: 7px; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu svg { + transform: rotate(180deg); + margin-right: unset; + margin-left: 7px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title { + grid-area: title; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--feds-borderColor-link-v2); + padding: 0 20px; + box-sizing: inherit; +} +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title .breadcrumbs { + display: block; /* even if the actual breadcrumbs are display none, we need the space to visible for the mobile gnav */ + height: 23px; + padding-top: 12px; + box-sizing: inherit; +} +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title h7 { + height: 25px; + font-size: 28px; + font-weight: 700; + line-height: 25px; + padding: 8px 0 24px; + box-sizing: inherit; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs { + grid-area: tabs; + display: flex; + flex-direction: column; + padding-top: 16px; + background-color: var(--feds-backgroundColor-tabs-v2); + overflow-y: auto; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs button { + min-height:48px; + width: 111px; + border: none; + padding: 8px 11px 8px 20px; + white-space: normal; + font-weight: 700; + text-align: unset; + color: var(--text-color); + font-family: var(--body-font-family); + font-size: 14px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs button[aria-selected="true"] { + background-color: var(--feds-color-black-v2); + color: var(--feds-color-white-v2); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content { + background-color: var(--feds-backgroundColor-tabContent-v2); + padding: 10px 13px 25px; + display: flex; + flex-direction: column; + gap: 28px; + font-size:14px; + line-height: 16px; + overflow-y: scroll; + border-left: 1px solid var(--feds-borderColor-link-v2); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content { + border-left: none; + border-right: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content a { + translate: 0 12px; + opacity: 0; + animation: slideup 0.6s ease, fadein 0.8s ease; + animation-fill-mode: forwards; + font-weight: 700; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink-title { + font-weight: 700; + white-space: break-spaces; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink--blue .feds-navLink-title { + font-weight: 400; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink-description { + display: inline-block; + font-weight: 400; + white-space: break-spaces; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .sticky-cta { + grid-area: cta; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + position:sticky; + border-top: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup .sticky-cta a { + width: 320px; + height: 39px; + border-radius: 50px; +} + +/* breadcrumbs new nav*/ +header.new-nav .feds-breadcrumbs-wrapper { + display: none; +} + +header.new-nav .feds-navItem--section { + display: flex; +} + +header.new-nav .feds-breadcrumbs { + padding: 2px 0 0; +} + +header.new-nav .feds-breadcrumbs ul { + font-weight: 700; +} + +header.new-nav .feds-breadcrumbs li:not(:first-of-type)::before { + padding: 0 4px; + color: var(--feds-color-link-breadcrumbs); +} + +header.new-nav .feds-breadcrumbs li:first-child:not(:nth-last-child(-n+3)):after { + content: '/ …'; + padding: 0 0 0 4px; + color: var(--feds-color-link-breadcrumbs); +} + +/* local-nav */ +.feds-localnav { + position: sticky; + top: 0px; + width: 100%; + left: 0px; + z-index: 9; +} + +.feds-localnav.has-promo { + top: var(--global-height-navPromo); +} + +.local-nav .feds-toggle[aria-expanded = "true"]:before { + content: "\2630"; +} + +.feds-localnav a { + text-decoration: unset; +} + +.feds-localnav-title { + width: 100%; + height: 40px; + font-size: 16px; + font-weight: 700; + border: 0; + padding: 0 20px; + text-align: justify; + color: var(--text-color); + cursor: pointer; + border-top: 1px solid var(--feds-borderColor-localnav-v2); + border-bottom: 1px solid var(--feds-borderColor-localnav-v2); + background-color: var(--feds-background-nav); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: var(--body-font-family); + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2); +} + +.feds-localnav-items { + padding: 20px 0 24px; + background: var(--feds-background-popup); + border-bottom: 1px solid var(--feds-borderColor-localnav-v2); + display: none; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); +} + +.feds-localnav-items .feds-navLink { + border: 0; +} + +.feds-localnav-items .feds-navItem.feds-navItem--active > .feds-navLink { + font-weight: 600; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items { + display: block; + width: 100%; + position: absolute; + overflow: auto; + top: var(--feds-localnav-height); +} + +.feds-localnav .feds-localnav-items li { + list-style: none; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-items { + padding: 0; + border-bottom: 0; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-column > ul { + padding: 0; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-column .feds-navLink { + padding-left: 38px; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-items .feds-navLink { + padding: 12px 56px; +} + +.feds-localnav .feds-localnav-items .feds-navLink { + border-bottom: 0; + font-size: 14px; + padding: 12px 20px; + background: var(--feds-background-popup); + outline-offset: -1px; + cursor: pointer; +} + +.feds-localnav .feds-localnav-items .feds-menu-headline { + background: var(--feds-background-popup); + font-weight: 400; + border-bottom: 0; + padding: 12px 38px; + outline-offset: -1px; + color: var(--feds-color-link); +} + +.feds-localnav .feds-localnav-items .feds-navItem--centered { + padding: 10px 20px; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items { + box-sizing: border-box; + max-height: calc(100dvh - (var(--global-height-nav) + var(--feds-localnav-height))); +} + +.feds-localnav.has-promo.feds-localnav--active .feds-localnav-items { + max-height: calc(100dvh - (var(--global-height-nav) + var(--feds-localnav-height) + var(--global-height-navPromo))); +} + +.feds-localnav.feds-localnav--active.is-sticky .feds-localnav-items { + max-height: calc(100dvh - var(--feds-localnav-height)); +} + +.feds-localnav .feds-localnav--active::before { + display: none; +} + +.feds-localnav .feds-localnav-exit { + display: none; +} + +.feds-localnav.feds-localnav--active .feds-localnav-title::after { + transform: rotateZ(-135deg); +} + +.feds-localnav.feds-localnav--active .feds-localnav-curtain { + width: 100%; + height: 101lvh; + background: var(--feds-color-black-v2); + opacity: 0.7; +} + +.feds-localnav.feds-localnav--active .feds-localnav-exit { + display: block; +} + +.feds-promo-aside-wrapper { + position: sticky; + top: 0; + z-index: 11; + height: var(--global-height-navPromo); +} + +@keyframes slideright { + from { + translate: 0 0; + } + to { + translate: -100px 0; + } +} + +@keyframes rtlslideright { + from { + translate: 0 0; + } + to { + translate: 100px 0; + } +} + +@keyframes slideleft { + from { + translate: -100px 0; + } + to { + translate: 0 0; + } +} + +@keyframes rtlslideleft { + from { + translate: 100px 0; + } + to { + translate: 0 0; + } +} + +@keyframes slideup { + from { + translate: 0 40px; + } + to { + translate: 0 0; + } +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeout { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +/* */ + +.disable-ios-scroll { + overflow: hidden; + width: 100%; + min-height: 100dvh; + position: fixed; +} + +html:has(body.disable-ios-scroll) { /* this class is only added on iOS */ + height: 100dvh; + position: fixed; } diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js index 515d97480f..0790786ae4 100644 --- a/libs/blocks/global-navigation/global-navigation.js +++ b/libs/blocks/global-navigation/global-navigation.js @@ -6,6 +6,7 @@ import { loadIms, decorateLinks, loadScript, + getGnavSource, } from '../../utils/utils.js'; import { closeAllDropdowns, @@ -38,6 +39,11 @@ import { darkIcons, setDisableAEDState, getDisableAEDState, + animateInSequence, + transformTemplateToMobile, + closeAllTabs, + disableMobileScroll, + enableMobileScroll, } from './utilities/utilities.js'; import { getFedsPlaceholderConfig } from '../../utils/federated.js'; @@ -227,10 +233,10 @@ const decorateProfileTrigger = async ({ avatar }) => { }; let keyboardNav; -const setupKeyboardNav = async () => { +const setupKeyboardNav = async (newMobileWithLnav) => { keyboardNav = keyboardNav || new Promise(async (resolve) => { const { default: KeyboardNavigation } = await import('./utilities/keyboard/index.js'); - const instance = new KeyboardNavigation(); + const instance = new KeyboardNavigation(newMobileWithLnav); resolve(instance); }); }; @@ -252,10 +258,12 @@ const getBrandImage = (image, brandImageOnly) => { return brandImageOnly ? CONFIG.icons.brand : CONFIG.icons.company; }; -const closeOnClickOutside = (e) => { - if (!isDesktop.matches) return; +const closeOnClickOutside = (e, isLocalNav, navWrapper) => { + if (isLocalNav && navWrapper.classList.contains('feds-nav-wrapper--expanded')) return; + const newMobileNav = getMetadata('mobile-gnav-v2') !== 'false'; + if (!isDesktop.matches && !newMobileNav) return; - const openElemSelector = `${selectors.globalNav} [aria-expanded = "true"]`; + const openElemSelector = `${selectors.globalNav} [aria-expanded = "true"], ${selectors.localNav} [aria-expanded = "true"]`; const isClickedElemOpen = [...document.querySelectorAll(openElemSelector)] .find((openItem) => openItem.parentElement.contains(e.target)); @@ -285,7 +293,7 @@ const convertToPascalCase = (str) => str .join(' '); class Gnav { - constructor({ content, block } = {}) { + constructor({ content, block, newMobileNav } = {}) { this.content = content; this.block = block; this.customLinks = getConfig()?.customLinks?.split(',') || []; @@ -301,8 +309,12 @@ class Gnav { this.setupUniversalNav(); this.elements = {}; + this.newMobileNav = newMobileNav; } + // eslint-disable-next-line no-return-assign + getOriginalTitle = (firstElem) => this.originalTitle ||= firstElem.textContent?.split('::'); + setupUniversalNav = () => { const meta = getMetadata('universal-nav')?.toLowerCase(); this.universalNavComponents = meta?.split(',').map((option) => option.trim()) @@ -323,24 +335,34 @@ class Gnav { // Order is important, decorateTopnavWrapper will render the nav // Ensure any critical task is executed before it const tasks = [ + // decorateAside is the only async function that fires prior to rendering + // (at time of writing). If there is no aside it returns sync -- no problem. + // But if there is, we need those functions (import + decorate) to enter the event loop + // before the delayed decorateDropdown function does. + // the rest is taken care of by the 'await' semantics + // We needn't worry about delays now since decorateAside + // needed to run anyway prior to decorateTopNavWrapper + this.decorateAside, this.decorateMainNav, this.decorateTopNav, - this.decorateAside, this.decorateTopnavWrapper, loadBaseStyles, this.ims, this.addChangeEventListeners, ]; + const fetchKeyboardNav = () => { + setupKeyboardNav(this.newMobileNav && this.isLocalNav()); + }; this.block.addEventListener('click', this.loadDelayed); - this.block.addEventListener('keydown', setupKeyboardNav); + this.block.addEventListener('keydown', fetchKeyboardNav); setTimeout(this.loadDelayed, CONFIG.delays.loadDelayed); - setTimeout(setupKeyboardNav, CONFIG.delays.keyboardNav); - for await (const task of tasks) { + setTimeout(fetchKeyboardNav, CONFIG.delays.keyboardNav); + for (const task of tasks) { await yieldToMain(); await task(); } - document.addEventListener('click', closeOnClickOutside); + document.addEventListener('click', (e) => closeOnClickOutside(e, this.isLocalNav(), this.elements.navWrapper)); isDesktop.addEventListener('change', closeAllDropdowns); }, 'Error in global navigation init', 'errorType=error,module=gnav'); @@ -380,17 +402,85 @@ class Gnav { `; }; + decorateLocalNav = async () => { + if (!this.isLocalNav()) return; + const localNavItems = this.elements.navWrapper.querySelector('.feds-nav').querySelectorAll('.feds-navItem:not(.feds-navItem--section, .feds-navItem--mobile-only)'); + const firstElem = localNavItems[0]?.querySelector('a'); + if (!firstElem) { + lanaLog({ message: 'GNAV: Incorrect authoring of localnav found.', tags: 'errorType=info,module=gnav' }); + return; + } + const [title, navTitle = ''] = this.getOriginalTitle(firstElem); + let localNav = document.querySelector('.feds-localnav'); + if (!localNav) { + lanaLog({ + message: 'GNAV: Localnav does not include \'localnav\' in its name.', + tags: 'errorType=info,module=gnav', + }); + localNav = toFragment`
`; + this.block.after(localNav); + } + localNav.setAttribute('daa-lh', `${title}_localNav`); + localNav.append(toFragment``, toFragment`
`, toFragment`
`, toFragment`.`); + + const itemWrapper = localNav.querySelector('.feds-localnav-items'); + const titleLabel = await replaceKey('overview', getFedsPlaceholderConfig()); + + localNavItems.forEach((elem, idx) => { + const clonedItem = elem.cloneNode(true); + const link = clonedItem.querySelector('a'); + + if (idx === 0) { + localNav.querySelector('.feds-localnav-title').innerText = title.trim(); + link.textContent = navTitle.trim() || titleLabel; + } + + itemWrapper.appendChild(clonedItem); + }); + + localNav.querySelector('.feds-localnav-title').addEventListener('click', () => { + localNav.classList.toggle('feds-localnav--active'); + const isActive = localNav.classList.contains('feds-localnav--active'); + localNav.querySelector('.feds-localnav-title').setAttribute('aria-expanded', isActive); + localNav.querySelector('.feds-localnav-title').setAttribute('daa-ll', `${title}_localNav|${isActive ? 'close' : 'open'}`); + }); + + localNav.querySelector('.feds-localnav-curtain').addEventListener('click', (e) => { + trigger({ element: e.currentTarget, event: e, type: 'localNav-curtain' }); + }); + const promo = document.querySelector('.feds-promo-aside-wrapper'); + if (promo) localNav.classList.add('has-promo'); + this.elements.localNav = localNav; + localNavItems[0].querySelector('a').textContent = title.trim(); + const isAtTop = () => { + const rect = this.elements.localNav.getBoundingClientRect(); + // note: ios safari changes between -0.34375, 0, and 0.328125 + return rect.top === 0; + }; + window.addEventListener('scroll', () => { + const classList = this.elements.localNav?.classList; + if (isAtTop()) { + if (!classList?.contains('is-sticky')) { + classList?.add('is-sticky'); + } + } else { + classList?.remove('is-sticky'); + } + }); + }; + decorateTopnavWrapper = async () => { const breadcrumbs = isDesktop.matches ? await this.decorateBreadcrumbs() : ''; this.elements.topnavWrapper = toFragment`
${this.elements.topnav} ${breadcrumbs} -
`; +
+ `; - this.block.append(this.elements.curtain, this.elements.aside, this.elements.topnavWrapper); - // TODO: Remove with mobile redesign code - const firstLocalNavItem = this.elements.navWrapper.querySelector('.feds-nav .feds-navItem:not(.feds-navItem--section) a'); - if (firstLocalNavItem) [firstLocalNavItem.textContent] = firstLocalNavItem.textContent.split('|'); + this.block.append( + this.elements.curtain, + this.elements.topnavWrapper, + ); }; addChangeEventListeners = () => { @@ -703,9 +793,32 @@ class Gnav { isToggleExpanded = () => this.elements.mobileToggle?.getAttribute('aria-expanded') === 'true'; + isLocalNav = () => this + .elements + .navWrapper + ?.querySelectorAll('.feds-nav > section.feds-navItem') + ?.length <= 1; + + hasMegaMenu = () => this + .elements + .navWrapper + ?.querySelectorAll('.feds-nav > section.feds-navItem') + ?.length >= 1; + toggleMenuMobile = () => { const toggle = this.elements.mobileToggle; const isExpanded = this.isToggleExpanded(); + if (!isExpanded && this.newMobileNav) { + const sections = document.querySelectorAll('header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink'); + animateInSequence(sections, 0.075); + if (this.isLocalNav() && this.hasMegaMenu()) { + disableMobileScroll(); + const section = sections[0]; + queueMicrotask(() => section.click()); + } + } else if (isExpanded && this.newMobileNav && this.isLocalNav()) { + enableMobileScroll(); + } toggle?.setAttribute('aria-expanded', !isExpanded); document.body.classList.toggle('disable-scroll', !isExpanded); this.elements.navWrapper?.classList?.toggle('feds-nav-wrapper--expanded', !isExpanded); @@ -715,7 +828,7 @@ class Gnav { }; decorateToggle = () => { - if (!this.mainNavItemCount) return ''; + if (!this.mainNavItemCount || (this.newMobileNav && !this.hasMegaMenu())) return ''; const toggle = toFragment` + `; + const brand = document.querySelector('.feds-brand')?.outerHTML; + const breadCrumbs = document.querySelector('.feds-breadcrumbs')?.outerHTML; + popup.innerHTML = ` +
+ ${localnav ? brand : await replaceText(mainMenu, getFedsPlaceholderConfig())} + +
+
+ ${breadCrumbs || ''} + ${item.textContent.trim()} +
+
+ ${tabs.map(({ name, daallTab }, i) => ` + + `).join('')} +
+
+ ${tabs.map(({ links, daalhTabContent }, i) => ` + `).join('')} +
+
+ ${CTA} +
+ `; + + popup.querySelector('.close-icon')?.addEventListener('click', () => { + document.querySelector(selectors.mainNavToggle).focus(); + closeAllDropdowns(); + enableMobileScroll(); + }); + popup.querySelector('.main-menu')?.addEventListener('click', (e) => { + e.target.closest(selectors.activeDropdown).querySelector('button').focus(); + enableMobileScroll(); + closeAllDropdowns(); + }); + const tabbuttons = popup.querySelectorAll('.tabs button'); + const tabpanels = popup.querySelectorAll('.tab-content [role="tabpanel"]'); + + tabpanels.forEach((panel) => { + animateInSequence(panel.querySelectorAll('a'), 0.02); + }); + + tabbuttons.forEach((tab, i) => { + tab.addEventListener('click', () => { + closeAllTabs(tabbuttons, tabpanels); + tabpanels?.[i]?.removeAttribute('hidden'); + tab.setAttribute('aria-selected', 'true'); + }); + }); + return originalContent; +}; + +export const takeWhile = (xs, f) => { + const r = []; + for (let i = 0; i < xs.length; i += 1) { + if (!f(xs[i])) return r; + r.push(xs[i]); + } + return r; +}; + +export const dropWhile = (xs, f) => { + if (!xs.length) return xs; + if (f(xs[0])) return dropWhile(xs.slice(1), f); + return xs; +}; diff --git a/libs/blocks/section-metadata/sticky-section.js b/libs/blocks/section-metadata/sticky-section.js index e3d36a1e20..4c3fba4320 100644 --- a/libs/blocks/section-metadata/sticky-section.js +++ b/libs/blocks/section-metadata/sticky-section.js @@ -2,8 +2,17 @@ import { createTag } from '../../utils/utils.js'; import { getMetadata, getDelayTime } from './section-metadata.js'; function handleTopHeight(section) { - const headerHeight = document.querySelector('header')?.offsetHeight ?? 0; - section.style.top = `${headerHeight}px`; + let topHeight = document.querySelector('header')?.offsetHeight ?? 0; + const localNav = document.querySelector('.feds-localnav'); + const fedsPromo = document.querySelector('.feds-promo-wrapper'); + if (localNav) { + topHeight = localNav.offsetHeight; + } + if (fedsPromo) { + topHeight += fedsPromo.offsetHeight; + } + + section.style.top = `${topHeight}px`; } let isFooterStart = false; diff --git a/libs/blocks/tabs/tabs.js b/libs/blocks/tabs/tabs.js index b32e5bcbbe..102ef7e508 100644 --- a/libs/blocks/tabs/tabs.js +++ b/libs/blocks/tabs/tabs.js @@ -37,7 +37,8 @@ const removeAttributes = (el, attrsKeys) => { const scrollStackedMobile = (content) => { if (!window.matchMedia('(max-width: 600px)').matches) return; const rects = content.getBoundingClientRect(); - const navHeight = document.querySelector('.global-navigation, .gnav')?.scrollHeight || 0; + const stickyTop = document.querySelector('.feds-localnav') ?? document.querySelector('.global-navigation, .gnav'); + const navHeight = stickyTop?.scrollHeight || 0; const topOffset = rects.top + window.scrollY - navHeight - 1; window.scrollTo({ top: topOffset, behavior: 'smooth' }); }; diff --git a/libs/mep/ace0861/section-metadata/sticky-section.js b/libs/mep/ace0861/section-metadata/sticky-section.js index 378ccc5d9e..a54f8518c1 100644 --- a/libs/mep/ace0861/section-metadata/sticky-section.js +++ b/libs/mep/ace0861/section-metadata/sticky-section.js @@ -2,8 +2,17 @@ import { createTag } from '../../../utils/utils.js'; import { getMetadata, getDelayTime } from './section-metadata.js'; function handleTopHeight(section) { - const headerHeight = document.querySelector('header').offsetHeight; - section.style.top = `${headerHeight}px`; + let topHeight = document.querySelector('header').offsetHeight; + const localNav = document.querySelector('.feds-localnav'); + const fedsPromo = document.querySelector('.feds-promo-wrapper'); + if (localNav) { + topHeight = localNav.offsetHeight; + } + if (fedsPromo) { + topHeight += fedsPromo.offsetHeight; + } + + section.style.top = `${topHeight}px`; } function promoIntersectObserve(el, stickySectionEl, options = {}) { diff --git a/libs/navigation/bootstrapper.js b/libs/navigation/bootstrapper.js index 55778464cc..2b76e09dd1 100644 --- a/libs/navigation/bootstrapper.js +++ b/libs/navigation/bootstrapper.js @@ -23,6 +23,7 @@ export default async function bootstrapBlock(initBlock, blockConfig) { const metaTags = [ { key: 'unavComponents', name: 'universal-nav' }, { key: 'redirect', name: 'adobe-home-redirect' }, + { key: 'mobileGnavV2', name: 'mobile-gnav-v2' }, ]; metaTags.forEach((tag) => { const { key } = tag; @@ -34,6 +35,10 @@ export default async function bootstrapBlock(initBlock, blockConfig) { document.head.append(metaTag); } }); + if (blockConfig.isLocalNav) { + const localNavWrapper = createTag('div', { class: 'feds-localnav' }); + document.querySelector('header').after(localNavWrapper); + } } await initBlock(document.querySelector(targetEl)); diff --git a/libs/navigation/navigation.css b/libs/navigation/navigation.css index ce0aae2d06..215354b5e4 100644 --- a/libs/navigation/navigation.css +++ b/libs/navigation/navigation.css @@ -7,6 +7,8 @@ :root { --navigation-link-color: #035FE6; --navigation-link-color--hover: #136FF6; + --feds-localnav-height: 40px; + --global-height-nav: 64px; } .global-navigation, .global-footer, .dialog-modal { @@ -45,6 +47,11 @@ header.global-navigation, header.global-navigation.feds--dark { visibility: hidden; } +header.global-navigation + .feds-localnav { + display: block; + height: var(--feds-localnav-height); +} + @media (min-width: 900px) { .feds-promo-link { color: var(--navigation-link-color); @@ -62,6 +69,10 @@ header.global-navigation, header.global-navigation.feds--dark { max-width: none; padding: 0 15px; } + + header.global-navigation + .feds-localnav { + display: none; + } } .feds-client-search { diff --git a/libs/navigation/navigation.js b/libs/navigation/navigation.js index efe77fd6ca..132900a057 100644 --- a/libs/navigation/navigation.js +++ b/libs/navigation/navigation.js @@ -72,8 +72,8 @@ export default async function loadBlock(configs, customLib) { env = 'prod', locale = '', theme, - allowedOrigins = [], stageDomainsMap = {}, + allowedOrigins = [], } = configs || {}; if (!header && !footer) { // eslint-disable-next-line no-console @@ -110,18 +110,18 @@ export default async function loadBlock(configs, customLib) { ]); const paramConfigs = getParamsConfigs(configs); const clientConfig = { + theme, + prodDomains, clientEnv: env, - origin: `https://main--federal--adobecom.aem.${env === 'prod' ? 'live' : 'page'}`, - miloLibs: `${miloLibs}/libs`, + standaloneGnav: true, pathname: `/${locale}`, + miloLibs: `${miloLibs}/libs`, locales: configs.locales || locales, contentRoot: authoringPath || footer.authoringPath, - theme, - ...paramConfigs, - prodDomains, - allowedOrigins: [...allowedOrigins, `https://main--federal--adobecom.aem.${env === 'prod' ? 'live' : 'page'}`], - standaloneGnav: true, stageDomainsMap: getStageDomainsMap(stageDomainsMap), + origin: `https://main--federal--adobecom.aem.${env === 'prod' ? 'live' : 'page'}`, + allowedOrigins: [...allowedOrigins, `https://main--federal--adobecom.aem.${env === 'prod' ? 'live' : 'page'}`], + ...paramConfigs, }; setConfig(clientConfig); for await (const block of blockConfig) { @@ -137,6 +137,8 @@ export default async function loadBlock(configs, customLib) { layout: configBlock.layout, noBorder: configBlock.noBorder, jarvis: configBlock.jarvis, + isLocalNav: configBlock.isLocalNav, + mobileGnavV2: configBlock.mobileGnavV2 || 'off', }); } else if (block.key === 'footer') { try { diff --git a/libs/styles/styles.css b/libs/styles/styles.css index 839966a67e..50f47b1bec 100644 --- a/libs/styles/styles.css +++ b/libs/styles/styles.css @@ -6,6 +6,7 @@ /* stylelint-disable-next-line custom-property-pattern */ --global-height-navPromo: 72px; --feds-totalheight-nav: calc(var(--feds-height-nav, --global-height-nav) + var(--feds-height-breadcrumbs, --global-height-breadcrumbs)); + --feds-localnav-height: 40px; /* Colors */ --link-color: rgb(59, 99, 251); @@ -715,19 +716,44 @@ header.global-navigation, header.global-navigation.feds--dark { visibility: hidden; } +header.global-navigation a { + text-decoration: unset; +} + +header.global-navigation + .feds-localnav { + display: block; + height: var(--feds-localnav-height); +} + header.global-navigation.has-promo { - height: auto; - min-height: calc(var(--global-height-nav) + var(--global-height-navPromo)); + height: var(--global-height-navPromo); + top: var(--global-height-navPromo); } -header.global-navigation a { - text-decoration: unset; +header.global-navigation.has-promo:has(+ .feds-localnav) { + top: 0; +} + +.feds-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } @media (min-width: 900px) { header.global-navigation.has-breadcrumbs { padding-bottom: var(--global-height-breadcrumbs); } + + header.global-navigation + .feds-localnav { + display: none; + } } .breadcrumbs { diff --git a/libs/utils/federated.js b/libs/utils/federated.js index c3e3d3d24e..d21f1354a6 100644 --- a/libs/utils/federated.js +++ b/libs/utils/federated.js @@ -12,7 +12,7 @@ export const getFederatedContentRoot = () => { ]; const { allowedOrigins = [], origin: configOrigin } = getConfig(); if (federatedContentRoot) return federatedContentRoot; - // Non milo consumers will have its origin from congig + // Non milo consumers will have its origin from config const origin = configOrigin || window.location.origin; federatedContentRoot = [...allowedOrigins, ...cdnWhitelistedOrigins].some((o) => origin.replace('.stage', '') === o) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index c5a6ee83a1..10f91e0d30 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -773,7 +773,17 @@ function decorateDefaults(el) { }); } -function decorateHeader() { +export async function getGnavSource() { + const { locale, dynamicNavKey } = getConfig(); + let url = getMetadata('gnav-source') || `${locale.contentRoot}/gnav`; + if (dynamicNavKey) { + const { default: dynamicNav } = await import('../features/dynamic-navigation/dynamic-navigation.js'); + url = dynamicNav(url, dynamicNavKey); + } + return url; +} + +async function decorateHeader() { const breadcrumbs = document.querySelector('.breadcrumbs'); breadcrumbs?.remove(); const header = document.querySelector('header'); @@ -794,9 +804,19 @@ function decorateHeader() { const dynamicNavActive = getMetadata('dynamic-nav') === 'on' && window.sessionStorage.getItem('gnavSource') !== null; if (!dynamicNavActive && (baseBreadcrumbs || breadcrumbs || autoBreadcrumbs)) header.classList.add('has-breadcrumbs'); + const gnavSource = await getGnavSource(); + if (gnavSource.split('/').pop().startsWith('localnav-') && getMetadata('mobile-gnav-v2') !== 'off') { + // Preserving space to avoid CLS issue + const localNavWrapper = createTag('div', { class: 'feds-localnav' }); + header.after(localNavWrapper); + } if (breadcrumbs) header.append(breadcrumbs); const promo = getMetadata('gnav-promo-source'); - if (promo?.length) header.classList.add('has-promo'); + if (promo?.length) { + const fedsPromoWrapper = createTag('div', { class: 'feds-promo-aside-wrapper' }); + header.before(fedsPromoWrapper); + header.classList.add('has-promo'); + } } async function decorateIcons(area, config) { diff --git a/test/blocks/global-navigation/global-navigation.test.js b/test/blocks/global-navigation/global-navigation.test.js index 7bee7cdcc2..04f517ac7c 100644 --- a/test/blocks/global-navigation/global-navigation.test.js +++ b/test/blocks/global-navigation/global-navigation.test.js @@ -11,6 +11,7 @@ import { unavLocalesTestData, analyticsTestData, unavVersion, + addMetaDataV2, } from './test-utilities.js'; import { setConfig, getLocale } from '../../../libs/utils/utils.js'; import initNav, { getUniversalNavLocale, osMap } from '../../../libs/blocks/global-navigation/global-navigation.js'; @@ -20,6 +21,7 @@ import longNav from './mocks/global-navigation-long.plain.js'; import darkNav from './mocks/dark-global-navigation.plain.js'; import navigationWithCustomLinks from './mocks/navigation-with-custom-links.plain.js'; import globalNavigationMock from './mocks/global-navigation.plain.js'; +import gnavWithlocalNav from './mocks/gnav-with-localnav.plain.js'; import noDropdownNav from './mocks/global-navigation-no-dropdown.plain.js'; import productEntryCTA from './mocks/global-navigation-product-entry-cta.plain.js'; import { getConfig } from '../../../tools/send-to-caas/send-utils.js'; @@ -293,8 +295,8 @@ describe('global navigation', () => { describe('Viewport changes', () => { it('should render desktop -> small desktop -> mobile', async () => { + document.head.appendChild(addMetaDataV2('off')); const nav = await createFullGlobalNavigation(); - expect(nav).to.exist; expect(isElementVisible(document.querySelector(selectors.globalNav))).to.equal(true); expect(isElementVisible(document.querySelector(selectors.search))).to.equal(true); @@ -323,7 +325,6 @@ describe('global navigation', () => { await setViewport(viewports.mobile); isDesktop.dispatchEvent(new Event('change')); - expect(isElementVisible(document.querySelector(selectors.globalNav))).to.equal(true); expect(isElementVisible(document.querySelector(selectors.search))).to.equal(false); expect(isElementVisible(document.querySelector(selectors.profile))).to.equal(true); @@ -695,4 +696,93 @@ describe('global navigation', () => { ).to.equal(customLinks.split(',').length); }); }); + + describe('local nav scenarios', () => { + let clock; + + beforeEach(async () => { + clock = sinon.useFakeTimers({ + toFake: ['setTimeout'], + shouldAdvanceTime: true, + }); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should load Local Nav', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav }); + const localNav = document.querySelector(selectors.localNav); + expect(!!localNav).to.be.true; + }); + + it('should open local nav on click of localnav title', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav }); + const localNavTitle = document.querySelector(selectors.localNavTitle); + localNavTitle.click(); + const localNav = document.querySelector(selectors.localNav); + expect(localNav.classList.contains('feds-localnav--active')).to.be.true; + }); + + it('should remove is-sticky class to localnav on scroll less than localnav placement', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav }); + const localNav = document.querySelector(selectors.localNav); + sinon.stub(localNav, 'getBoundingClientRect').returns({ top: 20 }); + window.dispatchEvent(new Event('scroll')); + const localNavAfterScroll = document.querySelector(selectors.localNav); + expect(localNavAfterScroll.classList.contains('is-sticky')).to.be.false; + }); + + it('should add is-sticky class to localnav on scroll greater than localnav placement', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav }); + const localNav = document.querySelector(selectors.localNav); + sinon.stub(localNav, 'getBoundingClientRect').returns({ top: 0 }); + window.dispatchEvent(new Event('scroll')); + const localNavAfterScroll = document.querySelector(selectors.localNav); + expect(localNavAfterScroll.classList.contains('is-sticky')).to.be.true; + }); + + it('should open both screen if localnav is present but shows only level 2 screen', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav, viewport: 'mobile' }); + const toggle = document.querySelector(selectors.mainNavToggle); + toggle.click(); + await clock.runAllAsync(); + const fedsNavWrapper = document.querySelector(selectors.navWrapper); + const largemenu = document.querySelector(selectors.largeMenu); + expect(fedsNavWrapper.classList.contains('feds-nav-wrapper--expanded')).to.be.true; + expect(largemenu.classList.contains('feds-dropdown--active')).to.be.true; + }); + + it('should expand nested dropdowm if click on headline', async () => { + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav, viewport: 'mobile' }); + const localNavTitle = document.querySelector(selectors.localNavTitle); + localNavTitle.click(); + localNavTitle.focus(); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + document.activeElement.click(); + expect(document.activeElement.getAttribute('aria-expanded')).to.equal('true'); + const headline = document.activeElement.parentElement.querySelector('.feds-menu-headline'); + headline.click(); + expect(headline.getAttribute('aria-expanded')).to.equal('true'); + }); + it('disables scroll for the popup but not for the localnav', async () => { + Object.defineProperty(navigator, 'userAgent', { get: () => 'Safari' }); + await createFullGlobalNavigation({ globalNavigation: gnavWithlocalNav, viewport: 'mobile' }); + const localNavTitle = document.querySelector(selectors.localNavTitle); + localNavTitle.click(); + const localNav = document.querySelector(selectors.localNav); + const curtain = localNav.querySelector('.feds-localnav-curtain'); + expect(document.body.classList.contains('disable-ios-scroll')).to.equal(false); + curtain.click(); + expect(document.body.classList.contains('disable-ios-scroll')).to.equal(false); + const toggle = document.querySelector(selectors.mainNavToggle); + toggle.click(); + expect(document.body.classList.contains('disable-ios-scroll')).to.equal(true); + const close = document.querySelector('.close-icon'); + close.click(); + expect(document.body.classList.contains('disable-ios-scroll')).to.equal(false); + }); + }); }); diff --git a/test/blocks/global-navigation/gnav-cross-cloud.test.js b/test/blocks/global-navigation/gnav-cross-cloud.test.js index 000fc0372b..a1963855ab 100644 --- a/test/blocks/global-navigation/gnav-cross-cloud.test.js +++ b/test/blocks/global-navigation/gnav-cross-cloud.test.js @@ -5,6 +5,7 @@ import { selectors, isElementVisible, unavVersion, + addMetaDataV2, } from './test-utilities.js'; import globalNavigationCrossCloud from './mocks/global-navigation-cross-cloud.plain.js'; @@ -18,6 +19,7 @@ describe('Cross Cloud Menu', () => { describe('desktop', () => { it('should render the Cross Cloud Menu', async () => { + document.head.appendChild(addMetaDataV2('off')); await createFullGlobalNavigation({ globalNavigation: globalNavigationCrossCloud }); const crossCloudMenu = document.querySelector(selectors.crossCloudMenuWrapper); diff --git a/test/blocks/global-navigation/gnav-main-nav-popup.test.js b/test/blocks/global-navigation/gnav-main-nav-popup.test.js index 3799a07514..b59edf0387 100644 --- a/test/blocks/global-navigation/gnav-main-nav-popup.test.js +++ b/test/blocks/global-navigation/gnav-main-nav-popup.test.js @@ -6,6 +6,7 @@ import { selectors, isElementVisible, unavVersion, + addMetaDataV2, } from './test-utilities.js'; import { toFragment } from '../../../libs/blocks/global-navigation/utilities/utilities.js'; import globalNavigationMock from './mocks/global-navigation.plain.js'; @@ -47,6 +48,7 @@ describe('main nav popups', () => { }); it('should render popups with wide columns', async () => { + document.head.appendChild(addMetaDataV2('off')); await createFullGlobalNavigation({ globalNavigation: globalNavigationWideColumnMock }); expect(document.querySelector('.feds-navItem--section .feds-menu-column--group .feds-menu-column + .feds-menu-column')).to.exist; expect(document.querySelector('.column-break')).to.not.exist; @@ -139,6 +141,7 @@ describe('main nav popups', () => { }); it('should open a popup and headline on click', async () => { + document.head.appendChild(addMetaDataV2('off')); await createFullGlobalNavigation({ viewport: 'mobile' }); document.querySelector(selectors.mainNavToggle).click(); diff --git a/test/blocks/global-navigation/gnav-main-nav.test.js b/test/blocks/global-navigation/gnav-main-nav.test.js index 5a09cb5518..ea253dc4d1 100644 --- a/test/blocks/global-navigation/gnav-main-nav.test.js +++ b/test/blocks/global-navigation/gnav-main-nav.test.js @@ -8,6 +8,7 @@ import { isElementVisible, viewports, unavVersion, + addMetaDataV2, } from './test-utilities.js'; import { isDesktop, setActiveLink, toFragment } from '../../../libs/blocks/global-navigation/utilities/utilities.js'; import globalNavigationActiveMock from './mocks/global-navigation-active.plain.js'; @@ -150,6 +151,7 @@ describe('main nav', () => { }); it('marks simple link as active', async () => { + document.head.appendChild(addMetaDataV2('off')); const targetSelector = '#simple-link'; const template = toFragment`
`; template.innerHTML = globalNavigationActiveMock; @@ -164,6 +166,7 @@ describe('main nav', () => { }); it('marks item with sync dropdown containing active link', async () => { + document.head.appendChild(addMetaDataV2('off')); const targetSelector = '#link-in-dropdown'; const template = toFragment`
`; template.innerHTML = globalNavigationActiveMock; @@ -175,6 +178,7 @@ describe('main nav', () => { }); it('marks item from a nav with a single async dropdown containing active link', async () => { + document.head.appendChild(addMetaDataV2('off')); await createFullGlobalNavigation({ globalNavigation: globalNavigationActiveMock }); const sections = document.querySelectorAll('section.feds-navItem--section'); expect(sections.length).to.equal(1); @@ -207,6 +211,7 @@ describe('main nav', () => { }); it('marks a single item as active if multiple links match URL', async () => { + document.head.appendChild(addMetaDataV2('off')); const targetSelector1 = '#simple-link'; const targetSelector2 = '#link-in-dropdown'; const template = toFragment`
`; diff --git a/test/blocks/global-navigation/gnav-promo.test.js b/test/blocks/global-navigation/gnav-promo.test.js index eede5833c5..58ac789836 100644 --- a/test/blocks/global-navigation/gnav-promo.test.js +++ b/test/blocks/global-navigation/gnav-promo.test.js @@ -19,27 +19,31 @@ describe('Promo', () => { it('doesn\'t exist if metadata is not referencing a fragment', async () => { const wrongPromoMeta = toFragment``; document.head.append(wrongPromoMeta); - const nav = await createFullGlobalNavigation({ hasPromo: true }); + const nav = await createFullGlobalNavigation({ hasPromo: true, hasBreadcrumbs: false }); expect(nav.block.classList.contains('has-promo')).to.be.false; - expect(nav.block.querySelector('.aside.promobar')).to.equal(null); + expect(document.body.querySelector('.aside.promobar')).to.equal(null); wrongPromoMeta.remove(); }); it('doesn\'t exist if fragment doesn\'t contain an aside block', async () => { const promoMeta = toFragment``; document.head.append(promoMeta); - const nav = await createFullGlobalNavigation({ hasPromo: true }); + const nav = await createFullGlobalNavigation({ hasPromo: true, hasBreadcrumbs: false }); expect(nav.block.classList.contains('has-promo')).to.be.false; - expect(nav.block.querySelector('.aside.promobar')).to.equal(null); + expect(document.body.querySelector('.aside.promobar')).to.equal(null); promoMeta.remove(); }); it('is available if set up correctly', async () => { const promoMeta = toFragment``; document.head.append(promoMeta); - const nav = await createFullGlobalNavigation({ hasPromo: true }); + const nav = await createFullGlobalNavigation({ + hasPromo: true, + imsInitialized: true, + hasBreadcrumbs: false, + }); expect(nav.block.classList.contains('has-promo')).to.be.true; - const asideElem = nav.block.querySelector('.aside.promobar'); + const asideElem = document.body.querySelector('.aside.promobar'); expect(asideElem).to.exist; expect(asideElem.getAttribute('daa-lh')).to.equal('Promo'); asideElem.querySelectorAll('a').forEach((linkElem) => { diff --git a/test/blocks/global-navigation/keyboard/keyboard.test.js b/test/blocks/global-navigation/keyboard/keyboard.test.js index fb5476010b..f21faf1b4f 100644 --- a/test/blocks/global-navigation/keyboard/keyboard.test.js +++ b/test/blocks/global-navigation/keyboard/keyboard.test.js @@ -401,10 +401,11 @@ describe('keyboard navigation', () => { describe('ArrowRight', () => { it('does nothing', async () => { for await (const element of otherNavItems) { - if (!isElementVisible(element)) continue; - element.focus(); - await sendKeys({ press: 'ArrowRight' }); - expect(document.activeElement).to.equal(element); + if (isElementVisible(element)) { + element.focus(); + await sendKeys({ press: 'ArrowRight' }); + expect(document.activeElement).to.equal(element); + } } }); }); @@ -412,10 +413,11 @@ describe('keyboard navigation', () => { describe('ArrowLeft', () => { it('does nothing', async () => { for await (const element of otherNavItems) { - if (!isElementVisible(element)) continue; - element.focus(); - await sendKeys({ press: 'ArrowLeft' }); - expect(document.activeElement).to.equal(element); + if (isElementVisible(element)) { + element.focus(); + await sendKeys({ press: 'ArrowLeft' }); + expect(document.activeElement).to.equal(element); + } } }); }); @@ -423,10 +425,11 @@ describe('keyboard navigation', () => { describe('ArrowUp', () => { it('does nothing', async () => { for await (const element of otherNavItems) { - if (!isElementVisible(element)) continue; - element.focus(); - await sendKeys({ press: 'ArrowUp' }); - expect(document.activeElement).to.equal(element); + if (isElementVisible(element)) { + element.focus(); + await sendKeys({ press: 'ArrowUp' }); + expect(document.activeElement).to.equal(element); + } } }); }); @@ -434,10 +437,11 @@ describe('keyboard navigation', () => { describe('ArrowDown', () => { it('nothing', async () => { for await (const element of otherNavItems) { - if (!isElementVisible(element)) continue; - element.focus(); - await sendKeys({ press: 'ArrowDown' }); - expect(document.activeElement).to.equal(element); + if (isElementVisible(element)) { + element.focus(); + await sendKeys({ press: 'ArrowDown' }); + expect(document.activeElement).to.equal(element); + } } }); }); @@ -805,16 +809,6 @@ describe('keyboard navigation', () => { }); describe('ArrowLeft', () => { - it('shifts focus to the previous section', async () => { - await sendKeys({ press: 'ArrowRight' }); - await sendKeys({ press: 'ArrowRight' }); - expect(document.activeElement.innerText).to.equal('second-column-first-section-first-item'); - await sendKeys({ press: 'ArrowLeft' }); - expect(document.activeElement.innerText).to.equal('first-column-second-section-first-item'); - await sendKeys({ press: 'ArrowLeft' }); - expect(document.activeElement).to.equal(trigger); - }); - it('shifts focus from the first popup item back to the trigger', async () => { await sendKeys({ press: 'ArrowLeft' }); expect(document.activeElement).to.equal(trigger); @@ -1053,4 +1047,114 @@ describe('keyboard navigation', () => { } }); }); + + describe('new mobile GNAV redesign', () => { + describe('Without localnav', () => { + beforeEach(async () => { + setViewport({ width: 600, height: 600 }); + document.body.innerHTML = await readFile({ path: './mocks/global-new-nav-mobile.html' }); + keyboardNavigation = new KeyboardNavigation(); + keyboardNavigation.mainNav.popup.desktop = { matches: false }; + }); + + it('shift focus on tab for links on level 1 screen', async () => { + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + const allMainMenuLinks = document.querySelectorAll('.feds-nav section > button'); + expect(document.activeElement).to.equal(allMainMenuLinks[2]); + }); + + it('shift focus on Escape to trigger element', async () => { + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Escape' }); + const toggle = document.querySelector('header.new-nav .feds-toggle'); + expect(document.activeElement).to.equal(toggle); + }); + }); + + describe('With localnav', () => { + beforeEach(async () => { + setViewport({ width: 600, height: 600 }); + document.body.innerHTML = await readFile({ path: './mocks/global-new-nav-mobile-localnav.html' }); + keyboardNavigation = new KeyboardNavigation(true); + keyboardNavigation.mainNav.popup.desktop = { matches: false }; + }); + + it('shift focus on tab for links on level 2 screen', async () => { + const firstTab = document.querySelector('header .feds-nav .tabs .tab'); + firstTab.focus(); + const allNavLinks = document.querySelectorAll('header .feds-nav section .feds-popup .tab-content > div:not([hidden="true"]) .feds-navLink'); + await sendKeys({ press: 'Tab' }); + expect(document.activeElement).to.equal(allNavLinks[0]); + }); + + it('shift focus on Escape to trigger element', async () => { + const firstTab = document.querySelector('header .feds-nav .tabs .tab'); + firstTab.focus(); + await sendKeys({ press: 'Escape' }); + const toggle = document.querySelector('header.new-nav .feds-toggle'); + expect(document.activeElement).to.equal(toggle); + }); + }); + + describe('Local Nav keyboard navigation', () => { + beforeEach(async () => { + setViewport({ width: 600, height: 600 }); + document.body.innerHTML = await readFile({ path: './mocks/localnav-mock.html' }); + keyboardNavigation = new KeyboardNavigation(true); + keyboardNavigation.mainNav.popup.desktop = { matches: false }; + }); + + it('Should open the section on Space', async () => { + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Space' }); + const headline = document.querySelector('.feds-localnav .feds-menu-headline'); + expect(document.activeElement).to.equal(headline); + }); + + it('Should focus back to localnav title after navigating to all the visible links of local nav', async () => { + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + const localnavTitle = document.querySelector('.feds-localnav-title'); + expect(document.activeElement).to.equal(localnavTitle); + }); + it('closes when escape is pressed', async () => { + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Space' }); + await sendKeys({ press: 'Escape' }); + expect(document.querySelector('feds-localnav--active')).to.not.exist; + }); + }); + }); }); diff --git a/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile-localnav.html b/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile-localnav.html new file mode 100644 index 0000000000..c3388328bc --- /dev/null +++ b/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile-localnav.html @@ -0,0 +1,176 @@ + diff --git a/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile.html b/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile.html new file mode 100644 index 0000000000..a3e200d5d6 --- /dev/null +++ b/test/blocks/global-navigation/keyboard/mocks/global-new-nav-mobile.html @@ -0,0 +1,518 @@ + diff --git a/test/blocks/global-navigation/keyboard/mocks/localnav-mock.html b/test/blocks/global-navigation/keyboard/mocks/localnav-mock.html new file mode 100644 index 0000000000..5b16488b88 --- /dev/null +++ b/test/blocks/global-navigation/keyboard/mocks/localnav-mock.html @@ -0,0 +1,457 @@ + + diff --git a/test/blocks/global-navigation/mocks/gnav-with-localnav.plain.js b/test/blocks/global-navigation/mocks/gnav-with-localnav.plain.js new file mode 100644 index 0000000000..bae6eb2e0f --- /dev/null +++ b/test/blocks/global-navigation/mocks/gnav-with-localnav.plain.js @@ -0,0 +1,70 @@ +export default ` + + +
+
+
+
+

Create

+
+
+
+
+
+

Education

+
K-12 Schools
+ +
Higher Ed
+ +
+
+
+

+ + + + + Hello I am alt text + +

+

www.adobe.com

+
+
+
+
+ + + + +
+`; diff --git a/test/blocks/global-navigation/mocks/mock-cc-column-1.plain.js b/test/blocks/global-navigation/mocks/mock-cc-column-1.plain.js new file mode 100644 index 0000000000..8f64d12b9c --- /dev/null +++ b/test/blocks/global-navigation/mocks/mock-cc-column-1.plain.js @@ -0,0 +1,49 @@ +export default ` + +
+
Shop for
+ + + + + +

View plans and pricing

+
+ +`; diff --git a/test/blocks/global-navigation/mocks/mock-cc-column-2.plain.js b/test/blocks/global-navigation/mocks/mock-cc-column-2.plain.js new file mode 100644 index 0000000000..14213156d9 --- /dev/null +++ b/test/blocks/global-navigation/mocks/mock-cc-column-2.plain.js @@ -0,0 +1,68 @@ +export default ` + +
+ + + + + + + + + +
+ +`; diff --git a/test/blocks/global-navigation/mocks/mock-megamenu.plain.js b/test/blocks/global-navigation/mocks/mock-megamenu.plain.js new file mode 100644 index 0000000000..e6aaa2cd75 --- /dev/null +++ b/test/blocks/global-navigation/mocks/mock-megamenu.plain.js @@ -0,0 +1,361 @@ +// Uses the franklin structure without any customizations +export default `
+
Shop For
+ + +
Test heading
+ + + + +

+ View plans and pricing +

+

+ And another link +

+
+
+ + + + + + + + +

+ View all Creative Cloud products +

+
+ +
+
+
+
+

+ + + + + + +

+

www.adobe.com

+
+
+
+
+`; diff --git a/test/blocks/global-navigation/test-utilities.js b/test/blocks/global-navigation/test-utilities.js index aeb60465e1..a32b195f48 100644 --- a/test/blocks/global-navigation/test-utilities.js +++ b/test/blocks/global-navigation/test-utilities.js @@ -7,6 +7,7 @@ import { setConfig, loadStyle } from '../../../libs/utils/utils.js'; import defaultPlaceholders from './mocks/placeholders.js'; import defaultProfile from './mocks/profile.js'; import largeMenuMock from './mocks/large-menu.plain.js'; +import mockMegaMenu from './mocks/mock-megamenu.plain.js'; import largeMenuActiveMock from './mocks/large-menu-active.plain.js'; import largeMenuWideColumnMock from './mocks/large-menu-wide-column.plain.js'; import largeMenuCrossCloud from './mocks/large-menu-cross-cloud.plain.js'; @@ -81,6 +82,13 @@ export const analyticsTestData = { export const unavVersion = '1.3'; +export const addMetaDataV2 = (value) => { + const metaTag = document.createElement('meta'); + metaTag.name = 'mobile-gnav-v2'; + metaTag.content = value; + return metaTag; +}; + export const unavLocalesTestData = Object.entries(LANGMAP).reduce((acc, curr) => { const result = []; const [locale, prefixes] = curr; @@ -165,11 +173,9 @@ export const createFullGlobalNavigation = async ({ hasPromo, hasBreadcrumbs = true, unavContent = null, + imsInitialized = false, } = {}) => { - const clock = sinon.useFakeTimers({ - // Intercept setTimeout and call the function immediately - toFake: ['setTimeout'], - }); + const clock = sinon.useFakeTimers({ shouldAdvanceTime: true }); setConfig({ ...config, ...customConfig }); await setViewport(viewports[viewport]); window.lana = { log: stub() }; @@ -186,9 +192,11 @@ export const createFullGlobalNavigation = async ({ if (url.includes('correct-promo-fragment')) { return mockRes({ payload: correctPromoFragmentMock }); } if (url.includes('wrong-promo-fragment')) { return mockRes({ payload: '
Non-promo content
' }); } if (url.includes('UniversalNav')) { return mockRes({ payload: {} }); } + if (url.includes('mock-megamenu')) { return mockRes({ payload: mockMegaMenu }); } return null; }); window.adobeIMS = { + initialized: imsInitialized, isSignedInUser: stub().returns(signedIn), getAccessToken: stub().returns('mock-access-token'), getProfile: stub().returns( @@ -209,19 +217,23 @@ export const createFullGlobalNavigation = async ({ ${breadcrumbsEl} `); + if (hasPromo) { + document.body.prepend(toFragment`
`); + } + await Promise.all([ loadStyles('../../../../libs/styles/styles.css'), loadStyles( '../../../../libs/blocks/global-navigation/global-navigation.css', ), ]); - const instancePromise = initGnav(document.body.querySelector('header')); - await clock.runToLastAsync(); + clock.tick(1000); const instance = await instancePromise; const imsPromise = instance.imsReady(); await clock.runToLastAsync(); + clock.tick(1000); // We restore the clock here, because waitForElement uses setTimeout clock.restore(); diff --git a/test/blocks/global-navigation/utilities/utilities.test.js b/test/blocks/global-navigation/utilities/utilities.test.js index 895a2ea6e2..b3972be8ac 100644 --- a/test/blocks/global-navigation/utilities/utilities.test.js +++ b/test/blocks/global-navigation/utilities/utilities.test.js @@ -13,6 +13,8 @@ import { trigger, getExperienceName, logErrorFor, + takeWhile, + dropWhile, } from '../../../../libs/blocks/global-navigation/utilities/utilities.js'; import { setConfig, getConfig } from '../../../../libs/utils/utils.js'; import { createFullGlobalNavigation, config } from '../test-utilities.js'; @@ -388,4 +390,91 @@ describe('global navigation utilities', () => { window.lana.log = originalLanaLog; }); }); + + describe('takeWhile functionality', () => { + it('should take elements from the array while the predicate returns true', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub(); + predicate.withArgs(1).returns(true); + predicate.withArgs(2).returns(true); + predicate.withArgs(3).returns(false); + + const result = takeWhile(array, predicate); + + expect(result).to.deep.equal([1, 2]); + expect(predicate.callCount).to.equal(3); + expect(predicate.firstCall.args[0]).to.equal(1); + expect(predicate.secondCall.args[0]).to.equal(2); + expect(predicate.thirdCall.args[0]).to.equal(3); + }); + + it('should return an empty array if the predicate returns false for the first element', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub(); + predicate.withArgs(1).returns(false); + + const result = takeWhile(array, predicate); + + expect(result).to.deep.equal([]); + expect(predicate.callCount).to.equal(1); + expect(predicate.firstCall.args[0]).to.equal(1); + }); + + it('should return the entire array if the predicate always returns true', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub().returns(true); + + const result = takeWhile(array, predicate); + + expect(result).to.deep.equal(array); + expect(predicate.callCount).to.equal(array.length); + array.forEach((value, index) => { + expect(predicate.getCall(index).args[0]).to.equal(value); + }); + }); + }); + + describe('dropWhile functionality', () => { + it('should drop elements from the array while the predicate returns true', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub(); + predicate.withArgs(1).returns(true); + predicate.withArgs(2).returns(true); + predicate.withArgs(3).returns(false); + const result = dropWhile(array, predicate); + expect(result).to.deep.equal([3, 4, 5]); + expect(predicate.callCount).to.equal(3); + expect(predicate.firstCall.args[0]).to.equal(1); + expect(predicate.secondCall.args[0]).to.equal(2); + expect(predicate.thirdCall.args[0]).to.equal(3); + }); + + it('should return an empty array if the predicate returns true for all elements', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub().returns(true); + const result = dropWhile(array, predicate); + expect(result).to.deep.equal([]); + expect(predicate.callCount).to.equal(array.length); + array.forEach((value, index) => { + expect(predicate.getCall(index).args[0]).to.equal(value); + }); + }); + + it('should return the original array if the predicate returns false for the first element', () => { + const array = [1, 2, 3, 4, 5]; + const predicate = sinon.stub().returns(false); + const result = dropWhile(array, predicate); + expect(result).to.deep.equal(array); + expect(predicate.callCount).to.equal(1); + expect(predicate.firstCall.args[0]).to.equal(1); + }); + + it('should handle an empty array gracefully', () => { + const array = []; + const predicate = sinon.stub().returns(true); + const result = dropWhile(array, predicate); + expect(result).to.deep.equal([]); + expect(predicate.callCount).to.equal(0); + }); + }); }); diff --git a/test/navigation/bootstrapper.test.js b/test/navigation/bootstrapper.test.js index d524c82fda..1d1d00f5f6 100644 --- a/test/navigation/bootstrapper.test.js +++ b/test/navigation/bootstrapper.test.js @@ -92,6 +92,15 @@ describe('Bootstrapper', async () => { expect(el.classList.contains('feds--no-border')).to.be.true; }); + it('Renders the localnav', async () => { + blockConfig.header.isLocalNav = true; + blockConfig.header.mobileGnavV2 = true; + const { default: init } = await import('../../libs/blocks/global-navigation/global-navigation.js'); + await loadBlock(init, blockConfig.header); + const el = document.querySelector('header'); + expect(el.nextElementSibling.classList.contains('feds-localnav')).to.be.true; + }); + it('should call openMessagingWindow when click on jarvis enabled button', async () => { blockConfig.header.jarvis = { id: '1.1' }; stub(window.AdobeMessagingExperienceClient, 'isAdobeMessagingClientInitialized').returns(true); From 201299e32d498ebb5db5fc6a91a548c4a4694c02 Mon Sep 17 00:00:00 2001 From: Sean Choi Date: Wed, 15 Jan 2025 02:15:45 -0700 Subject: [PATCH 3/8] Adding body aria-live to merch card - product (#3447) to test body aria-live --- libs/deps/mas/mas.js | 2 +- libs/deps/mas/merch-card.js | 2 +- libs/features/mas/dist/mas.js | 2 +- libs/features/mas/src/variants/product.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/deps/mas/mas.js b/libs/deps/mas/mas.js index 9a5ae9c301..20683e1de9 100644 --- a/libs/deps/mas/mas.js +++ b/libs/deps/mas/mas.js @@ -1045,7 +1045,7 @@ merch-card[variant="plans"] [slot="quantity-select"] { } } `;var Ue=class extends I{constructor(t){super(t),this.postCardUpdateHook=this.postCardUpdateHook.bind(this)}getGlobalCSS(){return Uo}adjustProductBodySlots(){if(this.card.getBoundingClientRect().width===0)return;["heading-xs","body-xxs","body-xs","promo-text","callout-content","body-lower"].forEach(r=>this.updateCardElementMinHeight(this.card.shadowRoot.querySelector(`slot[name="${r}"]`),r))}renderLayout(){return x` ${this.badge} -
+
diff --git a/libs/deps/mas/merch-card.js b/libs/deps/mas/merch-card.js index 2fb96eb752..b77a2cd164 100644 --- a/libs/deps/mas/merch-card.js +++ b/libs/deps/mas/merch-card.js @@ -1070,7 +1070,7 @@ merch-card[variant="plans"] [slot="quantity-select"] { } } `;var b=class extends h{constructor(e){super(e),this.postCardUpdateHook=this.postCardUpdateHook.bind(this)}getGlobalCSS(){return St}adjustProductBodySlots(){if(this.card.getBoundingClientRect().width===0)return;["heading-xs","body-xxs","body-xs","promo-text","callout-content","body-lower"].forEach(t=>this.updateCardElementMinHeight(this.card.shadowRoot.querySelector(`slot[name="${t}"]`),t))}renderLayout(){return X` ${this.badge} -
+
diff --git a/libs/features/mas/dist/mas.js b/libs/features/mas/dist/mas.js index 9a5ae9c301..20683e1de9 100644 --- a/libs/features/mas/dist/mas.js +++ b/libs/features/mas/dist/mas.js @@ -1045,7 +1045,7 @@ merch-card[variant="plans"] [slot="quantity-select"] { } } `;var Ue=class extends I{constructor(t){super(t),this.postCardUpdateHook=this.postCardUpdateHook.bind(this)}getGlobalCSS(){return Uo}adjustProductBodySlots(){if(this.card.getBoundingClientRect().width===0)return;["heading-xs","body-xxs","body-xs","promo-text","callout-content","body-lower"].forEach(r=>this.updateCardElementMinHeight(this.card.shadowRoot.querySelector(`slot[name="${r}"]`),r))}renderLayout(){return x` ${this.badge} -
+
diff --git a/libs/features/mas/src/variants/product.js b/libs/features/mas/src/variants/product.js index 51d30a5a33..df970edd77 100644 --- a/libs/features/mas/src/variants/product.js +++ b/libs/features/mas/src/variants/product.js @@ -35,7 +35,7 @@ export class Product extends VariantLayout { renderLayout() { return html` ${this.badge} -
+
From a1c1cca984fa42cb6fa5422187b4f89684935593 Mon Sep 17 00:00:00 2001 From: Denys Fedotov Date: Wed, 15 Jan 2025 02:15:52 -0700 Subject: [PATCH 4/8] [MWPW-165303]: MMM - Implemented BE Pagination (#3462) * initial commit * initial commit. still need to fix tests * initial commit * added pagination summary + min 2 chars for seatch API trigger * added pagination summary + min 2 chars for seatch API trigger * added pageNum to shareUrl * changed pagination summary * changed pagination summary * css updates * css updates * fixed tests * fixed tests * fixed tests * fixed tests * fixed tests * remove unneeded data from DOM * center button text with new count * even spacing for form fields * change to filter * removed dead code = css fix for mobile view for share button * css fix for pagination on mobile * lint error fix --------- Co-authored-by: Denys Fedotov Co-authored-by: vgoodric --- libs/blocks/mmm/mmm.css | 100 +++++++++--- libs/blocks/mmm/mmm.js | 194 ++++++++++++++++------- libs/features/personalization/preview.js | 1 + test/blocks/mmm/mmm.test.js | 70 ++++---- test/blocks/mmm/mocks/body.html | 160 +++++++++---------- test/blocks/mmm/mocks/get-pages.json | 89 ++++++----- 6 files changed, 374 insertions(+), 240 deletions(-) diff --git a/libs/blocks/mmm/mmm.css b/libs/blocks/mmm/mmm.css index 044a9ff464..60dd361270 100644 --- a/libs/blocks/mmm/mmm.css +++ b/libs/blocks/mmm/mmm.css @@ -16,7 +16,7 @@ div.mmm { } dl.mmm { - margin: 0 auto; + margin: 32px auto; max-width: var(--grid-container-width); border-bottom: 1px solid var(--color-gray-500); } @@ -44,7 +44,7 @@ dl.mmm { font-size: var(--type-heading-xs-size); font-weight: 700; line-height: var(--type-heading-s-lh); - padding: var(--spacing-s) var(--spacing-m) var(--spacing-s) var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-m) var(--spacing-m) var(--spacing-xs); position: relative; text-align: start; width: 100%; @@ -261,23 +261,27 @@ dd .mep-popup-header .mep-close { } } -.mmm-container .filter-hide { +.mmm-hide { display: none; } - +.mmm-search-container { + margin-top: var(--spacing-m); +} .mmm-form-container { display: flex; justify-content: center; gap: var(--spacing-s); - margin: var(--spacing-m) auto 0; - width: 800px; + margin: 0 auto; + width: 700px; flex-flow: row nowrap; max-width: 80vw; + position: relative; } .mmm-form-container > div { flex: 1; } .mmm-form-container label { + margin-top: var(--spacing-xxs); margin-right: var(--spacing-xxs); display: block; font-size: var(--type-body-xs-size); @@ -311,9 +315,6 @@ dd .mep-popup-header .mep-close { #mmm-dropdown-container { flex: 0 0 50%; } - #mmm-search-query-container { - max-width: 700px; - } #mmm-shareButtonContainer { right: 100px; } @@ -324,13 +325,15 @@ dd .mep-popup-header .mep-close { .mmm-form-container > div.share-mmm { - margin-left: auto; - margin-right: auto; - text-align: center; - padding-top: 10px; - flex: 0; + right: -4rem; + top: 33px; + position: absolute; + height: 33px; + width: 33px; display: flex; - align-items: flex-start; + justify-content: center; + align-items: flex-end; + } .share-mmm svg.icon { @@ -346,16 +349,10 @@ dd .mep-popup-header .mep-close { filter: brightness(0.7); } -.share-mmm p.icon-container, -.share-mmm p.icon-container a { +.share-mmm p.icon-container { display: flex; - align-items: center; - padding-bottom: 0; justify-content: center; -} - -.share-mmm p { - padding-bottom: 8px; + align-items: center; } .share-mmm p.icon-container svg { @@ -377,7 +374,6 @@ dd .mep-popup-header .mep-close { border-radius: 4px; border: 2px solid transparent; outline: none; - margin-inline-start: 16px; } .share-mmm .copy-to-clipboard:focus-visible { @@ -459,6 +455,45 @@ dd .mep-popup-header .mep-close { margin: 0; } +#mmm-pagination { + display: flex; + flex-direction: row; + gap: 1rem; + font-size: 1.25rem; + justify-content: center; + padding: 2rem 8rem; + flex-wrap: wrap; + max-width: 1200px; + margin: 0 auto; +} + +#mmm-pagination a { + text-decoration: none; + cursor: pointer; + color: #4285f4; +} +#mmm-pagination .current-page { + font-weight: 600; + cursor: default; + pointer-events: none; + color: #000; +} +#mmm-pagination .disabled { + color: #ddd; + cursor: default; + pointer-events: none; +} +#mmm-pagination .arrow { + font-size: 1.5rem; + font-weight: 600; +} + +.mmm-pagination-summary { + display: flex; + justify-content: flex-end; + padding: 0 0 .5rem; +} + main > .section > .share-mmm.inline { margin: 0 auto; } @@ -468,7 +503,22 @@ main > .section > .inline-wrapper > .share-mmm.inline { margin-bottom: 0; } -main > .section.center .share-mmm.inline p.icon-container { +/* main > .section.center .share-mmm.inline p.icon-container { margin: 0 auto; justify-content: center; +} */ + +@media (max-width: 900px) { + .share-mmm { + left: 0; + right: 0 !important; + top: -1rem !important; + margin: auto; + } } + +@media (max-width: 700px) { + #mmm-pagination { + padding: 2rem 4rem; + } +} \ No newline at end of file diff --git a/libs/blocks/mmm/mmm.js b/libs/blocks/mmm/mmm.js index fc0b05fb38..9bbaebb56b 100644 --- a/libs/blocks/mmm/mmm.js +++ b/libs/blocks/mmm/mmm.js @@ -2,7 +2,10 @@ import { createTag, loadStyle } from '../../utils/utils.js'; import { fetchData, DATA_TYPE } from '../../features/personalization/personalization.js'; import { getMepPopup, API_URLS } from '../../features/personalization/preview.js'; -async function toggleDrawer(target, dd) { +const SEARCH_CRITERIA_CHANGE_EVENT = 'mmm-search-change'; +export const DEBOUNCE_TIME = 800; + +async function toggleDrawer(target, dd, pageId) { const el = target.closest('button'); const expanded = el.getAttribute('aria-expanded') === 'true'; if (expanded) { @@ -19,12 +22,12 @@ async function toggleDrawer(target, dd) { dd.removeAttribute('hidden'); const loading = dd.querySelector('.loading'); if (dd.classList.contains('placeholder-resolved') || !loading) return; - const { pageId } = dd.dataset; const pageData = await fetchData(`${API_URLS.pageDetails}${pageId}`, DATA_TYPE.JSON); loading.replaceWith(getMepPopup(pageData, true)); dd.classList.add('placeholder-resolved'); } } + function createButtonDetailsPair(mmmEl, page) { const { url, pageId, numOfActivities } = page; const triggerId = `mmm-trigger-${pageId}`; @@ -64,19 +67,16 @@ function createButtonDetailsPair(mmmEl, page) { `, ); const dd = createTag('dd', { id: panelId, hidden: true }, loading); - Object.keys(page).forEach((key) => { - const val = page[key] || 'us'; - dt.dataset[key] = val; - dd.dataset[key] = val; - }); button.addEventListener('click', (e) => { toggleDrawer(e.target, dd, pageId, 'mmm'); }); mmmEl.append(dt, dd); } -function filterPageList() { - const mmmEntries = document.querySelectorAll('div.mmm-container > dl > *'); + +function filterPageList(pageNum, event) { const shareUrl = new URL(`${window.location.origin}${window.location.pathname}`); const searchValues = {}; - document.querySelectorAll('.tabs input, .tabs select').forEach((field) => { + const activeSearchWithShortKeyword = event?.target?.value && event.target.value.length < 2; + + document.querySelector('.mmm-search-container').querySelectorAll('input, select').forEach((field) => { const id = field.getAttribute('id').split('-').pop(); const { value, tagName } = field; searchValues[id] = { @@ -85,29 +85,26 @@ function filterPageList() { }; if (value) shareUrl.searchParams.set(id, value); }); - const selectedRadio = document.querySelector('.tab-list-container button[aria-selected="true"]'); - const filterType = selectedRadio?.getAttribute('id') === 'tab-mmm-options-2' ? 'search' : 'filter'; - if (filterType === 'search') shareUrl.searchParams.set('tab', 'mmm-options-2'); + + // add page number to share url + shareUrl.searchParams.set('pageNum', pageNum || 1); + searchValues.pageNum = { value: pageNum || 1, tagName: 'A' }; + + // This event triggers an API call with beloww search criterias and a re-render + if (!activeSearchWithShortKeyword) { + document.dispatchEvent(new CustomEvent(SEARCH_CRITERIA_CHANGE_EVENT, { + detail: { + urls: searchValues.urls?.value, + geos: searchValues.geos?.value, + pages: searchValues.pages?.value, + pageNum: searchValues.pageNum?.value, + }, + })); + } + document.querySelectorAll('button.copy-to-clipboard').forEach((button) => { button.dataset.destination = shareUrl.href; }); - - mmmEntries.forEach((entry) => { - const data = entry.dataset; - entry.classList.remove('filter-hide'); - if (filterType === 'search') { - if (!data.url.includes(searchValues.query.value)) entry.classList.add('filter-hide'); - return; - } - Object.keys(searchValues).forEach((key) => { - const { value, tagName } = searchValues[key]; - if (tagName !== 'SELECT') return; - const inputVal = data[key]; - if (value && !value.split(',').some((val) => inputVal === val)) { - entry.classList.add('filter-hide'); - } - }); - }); } function parseData(el) { const data = {}; @@ -131,13 +128,12 @@ function parseData(el) { }); return data; } + function createShareButton() { const div = createTag( 'div', { class: 'share-mmm' }, ); - const p = createTag('p', { class: 'icon-container' }); - div.append(p); const buttonLabel = 'Copy link to these search settings'; const button = createTag( 'button', @@ -152,7 +148,8 @@ function createShareButton() { `, ); - p.append(button); + button.dataset.destination = document.location.href; // set original destination + div.append(button); button.addEventListener('click', (e) => { /* c8 ignore start */ e.preventDefault(); @@ -168,13 +165,14 @@ function createShareButton() { }); return div; } + function createDropdowns(data, sharedUrlSettings) { - const dropdownTab = document.querySelector('.section-metadata.dropdowns'); + const searchContainer = document.querySelector('.mmm-search-container'); const dropdownForm = createTag( 'div', { id: 'mmm-dropdown-container', class: 'mmm-form-container' }, ); - dropdownTab.parentNode.append(dropdownForm); + searchContainer.append(dropdownForm); const dropdownSubContainer = createTag('div', { id: 'mmm-dropdown-sub-container' }); dropdownForm.append(dropdownSubContainer); dropdownForm.append(createShareButton()); @@ -192,37 +190,108 @@ function createDropdowns(data, sharedUrlSettings) { const startingVal = sharedUrlSettings[key]; if (startingVal === option) optionEl.setAttribute('selected', 'selected'); }); - select.addEventListener('change', filterPageList); + select.addEventListener('change', () => filterPageList()); }); } + +function debounce(func) { + let timeout; + return (event) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(event), DEBOUNCE_TIME); + }; +} + function createSearchField(data, sharedUrlSettings) { - const searchTab = document.querySelector('.section-metadata.search'); + const searchContainer = document.querySelector('.mmm-search-container'); const searchForm = createTag( 'div', - { id: 'mmm-search-query-container', class: 'mmm-form-container' }, + { id: 'mmm-search-urls-container', class: 'mmm-form-container' }, `
- - + +
`, ); - searchForm.append(createShareButton()); - searchTab.parentNode.insertBefore(searchForm, searchTab); + searchContainer.append(searchForm); const searchField = searchForm.querySelector('input'); - if (sharedUrlSettings.query) searchField.value = sharedUrlSettings.query; - searchField.addEventListener('keyup', filterPageList); - searchField.addEventListener('change', filterPageList); + if (sharedUrlSettings.urls) searchField.value = sharedUrlSettings.urls; + + searchField.addEventListener('keyup', debounce((event) => filterPageList(null, event))); + searchField.addEventListener('change', debounce((event) => filterPageList(null, event))); } + async function createForm(el) { const data = parseData(el); const urlParams = new URLSearchParams(window.location.search); const sharedUrlSettings = Object.fromEntries(urlParams.entries()); - createSearchField(data, sharedUrlSettings); + const searchContainer = createTag('div', { class: 'mmm-search-container' }); + document.querySelector('.mmm-container').parentNode.prepend(searchContainer); createDropdowns(data, sharedUrlSettings); - document.querySelectorAll('.tab-list-container button').forEach((button) => { - button.addEventListener('click', filterPageList); + createSearchField(data, sharedUrlSettings); +} + +function createPaginationEl({ data, el }) { + const paginationEl = createTag('div', { id: 'mmm-pagination', 'data-current-page': data.pageNum }); + const totalPages = Math.ceil(data.totalRecords / data.perPage); + const noResult = !data.totalRecords; + const prev = data.pageNum - 1 || 1; + const next = data.pageNum < totalPages ? data.pageNum + 1 : data.pageNum; + + const prevEl = createTag('a', { + 'data-page-num': prev, + class: `arrow ${data.pageNum === 1 ? 'disabled' : ''}`, + }, '<'); + const nextEl = createTag('a', { + 'data-page-num': next, + class: `arrow ${data.pageNum === totalPages ? 'disabled' : ''}`, + }, '>'); + + const paginationSummary = createTag('div', { class: 'mmm-pagination-summary' }); + const range = `${data.pageNum * data.perPage - (data.perPage - 1)}-${data.pageNum * data.perPage < data.totalRecords ? data.pageNum * data.perPage : data.totalRecords}`; + paginationSummary.innerHTML = ` +
+ ${range} of ${data.totalRecords} +
+ `; + if (!noResult) { + paginationEl.append(prevEl); + for (let i = 1; i <= totalPages; i += 1) { + const pageLink = createTag('a', { + class: `${i === data.pageNum ? 'current-page' : ''}`, + 'data-page-num': i, + }, i); + paginationEl.append(pageLink); + } + paginationEl.append(nextEl); + document.querySelector('#mmm').prepend(paginationSummary); + } else { + paginationEl.innerHTML = '
No results.
'; + } + el.append(paginationEl); +} + +function getSearchParams(obj) { + let searchString = ''; + Object.keys(obj).forEach((key) => { + if (obj[key]) searchString += `&${key}=${obj[key]}`; + }); + searchString = `?${searchString.slice(1)}`; + return searchString; +} + +function handlePaginationClicks() { + const paginationEl = document.querySelector('#mmm-pagination'); + paginationEl?.querySelectorAll('a').forEach((item) => { + item?.addEventListener('click', () => { + item.parentNode.setAttribute('data-current-page', item.getAttribute('data-page-num')); + filterPageList(item.getAttribute('data-page-num')); + }); }); } -async function createPageList(el) { + +async function createPageList(el, search) { + const paginationEl = document.querySelector('.mmm-pagination'); + paginationEl?.classList.add('mmm-hide'); const mmmElContainer = createTag('div', { class: 'mmm-container max-width-12-desktop' }); const mmmEl = createTag('dl', { class: 'mmm foreground', @@ -230,16 +299,33 @@ async function createPageList(el) { role: 'presentation', }); mmmElContainer.append(mmmEl); - const pageList = await fetchData(API_URLS.pageList, DATA_TYPE.JSON); - pageList.map((page) => createButtonDetailsPair(mmmEl, page)); + const url = `${API_URLS.pageList}${search ? getSearchParams(search) : window.location.search || '?pageNum=1'}`; + const response = await fetchData( + url, + DATA_TYPE.JSON, + ); + response.result.map((page) => createButtonDetailsPair(mmmEl, page)); const section = createTag('div', { id: 'mep-section', class: 'section' }); const main = document.querySelector('main'); el.replaceWith(mmmElContainer); main.append(section); - filterPageList(); - loadStyle('/libs/features/personalization/preview.css'); + createPaginationEl({ + el: mmmElContainer, + data: response, + }); + paginationEl?.classList.remove('mmm-hide'); + handlePaginationClicks(); } + +function subscribeToSearchCriteriaChanges() { + document.addEventListener(SEARCH_CRITERIA_CHANGE_EVENT, (el) => { + createPageList(document.querySelector('.mmm').parentNode, el.detail); + }); +} + export default async function init(el) { + await createPageList(el); createForm(el); - createPageList(el); + subscribeToSearchCriteriaChanges(); + loadStyle('/libs/features/personalization/preview.css'); } diff --git a/libs/features/personalization/preview.js b/libs/features/personalization/preview.js index 3ee4f925cb..ba61b75418 100644 --- a/libs/features/personalization/preview.js +++ b/libs/features/personalization/preview.js @@ -2,6 +2,7 @@ import { createTag, getConfig, getMetadata, loadStyle } from '../../utils/utils. import { US_GEO, getFileName, normalizePath } from './personalization.js'; const API_DOMAIN = 'https://jvdtssh5lkvwwi4y3kbletjmvu0qctxj.lambda-url.us-west-2.on.aws'; + export const API_URLS = { pageList: `${API_DOMAIN}/get-pages`, pageDetails: `${API_DOMAIN}/get-page?id=`, diff --git a/test/blocks/mmm/mmm.test.js b/test/blocks/mmm/mmm.test.js index 1912e8b5f7..f90de99e75 100644 --- a/test/blocks/mmm/mmm.test.js +++ b/test/blocks/mmm/mmm.test.js @@ -1,6 +1,11 @@ import { readFile } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; import { stub } from 'sinon'; +import { DEBOUNCE_TIME } from '../../../libs/blocks/mmm/mmm.js'; + +const delay = (ms = 0) => new Promise((resolve) => { + setTimeout(() => resolve(), ms); +}); document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { @@ -143,54 +148,43 @@ describe('MMM', () => { }); it('Test filters', async () => { - const showSelector = 'dt:not(.filter-hide),dd:not(.filter-hide)'; const copyButton = document.querySelector('.copy-to-clipboard'); expect(copyButton).to.exist; const event = new Event('change'); - expect(document.querySelectorAll(showSelector).length).to.equal(10); - expect(copyButton.dataset.destination).to.not.include('geo'); - expect(copyButton.dataset.destination).to.not.include('page'); - expect(copyButton.dataset.destination).to.not.include('tab'); - expect(copyButton.dataset.destination).to.not.include('query'); - const geoDropdown = document.querySelector('#mmm-dropdown-geo'); + expect(copyButton.dataset.destination).to.not.include('geos'); + expect(copyButton.dataset.destination).to.not.include('pages'); + expect(copyButton.dataset.destination).to.not.include('urls'); + + const geoDropdown = document.querySelector('#mmm-dropdown-geos'); + expect(geoDropdown).to.exist; geoDropdown.options[1].selected = true; geoDropdown.dispatchEvent(event); - expect(document.querySelectorAll(showSelector).length).to.equal(8); - expect(copyButton.dataset.destination).to.include('geo'); - expect(copyButton.dataset.destination).to.not.include('page'); - expect(copyButton.dataset.destination).to.not.include('tab'); - expect(copyButton.dataset.destination).to.not.include('query'); - const pageDropdown = document.querySelector('#mmm-dropdown-page'); + expect(copyButton.dataset.destination).to.include('geos'); + expect(copyButton.dataset.destination).to.not.include('pages'); + expect(copyButton.dataset.destination).to.not.include('urls'); + const pageDropdown = document.querySelector('#mmm-dropdown-pages'); + expect(pageDropdown).to.exist; + pageDropdown.options[2].selected = true; pageDropdown.dispatchEvent(event); - expect(document.querySelectorAll(showSelector).length).to.equal(4); - expect(copyButton.dataset.destination).to.include('geo'); - expect(copyButton.dataset.destination).to.include('page'); - expect(copyButton.dataset.destination).to.not.include('tab'); - expect(copyButton.dataset.destination).to.not.include('query'); + expect(copyButton.dataset.destination).to.include('geos'); + expect(copyButton.dataset.destination).to.include('pages'); + expect(copyButton.dataset.destination).to.not.include('urls'); + geoDropdown.options[0].selected = true; geoDropdown.dispatchEvent(event); - expect(document.querySelectorAll(showSelector).length).to.equal(6); - expect(copyButton.dataset.destination).to.not.include('geo'); - expect(copyButton.dataset.destination).to.include('page'); - expect(copyButton.dataset.destination).to.not.include('tab'); - expect(copyButton.dataset.destination).to.not.include('query'); - const filterTabs = document.querySelectorAll('.tab-list-container button'); - filterTabs[0].setAttribute('aria-selected', 'false'); - filterTabs[1].setAttribute('aria-selected', 'true'); - filterTabs[1].click(); - expect(document.querySelectorAll(showSelector).length).to.equal(10); - expect(copyButton.dataset.destination).to.not.include('geo'); - expect(copyButton.dataset.destination).to.include('page'); - expect(copyButton.dataset.destination).to.include('tab'); - expect(copyButton.dataset.destination).to.not.include('query'); - const mmmSearchQuery = document.querySelector('#mmm-search-query'); + expect(copyButton.dataset.destination).to.not.include('geos'); + expect(copyButton.dataset.destination).to.include('pages'); + expect(copyButton.dataset.destination).to.not.include('urls'); + + const mmmSearchQuery = document.querySelector('#mmm-search-urls'); + expect(mmmSearchQuery).to.exist; mmmSearchQuery.value = 'pricing'; mmmSearchQuery.dispatchEvent(event); - expect(document.querySelectorAll(showSelector).length).to.equal(2); - expect(copyButton.dataset.destination).to.not.include('geo'); - expect(copyButton.dataset.destination).to.include('page'); - expect(copyButton.dataset.destination).to.include('tab'); - expect(copyButton.dataset.destination).to.include('query'); + // await debounce time + await delay(DEBOUNCE_TIME + 1); + expect(copyButton.dataset.destination).to.not.include('geos'); + expect(copyButton.dataset.destination).to.include('pages'); + expect(copyButton.dataset.destination).to.include('urls'); }); }); diff --git a/test/blocks/mmm/mocks/body.html b/test/blocks/mmm/mocks/body.html index e8bd9bb266..47e766446c 100644 --- a/test/blocks/mmm/mocks/body.html +++ b/test/blocks/mmm/mocks/body.html @@ -42,89 +42,87 @@
-
-
-
-
Menu: geo
-
Top Regions/Geos
-
-
-
us,ca,ca_fr
-
NA region: US, CA, CA_FR
-
-
-
au,nz,kr
-
APAC region: AU, NZ, KR
-
-
-
mx,br
-
LATAM region: MX, BR
-
-
-
jp
-
Japan
-
-
-
us
-
US: United States
-
-
-
ca
-
CA-EN: Canada - English
-
-
-
ca_fr
-
CA-FR: Canada - Français
-
-
-
au
-
AU: Australia
-
-
-
nz
-
NZ: New Zealand
-
-
-
kr
-
KR: Korea
-
-
-
mx
-
MX: México
-
-
-
br
-
BR: Brasil
-
-
-
Menu: page
-
Top Pages
-
-
-
-

/,

-

/creativecloud.html,

-

/products/photoshop.html,

-

/products/illustrator.html

-
-
Only top pages
-
-
-
/
-
Homepage
-
-
-
/creativecloud.html
-
CCOV
-
-
-
/products/photoshop.html
-
Photoshop product page
-
+
+
+
Menu: geos
+
Top Regions/Geos
+
+
+
us,ca,ca_fr
+
NA region: US, CA, CA_FR
+
+
+
au,nz,kr
+
APAC region: AU, NZ, KR
+
+
+
mx,br
+
LATAM region: MX, BR
+
+
+
jp
+
Japan
+
+
+
us
+
US: United States
+
+
+
ca
+
CA-EN: Canada - English
+
+
+
ca_fr
+
CA-FR: Canada - Français
+
+
+
au
+
AU: Australia
+
+
+
nz
+
NZ: New Zealand
+
+
+
kr
+
KR: Korea
+
+
+
mx
+
MX: México
+
+
+
br
+
BR: Brasil
+
+
+
Menu: pages
+
Top Pages
+
+
-
/products/illustrator.html
-
Illustrator product page
+

/,

+

/creativecloud.html,

+

/products/photoshop.html,

+

/products/illustrator.html

+
Only top pages
+
+
+
/
+
Homepage
+
+
+
/creativecloud.html
+
CCOV
+
+
+
/products/photoshop.html
+
Photoshop product page
+
+
+
/products/illustrator.html
+
Illustrator product page
diff --git a/test/blocks/mmm/mocks/get-pages.json b/test/blocks/mmm/mocks/get-pages.json index f67ae0a11d..bbb40e0b98 100644 --- a/test/blocks/mmm/mocks/get-pages.json +++ b/test/blocks/mmm/mocks/get-pages.json @@ -1,42 +1,47 @@ -[ - { - "geo": "", - "pageId": 4, - "page": "/", - "locale": "en-US", - "numOfActivities": 1, - "url": "https://www.adobe.com/" - }, - { - "geo": "", - "pageId": 7, - "page": "/acrobat/pricing.html", - "locale": "en-US", - "numOfActivities": 1, - "url": "https://www.adobe.com/acrobat/pricing.html" - }, - { - "geo": "", - "pageId": 3, - "page": "/creativecloud.html", - "locale": "en-US", - "numOfActivities": 1, - "url": "https://www.adobe.com/creativecloud.html" - }, - { - "geo": "ca_fr", - "pageId": 16, - "page": "/", - "locale": "fr-CA", - "numOfActivities": 1, - "url": "https://www.adobe.com/ca_fr/" - }, - { - "geo": "de", - "pageId": 61, - "page": "/", - "locale": "de-DE", - "numOfActivities": 1, - "url": "https://www.adobe.com/de/" - } -] +{ + "result": [ + { + "geo": "", + "pageId": 4, + "page": "/", + "locale": "en-US", + "numOfActivities": 1, + "url": "https://www.adobe.com/" + }, + { + "geo": "", + "pageId": 7, + "page": "/acrobat/pricing.html", + "locale": "en-US", + "numOfActivities": 1, + "url": "https://www.adobe.com/acrobat/pricing.html" + }, + { + "geo": "", + "pageId": 3, + "page": "/creativecloud.html", + "locale": "en-US", + "numOfActivities": 1, + "url": "https://www.adobe.com/creativecloud.html" + }, + { + "geo": "ca_fr", + "pageId": 16, + "page": "/", + "locale": "fr-CA", + "numOfActivities": 1, + "url": "https://www.adobe.com/ca_fr/" + }, + { + "geo": "de", + "pageId": 61, + "page": "/", + "locale": "de-DE", + "numOfActivities": 1, + "url": "https://www.adobe.com/de/" + } + ], + "totalRecords": 20, + "pageNum": 1, + "perPage": 5 +} From 62d554bc4ead0dc93740b0ec557e1785e051f489 Mon Sep 17 00:00:00 2001 From: Milica Micic Date: Wed, 15 Jan 2025 13:57:09 +0100 Subject: [PATCH 5/8] mas: update failing Nala test on stage (#3490) * [Nala] Fix failing promo code in Nala tests * mas: update Nala test --- nala/features/mas/ccd/masccd.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nala/features/mas/ccd/masccd.spec.js b/nala/features/mas/ccd/masccd.spec.js index ea012fbb6d..4797c68d83 100644 --- a/nala/features/mas/ccd/masccd.spec.js +++ b/nala/features/mas/ccd/masccd.spec.js @@ -181,7 +181,7 @@ module.exports = { data: { id: '58c7906f-70a6-4e2b-bc29-257ff2ade513', description: 'Creative Cloud Photography plan. Starting at', - price: 'US$9.99/mo', + price: 'US$11.99/mo', cta: 'Buy now', offerid: '7D31EB7B815967837F7882380437117D', background: 'media_10bef5ec21c22fd7fe201cb02735082df13bf4960.jpeg', From 62d03e5676daee16b487567bd5e09f359db02255 Mon Sep 17 00:00:00 2001 From: Swati Mukherjee Date: Wed, 15 Jan 2025 19:47:31 +0530 Subject: [PATCH 6/8] [ENB-7645] Add page-view send event, fix window.satellite empty issue (#3489) Add pageview send event, fix window.stallite empty issue --- libs/features/personalization/personalization.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 91e67a8c64..225b09d0c7 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1126,7 +1126,13 @@ async function updateManifestsAndPropositions({ config, targetManifests, targetP manifest.source = ['target']; }); config.mep.targetManifests = targetManifests; - if (targetPropositions?.length && window._satellite) { + if (enablePersonalizationV2()) { + window.addEventListener('alloy_sendEvent', () => { + if (targetPropositions?.length && window._satellite) { + window._satellite.track('propositionDisplay', targetPropositions); + } + }, { once: true }); + } else if (targetPropositions?.length && window._satellite) { window._satellite.track('propositionDisplay', targetPropositions); } if (config.mep.targetEnabled === 'postlcp') { From 6718d230085d08935b73fb4c43499cb461f52b60 Mon Sep 17 00:00:00 2001 From: Bandana Laishram Date: Wed, 15 Jan 2025 21:02:45 +0530 Subject: [PATCH 7/8] Taking localnav height only when it is visible (#3488) * Taking localnav height only when it is visible * Fixing top for gnav since promo is not inside gnav anymore * Code fix * Feds promo wrapper rule should be ins tyles.css to avoid CLS * Check for newNav search param when reserving space for the localnav --------- Co-authored-by: Raghav Sharma --- .../blocks/global-navigation/global-navigation.css | 7 ------- libs/blocks/section-metadata/sticky-section.js | 2 +- libs/navigation/navigation.css | 7 +++++++ libs/styles/styles.css | 14 ++++++++------ libs/utils/utils.js | 3 ++- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/libs/blocks/global-navigation/global-navigation.css b/libs/blocks/global-navigation/global-navigation.css index 21dab7dff8..94bea4769b 100644 --- a/libs/blocks/global-navigation/global-navigation.css +++ b/libs/blocks/global-navigation/global-navigation.css @@ -1246,13 +1246,6 @@ header.new-nav .feds-breadcrumbs li:first-child:not(:nth-last-child(-n+3)):after display: block; } -.feds-promo-aside-wrapper { - position: sticky; - top: 0; - z-index: 11; - height: var(--global-height-navPromo); -} - @keyframes slideright { from { translate: 0 0; diff --git a/libs/blocks/section-metadata/sticky-section.js b/libs/blocks/section-metadata/sticky-section.js index 4c3fba4320..0232426e26 100644 --- a/libs/blocks/section-metadata/sticky-section.js +++ b/libs/blocks/section-metadata/sticky-section.js @@ -5,7 +5,7 @@ function handleTopHeight(section) { let topHeight = document.querySelector('header')?.offsetHeight ?? 0; const localNav = document.querySelector('.feds-localnav'); const fedsPromo = document.querySelector('.feds-promo-wrapper'); - if (localNav) { + if (localNav && localNav.offsetHeight > 0) { topHeight = localNav.offsetHeight; } if (fedsPromo) { diff --git a/libs/navigation/navigation.css b/libs/navigation/navigation.css index 215354b5e4..93c6e66815 100644 --- a/libs/navigation/navigation.css +++ b/libs/navigation/navigation.css @@ -52,6 +52,13 @@ header.global-navigation + .feds-localnav { height: var(--feds-localnav-height); } +.feds-promo-aside-wrapper { + position: sticky; + top: 0; + z-index: 11; + height: var(--global-height-navpromo); +} + @media (min-width: 900px) { .feds-promo-link { color: var(--navigation-link-color); diff --git a/libs/styles/styles.css b/libs/styles/styles.css index 50f47b1bec..ad2aff48c9 100644 --- a/libs/styles/styles.css +++ b/libs/styles/styles.css @@ -725,13 +725,11 @@ header.global-navigation + .feds-localnav { height: var(--feds-localnav-height); } -header.global-navigation.has-promo { - height: var(--global-height-navPromo); - top: var(--global-height-navPromo); -} - -header.global-navigation.has-promo:has(+ .feds-localnav) { +.feds-promo-aside-wrapper { + position: sticky; top: 0; + z-index: 11; + height: var(--global-height-navPromo); } .feds-sr-only { @@ -751,6 +749,10 @@ header.global-navigation.has-promo:has(+ .feds-localnav) { padding-bottom: var(--global-height-breadcrumbs); } + header.global-navigation.has-promo { + top: var(--global-height-navPromo); + } + header.global-navigation + .feds-localnav { display: none; } diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 10f91e0d30..2f6d849a8e 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -805,7 +805,8 @@ async function decorateHeader() { && window.sessionStorage.getItem('gnavSource') !== null; if (!dynamicNavActive && (baseBreadcrumbs || breadcrumbs || autoBreadcrumbs)) header.classList.add('has-breadcrumbs'); const gnavSource = await getGnavSource(); - if (gnavSource.split('/').pop().startsWith('localnav-') && getMetadata('mobile-gnav-v2') !== 'off') { + const newNavEnabled = new URLSearchParams(window.location.search).get('newNav') || getMetadata('mobile-gnav-v2') !== 'off'; + if (gnavSource.split('/').pop().startsWith('localnav-') && newNavEnabled) { // Preserving space to avoid CLS issue const localNavWrapper = createTag('div', { class: 'feds-localnav' }); header.after(localNavWrapper); From b1351a6df6de10659dad4dba7bb4a9ade8e5b838 Mon Sep 17 00:00:00 2001 From: Sino Kholkhojaev <132879006+skholkhojaev@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:16:59 +0100 Subject: [PATCH 8/8] fixed the flaky test for logWebVitals.test.js & logWebVitalsUtils.test.js (#3469) * fixed the flaky test * move performance mock to beforeEach/afterEach for isolation, simplify lcpElType check * removed unused code * fixed the same issue in logWebVitals.test.js and reverted unnecessary changes * Updated README.md --- README.md | 2 +- test/utils/logWebVitals.test.js | 7 +++++-- test/utils/logWebVitalsUtils.test.js | 9 ++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dcdec1b1fa..798920dd41 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Milo is a shared set of features and services to power Franklin-based websites o ### Detailed 1. Fork this repo. -1. Install the [AEM Code Sync](https://github.com/apps/aem-code-sync) on your forked repo. +1. Install the [AEM Code Sync](https://github.com/apps/aem-code-sync) on your forked repo. Make sure that a `main` branch exists in your forked repository. 1. Clone your forked repo down to your computer. 1. Install the [AEM CLI](https://github.com/adobe/helix-cli) using your terminal: `sudo npm install -g @adobe/aem-cli` 1. In a terminal, run `aem up` your repo's folder on your computer. It will open a browser. diff --git a/test/utils/logWebVitals.test.js b/test/utils/logWebVitals.test.js index 199e7ca1dd..b045a7284e 100644 --- a/test/utils/logWebVitals.test.js +++ b/test/utils/logWebVitals.test.js @@ -23,8 +23,11 @@ describe('Log Web Vitals', () => { const downlink = parseFloat(vitals.downlink); expect(downlink).to.be.within(0, 10); expect(parseInt(vitals.lcp, 10)).to.be.greaterThan(1); - expect(vitals.lcpEl).to.be.equal('/test/utils/mocks/media_.png'); - expect(vitals.lcpElType).to.be.equal('img'); + expect(vitals).to.have.property('lcpEl'); + expect(vitals.lcpEl).to.be.a('string').that.is.not.empty; + expect(vitals).to.have.property('lcpElType'); + expect(vitals.lcpElType).to.be.a('string').that.is.not.empty; + expect(vitals.lcpSectionOne).to.equal('true'); expect(vitals.loggedIn).to.equal('false'); expect(vitals.manifest3path).to.equal('/cc-shared/fragments/promos/2024/americas/cci-all-apps-q3/cci-all-apps-q3.json'); expect(vitals.manifest3selected).to.equal('all'); diff --git a/test/utils/logWebVitalsUtils.test.js b/test/utils/logWebVitalsUtils.test.js index 7bbee89984..c98c6a99d4 100644 --- a/test/utils/logWebVitalsUtils.test.js +++ b/test/utils/logWebVitalsUtils.test.js @@ -41,9 +41,12 @@ describe('Log Web Vitals Utils', () => { const downlink = parseFloat(vitals.downlink); expect(downlink).to.be.within(0, 10); expect(parseInt(vitals.lcp, 10)).to.be.greaterThan(1); - expect(vitals.lcpEl).to.be.equal('/test/utils/mocks/media_.png'); - expect(vitals.lcpElType).to.be.equal('img'); - expect(vitals.lcpSectionOne).to.be.equal('true'); + + expect(vitals).to.have.property('lcpEl'); + expect(vitals.lcpEl).to.be.a('string').that.is.not.empty; + expect(vitals).to.have.property('lcpElType'); + expect(vitals.lcpElType).to.be.a('string').that.is.not.empty; + expect(vitals.lcpSectionOne).to.equal('true'); expect(vitals.loggedIn).to.equal('false'); expect(vitals.os).to.be.oneOf(['mac', 'win', 'android', 'linux', '']);