import GNS from "../GNS.js";
import AriaButton from "./AriaButton.js";
const NAME = "MainNav";
const NS = `${GNS}.${NAME}`;
const $ = GNS.jQuery;
$.easing[NS] = x => 1 - Math.pow(1 - x, 3.5);
const SUPPORT = {
HISTORY: window.history && history.replaceState,
PASSIVE_EVENT: ((support = false) => {
try { addEventListener("x", null, { get passive() { support = true; } }); } catch (e) {}
return support;
})()
};
const CLASS = {
activeLink: "main-nav__link--active",
fixableFixed: "page-header__top--fixed"
};
const DATA = {
focus: "main-nav-focus",
active: "main-nav-active",
scrollTo: "main-nav-scroll-to",
ignore: "main-nav-ignore",
call: "main-nav-call"
};
const SELECTOR = {
self: ".main-nav",
itemsWrapper: ".main-nav__items",
item: ".main-nav__item",
link: ".main-nav__link",
activeLink: "." + CLASS.activeLink,
mobileOpener: ".main-nav__opener.js-aria-button",
fixable: ".page-header__top",
scrollTarget: "[data-main-nav-target='true']",
linkWithHash: "a[href*='#']:not([href='#']):not([data-" + DATA.ignore + "='true'])"
};
const EVENT = {
change: NS + ".change"
};
const ELEMENT = {
$self: null,
$itemsWrapper: null,
$acitveLink: null,
$mobileOpener: null,
$fixable: null,
$scrollTargets: null,
currentScrollTarget: null,
$scrollingElement: null
};
const OPTION = {
SCROLL_DURATION_BASE: 400,
TARGET_IN_VIEW_DIV: 4
};
const SCROLL_OFFSET = {
"(max-height: 29.9375em)": 0,
"(min-height: 30em) and (max-width: 47.9375em)": 60,
"(min-height: 30em) and (max-width: 63.9375em)": 66,
"(min-height: 40em) and (max-width: 78.6875em)": 77,
"(min-height: 40em)": 84
};
let isFixed = false;
let skipFindLinkToActivateOnScroll;
let skipFindLinkToActivateOnScrollTimeout;
let scrollThrottle;
function getFixPosition() {
return window.matchMedia(Object.keys(SCROLL_OFFSET)[0]).matches ? Infinity: 0;
}
function getScrollOffset() {
let offset = 0;
Object.keys(SCROLL_OFFSET).some(mq => {
if (window.matchMedia(mq).matches) {
offset = SCROLL_OFFSET[mq];
return true;
}
});
return offset;
}
function getDataOption(option, ...$elements) {
let elements = $elements.filter($el => typeof $el.attr("data-" + option) !== "undefined");
return elements.length ? elements[0].attr("data-" + option): undefined;
}
function toggleFixed(state) {
isFixed = state;
ELEMENT.$fixable[state ? "addClass" : "removeClass"](CLASS.fixableFixed);
}
function toggleMobileOpener(state) {
if (ELEMENT.$mobileOpener[0]) {
AriaButton.setState(ELEMENT.$mobileOpener[0], state);
}
}
function getScrollableHeight() {
return document.documentElement.scrollHeight - window.innerHeight;
}
function deactivateActiveLink() {
ELEMENT.$self
.find(SELECTOR.activeLink)
.removeClass(CLASS.activeLink);
ELEMENT.$acitveLink = null;
}
function activateLink($link) {
if (!$link || (ELEMENT.$acitveLink && ELEMENT.$acitveLink[0] === $link[0])) {
return;
}
deactivateActiveLink();
if ($link.length && $link.closest(SELECTOR.self).length) {
$link.addClass(CLASS.activeLink);
ELEMENT.$acitveLink = $link;
updateHistory($link);
}
}
function updateHistory($link = null) {
if ($link === null && SUPPORT.HISTORY) {
history.replaceState(null, null, "#");
GNS.$win.trigger(EVENT.change, [null]);
}
if ($link && $link[0]) {
let linkHash = $link[0].hash;
if (SUPPORT.HISTORY && history.state !== linkHash) {
history.replaceState(linkHash, null, linkHash);
GNS.$win.trigger(EVENT.change, [linkHash.replace("#", "")]);
}
}
}
function make$ElementFocusableAndFocus($element) {
if (typeof $element.attr("tabindex") === "undefined") {
$element
.attr("tabindex", -1)
.off("blur." + NS + " focusout." + NS)
.one("blur." + NS + " focusout." + NS, () => {
$element
.removeAttr("tabindex")
.off("blur." + NS + " focusout." + NS);
});
}
let currentScrollTop = GNS.$win.scrollTop();
$element[0].focus({
preventScroll: true
});
GNS.$win.scrollTop(currentScrollTop);
}
function moveFocus($link, $focusTarget) {
let focusSelector = getDataOption(DATA.focus, $link, $focusTarget);
if (focusSelector) {
$focusTarget = $focusTarget.find(focusSelector);
}
if ($focusTarget.length) {
make$ElementFocusableAndFocus($focusTarget);
}
}
function isTargetInView(rect) {
return rect.top <= window.innerHeight / OPTION.TARGET_IN_VIEW_DIV &&
rect.bottom > window.innerHeight / OPTION.TARGET_IN_VIEW_DIV;
}
function isLocalLink(linkEl) {
return linkEl &&
location.hostname === linkEl.hostname &&
location.pathname.replace(/^\//, "") === linkEl.pathname.replace(/^\//, "");
}
function findMainNav$LinkByTargetId(idOrHash) {
let id = idOrHash.replace("#", "");
let $link = ELEMENT.$itemsWrapper.find(`[href*="#${id}"]`);
return $link.filter((i, el) => el.hash === ("#" + id));
}
function getScrollTargetTop($link, $target) {
let scrollTo = parseFloat(getDataOption(DATA.scrollTo, $link, $target));
return Math.min(
Math.max(
!isNaN(scrollTo) && isFinite(scrollTo) ?
$target.offset().top - (window.innerHeight * (scrollTo / 100)):
$target.offset().top - getScrollOffset(),
0
),
getScrollableHeight()
);
}
function onScrollAnimationBreakByUser(event) {
if (event.type === "touchstart" && !GNS.$t(event.target).closest(SELECTOR.link).length) {
event.preventDefault();
}
ELEMENT.$scrollingElement.stop();
clearTimeout(skipFindLinkToActivateOnScrollTimeout);
skipFindLinkToActivateOnScroll = false;
activateByScroll(true);
}
function onScrollAnimationStop() {
GNS.$t(document.body).removeAttr("aria-busy");
destroyScrollAnimationBreakByUser();
clearTimeout(scrollThrottle);
scrollThrottle = null;
handleFixableEl();
}
function initScrollAnimationBreakByUser() {
destroyScrollAnimationBreakByUser();
window.addEventListener("mousewheel", onScrollAnimationBreakByUser);
window.addEventListener("DOMMouseScroll", onScrollAnimationBreakByUser);
window.addEventListener("keydown", onScrollAnimationBreakByUser);
window.addEventListener("touchstart", onScrollAnimationBreakByUser, SUPPORT.PASSIVE_EVENT ? { passive: false }: false);
}
function destroyScrollAnimationBreakByUser() {
window.removeEventListener("mousewheel", onScrollAnimationBreakByUser);
window.removeEventListener("DOMMouseScroll", onScrollAnimationBreakByUser);
window.removeEventListener("keydown", onScrollAnimationBreakByUser);
window.removeEventListener("touchstart", onScrollAnimationBreakByUser);
}
function animateScrollTop(scrollTop, scrollDuration, onComplete) {
GNS.$t(document.body).attr("aria-busy", "true");
skipFindLinkToActivateOnScroll = true;
debounceSkipFindLinkToActivateOnScroll();
initScrollAnimationBreakByUser();
ELEMENT.$scrollingElement
.stop()
.animate(
{ scrollTop: scrollTop },
{
duration: scrollDuration,
easing: NS,
always: onScrollAnimationStop,
complete: () => {
if (onComplete && !onComplete.run) {
onComplete();
onComplete.run = true;
}
}
}
);
}
function getScrollDuration(targetScrollTop) {
let scrollAmount = Math.abs(GNS.$win.scrollTop() - targetScrollTop);
return OPTION.SCROLL_DURATION_BASE + (OPTION.SCROLL_DURATION_BASE * scrollAmount / getScrollableHeight());
}
function callOnScrollAnimCompleteCallFn($link, $scrollTarget) {
let call = getDataOption(DATA.call, $link, $scrollTarget);
if (call && typeof GNS[call] === "function") {
GNS[call]();
} else if (call && typeof window[call] === "function") {
window[call]();
}
}
function get$LinkToActivate($link, $scrollTarget) {
let activateLinkSelector = getDataOption(DATA.active, $link, $scrollTarget);
if (activateLinkSelector) {
return $(activateLinkSelector);
}
let linkIsInsideMainNav = $link.closest(SELECTOR.self).length;
return linkIsInsideMainNav ? $link: findMainNav$LinkByTargetId($link[0].hash);
}
function get$LinkToActivateFromTargetEl(scrollTargetEl) {
let activateLinkSelector = scrollTargetEl.getAttribute("data-" + DATA.active);
return activateLinkSelector ? $(activateLinkSelector): findMainNav$LinkByTargetId(scrollTargetEl.id);
}
function scrollToTargetByLink(linkElOr$link) {
if (!linkElOr$link || (linkElOr$link.jquery && !linkElOr$link.length)) {
return false;
}
let $link = linkElOr$link.jquery ? linkElOr$link: $(linkElOr$link);
let targetId = $link[0].hash.replace("#", "");
let $scrollTarget = ELEMENT.$scrollTargets.filter("#" + targetId);
if ($scrollTarget.length) {
$scrollTarget.attr("id", "");
$link.blur();
ELEMENT.currentScrollTarget = $scrollTarget[0];
let scrollTargetTop = getScrollTargetTop($link, $scrollTarget);
animateScrollTop(
scrollTargetTop,
getScrollDuration(scrollTargetTop),
() => {
moveFocus($link, $scrollTarget);
callOnScrollAnimCompleteCallFn($link, $scrollTarget);
}
);
activateLink(get$LinkToActivate($link, $scrollTarget));
toggleMobileOpener(false);
$scrollTarget.attr("id", targetId);
return true;
}
return false;
}
function findCurrentScrollTargetEl() {
let currentScrollTargetEl = null;
let currentScrollTargetTop = null;
ELEMENT.$scrollTargets.each((i, targetEl) => {
let rect = targetEl.getBoundingClientRect();
if (isTargetInView(rect) && (rect.top > currentScrollTargetTop || currentScrollTargetTop === null)) {
currentScrollTargetEl = targetEl;
currentScrollTargetTop = rect.top;
}
});
return currentScrollTargetEl;
}
function debounceSkipFindLinkToActivateOnScroll() {
clearTimeout(skipFindLinkToActivateOnScrollTimeout);
skipFindLinkToActivateOnScrollTimeout = setTimeout(() => {
skipFindLinkToActivateOnScroll = false;
}, 150);
}
function activateByScroll(forceActivation) {
if (!forceActivation && skipFindLinkToActivateOnScroll) {
return debounceSkipFindLinkToActivateOnScroll();
}
let currentScrollTargetEl = findCurrentScrollTargetEl();
if (forceActivation || ELEMENT.currentScrollTarget !== currentScrollTargetEl) {
ELEMENT.currentScrollTarget = currentScrollTargetEl;
if (!currentScrollTargetEl) {
deactivateActiveLink();
updateHistory(null);
return;
}
let $link = get$LinkToActivateFromTargetEl(currentScrollTargetEl);
activateLink($link);
updateHistory($link);
}
}
function onMobileOpenerPressed() {
make$ElementFocusableAndFocus(ELEMENT.$itemsWrapper);
}
function scrollTo(targetOrLinkEl) {
if (typeof targetOrLinkEl !== "string") {
if (GNS.$t(targetOrLinkEl).is(SELECTOR.link)) {
return scrollToTargetByLink(targetOrLinkEl);
}
return scrollToTargetByLink(
findMainNav$LinkByTargetId(
targetOrLinkEl.jquery ? targetOrLinkEl[0].id: targetOrLinkEl.id
)
);
}
return scrollToTargetByLink(
findMainNav$LinkByTargetId(targetOrLinkEl)
);
}
function handleFixableEl() {
let scrollTop = GNS.$win.scrollTop();
if (scrollTop > getFixPosition()) {
if (!isFixed) {
toggleFixed(true);
}
} else if (isFixed) {
toggleFixed(false);
}
}
function handleLinkWithHashClick(event) {
if (isLocalLink(event.currentTarget) && scrollToTargetByLink(event.currentTarget)) {
event.preventDefault();
}
}
function handleScrollAndResize() {
if (!scrollThrottle) {
if (!isFixed) {
handleFixableEl();
}
scrollThrottle = setTimeout(() => {
activateByScroll();
handleFixableEl();
scrollThrottle = null;
}, 66.666);
}
}
function initEvents() {
GNS.$win.on("scroll." + NS + " resize." + NS, handleScrollAndResize);
GNS.$doc.on("click." + NS, SELECTOR.linkWithHash, handleLinkWithHashClick);
GNS.$win.trigger("scroll." + NS);
window.addEventListener("ariabutton__change", event => {
if (event.target === ELEMENT.$mobileOpener[0] && event.detail.state) {
onMobileOpenerPressed();
}
});
}
function initElements() {
ELEMENT.$self = $(SELECTOR.self);
ELEMENT.$itemsWrapper = ELEMENT.$self.find(SELECTOR.itemsWrapper);
ELEMENT.$mobileOpener = ELEMENT.$self.find(SELECTOR.mobileOpener);
ELEMENT.$fixable = $(SELECTOR.fixable);
ELEMENT.$scrollTargets = $(SELECTOR.scrollTarget);
ELEMENT.$scrollingElement = $(document.scrollingElement || "html, body");
}
function init() {
if (!init.initialized) {
initElements();
initEvents();
init.initialized = true;
}
}
export default { init, scrollTo };