From 3feae2e01dc73078ec4a9e87c633ea4ba622052a Mon Sep 17 00:00:00 2001 From: david ornelas Date: Tue, 31 Mar 2026 16:57:29 -0700 Subject: [PATCH 1/3] fix(unity-bootstrap-theme): fix anchor menu js --- .../src/js/anchor-menu.js | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/unity-bootstrap-theme/src/js/anchor-menu.js b/packages/unity-bootstrap-theme/src/js/anchor-menu.js index 8afdddc12e..5e59bbaf13 100644 --- a/packages/unity-bootstrap-theme/src/js/anchor-menu.js +++ b/packages/unity-bootstrap-theme/src/js/anchor-menu.js @@ -15,9 +15,9 @@ function initAnchorMenu() { const globalHeader = document.getElementById(globalHeaderId); const navbar = document.getElementById("uds-anchor-menu"); if (!navbar || !globalHeader) { - console.warn( - "Anchor menu initialization failed: required elements not found" - ); + return; + } + if (Array.from(navbar.classList).some(cls => cls.startsWith("sc-"))) { return; } @@ -203,10 +203,26 @@ function initAnchorMenu() { return; } - // Get current viewport height and calculate the 1/4 position so that the - // top of section is visible when you click on the anchor. + // For the first anchor item, skip scrolling if the section top is + // already clearly visible in the viewport (above the midpoint). + const isFirstAnchor = anchor === anchors[0]; + if (isFirstAnchor) { + const headerBottom = globalHeader.getBoundingClientRect().bottom; + const navbarHeight = navbar.offsetHeight; + const topOffset = headerBottom + navbarHeight; + const targetTop = anchorTarget.getBoundingClientRect().top; + const viewportMid = window.innerHeight / 2; + console.log("viewportmid", viewportMid, "targetTop", targetTop); + + if (targetTop >= topOffset && targetTop <= viewportMid) { + history.replaceState(null, "", anchor.getAttribute("href")); + moveFocusToTarget(anchorTarget); + return; + } + } + const viewportHeight = window.innerHeight; - const targetQuarterPosition = Math.round(viewportHeight * 0.25); + const targetQuarterPosition = Math.round(viewportHeight * 0.35); // 35% was determined to be a good position for the section top after testing different offsets, including centering the section in the viewport. Can work in wordpress or any other platform where there are admin toolbars const targetAbsoluteTop = anchorTarget.getBoundingClientRect().top + window.scrollY; @@ -226,8 +242,34 @@ function initAnchorMenu() { } e.target.classList.add("active"); + + const targetHash = anchor.getAttribute("href"); + if (targetHash) { + history.replaceState(null, "", targetHash); + } + + // Move focus to the target section so keyboard users can Tab + // into its content (WCAG 2.4.3 Focus Order). + moveFocusToTarget(anchorTarget); }); } + + /** + * Moves keyboard focus to the anchor target section. + * Adds tabindex="-1" if needed so the element is programmatically + * focusable without entering the natural tab order. + * + * fixes + * WCAG 2.4.3 Focus Order (Level A) — focus sequence doesn't match visual/logical order + * WCAG 2.1.1 Keyboard (Level A) — functionality isn't fully keyboard operable + */ + function moveFocusToTarget(target) { + if (!target.hasAttribute("tabindex")) { + target.setAttribute("tabindex", "-1"); + target.style.outline = "none"; + } + target.focus({ preventScroll: true }); + } } EventHandler.on(window, "load.uds.anchor-menu", initAnchorMenu); From 68fca1eb22954271d28d94600a980fdef9911b45 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Fri, 3 Apr 2026 12:14:58 -0700 Subject: [PATCH 2/3] fix(unity-react-core): fix anchor menu logic --- .../src/components/AnchorMenu/AnchorMenu.jsx | 43 +++++++++++++------ .../AnchorMenu/AnchorMenu.stories.jsx | 3 +- .../AnchorMenu/AnchorMenu.styles.js | 9 +--- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx index 8c4ff25fc0..b5c4bfc877 100644 --- a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx +++ b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx @@ -56,11 +56,21 @@ export const AnchorMenu = ({ showMenu: false, sticky: false, }); - const headerHeight = isSmallDevice ? 110 : 142; + + const getPageHeader = () => + document.getElementById("asu-header") || + document.getElementById("headerContainer") || + document.getElementById("asuHeader"); + + const getHeaderBottomOffset = () => { + const pageHeader = getPageHeader(); + return Math.max(pageHeader?.getBoundingClientRect().bottom || 0, 0); + }; const handleWindowScroll = () => { const newState = {}; const curPos = window.scrollY; + const headerBottomOffset = getHeaderBottomOffset(); // Select first next sibling element of the anchor menu const firstElement = document .getElementById(firstElementId) @@ -77,7 +87,7 @@ export const AnchorMenu = ({ // Change active containers on scroll const subsHeight = state.hasHeader - ? headerHeight + anchorMenuHeight + ? headerBottomOffset + anchorMenuHeight : anchorMenuHeight; items?.forEach(({ targetIdName }) => { const container = document.getElementById(targetIdName); @@ -105,10 +115,7 @@ export const AnchorMenu = ({ // Is ASU Header on the document const isHeader = () => { - const pageHeader = - document.getElementById("asu-header") || - document.getElementById("headerContainer") || - document.getElementById("asuHeader"); + const pageHeader = getPageHeader(); return !!pageHeader; }; @@ -162,9 +169,10 @@ export const AnchorMenu = ({ }, [state.hasHeader]); const handleClickLink = container => { + const headerBottomOffset = getHeaderBottomOffset(); // Set scroll position considering if ASU Header is setted or not const curScroll = - window.scrollY - (state.hasHeader ? headerHeight + 100 : 100); + window.scrollY - (state.hasHeader ? headerBottomOffset + 100 : 100); const anchorMenuHeight = isSmallDevice ? 410 : 90; // Set where to scroll to let scrollTo = @@ -187,11 +195,19 @@ export const AnchorMenu = ({ })); }; + const headerBottomOffset = state.hasHeader ? getHeaderBottomOffset() : 0; + const WrapperComponent = isBootstrap ? "div" : AnchorMenuWrapper; + const wrapperProps = isBootstrap + ? {} + : { + // @ts-ignore + requiresAltMenuSpacing: state.hasAltMenuSpacing, + }; + return ( items?.length > 0 && ( -
{isSmallDevice ? ( @@ -265,7 +284,7 @@ export const AnchorMenu = ({
-
+ ) ); }; diff --git a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx index ae14629abb..8766a47771 100644 --- a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx +++ b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx @@ -95,8 +95,7 @@ const Template = args => { return ( <> - {/* Bootstrap version of anchor menu depends on the header component */} - {isBootstrap &&
} +
diff --git a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.styles.js b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.styles.js index bb019213f4..64cd9a6e98 100644 --- a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.styles.js +++ b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.styles.js @@ -3,16 +3,9 @@ import styled from "styled-components"; const AnchorMenuWrapper = styled.div` &.sticky { position: fixed; - top: 0; + top: var(--uds-anchor-menu-top, 0px); left: 0; width: 100%; - &.with-header { - top: ${({ requiresAltMenuSpacing }) => - requiresAltMenuSpacing ? "112px" : "142px"}; - @media (max-width: 992px) { - top: 110px; - } - } } .mobile-menu-toggler { background-color: transparent; From a7a7a495de739fbc04abf9c0238f73cc0e7b6dd3 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Mon, 6 Apr 2026 16:35:57 -0700 Subject: [PATCH 3/3] fix(unity-bootstrap-theme): remove console logs --- packages/unity-bootstrap-theme/src/js/anchor-menu.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/unity-bootstrap-theme/src/js/anchor-menu.js b/packages/unity-bootstrap-theme/src/js/anchor-menu.js index 5e59bbaf13..0c80c0d790 100644 --- a/packages/unity-bootstrap-theme/src/js/anchor-menu.js +++ b/packages/unity-bootstrap-theme/src/js/anchor-menu.js @@ -53,8 +53,6 @@ function initAnchorMenu() { const target = document.getElementById(targetId); if (target) { anchorTargets.set(anchor, target); - } else { - console.warn(`Anchor menu: target element "${targetId}" not found`); } } @@ -199,7 +197,7 @@ function initAnchorMenu() { e.preventDefault(); if (!anchorTarget || !document.body.contains(anchorTarget)) { - console.warn("Anchor target no longer exists in DOM"); + console.warn("Anchor target no longer exists in DOM"); // This should be rare but if the target element has been removed from the DOM, this will make debuggin easier in webspark sites return; } @@ -212,7 +210,6 @@ function initAnchorMenu() { const topOffset = headerBottom + navbarHeight; const targetTop = anchorTarget.getBoundingClientRect().top; const viewportMid = window.innerHeight / 2; - console.log("viewportmid", viewportMid, "targetTop", targetTop); if (targetTop >= topOffset && targetTop <= viewportMid) { history.replaceState(null, "", anchor.getAttribute("href"));