astro-ghostcms/.pnpm-store/v3/files/c0/a13963934e17d13048bd30f8d26...

478 lines
16 KiB
Plaintext
Raw Permalink Normal View History

2024-02-14 14:10:47 +00:00
import { TRANSITION_AFTER_SWAP, doPreparation, doSwap } from "./events.js";
const inBrowser = import.meta.env.SSR === false;
const pushState = inBrowser && history.pushState.bind(history);
const replaceState = inBrowser && history.replaceState.bind(history);
const updateScrollPosition = (positions) => {
if (history.state) {
history.scrollRestoration = "manual";
replaceState({ ...history.state, ...positions }, "");
}
};
const supportsViewTransitions = inBrowser && !!document.startViewTransition;
const transitionEnabledOnThisPage = () => inBrowser && !!document.querySelector('[name="astro-view-transitions-enabled"]');
const samePage = (thisLocation, otherLocation) => thisLocation.pathname === otherLocation.pathname && thisLocation.search === otherLocation.search;
let originalLocation;
let viewTransition;
let skipTransition = false;
let viewTransitionFinished;
const triggerEvent = (name) => document.dispatchEvent(new Event(name));
const onPageLoad = () => triggerEvent("astro:page-load");
const announce = () => {
let div = document.createElement("div");
div.setAttribute("aria-live", "assertive");
div.setAttribute("aria-atomic", "true");
div.className = "astro-route-announcer";
document.body.append(div);
setTimeout(
() => {
let title = document.title || document.querySelector("h1")?.textContent || location.pathname;
div.textContent = title;
},
// Much thought went into this magic number; the gist is that screen readers
// need to see that the element changed and might not do so if it happens
// too quickly.
60
);
};
const PERSIST_ATTR = "data-astro-transition-persist";
const DIRECTION_ATTR = "data-astro-transition";
const OLD_NEW_ATTR = "data-astro-transition-fallback";
const VITE_ID = "data-vite-dev-id";
let parser;
let currentHistoryIndex = 0;
if (inBrowser) {
if (history.state) {
currentHistoryIndex = history.state.index;
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
replaceState({ index: currentHistoryIndex, scrollX, scrollY }, "");
history.scrollRestoration = "manual";
}
}
const throttle = (cb, delay) => {
let wait = false;
let onceMore = false;
return (...args) => {
if (wait) {
onceMore = true;
return;
}
cb(...args);
wait = true;
setTimeout(() => {
if (onceMore) {
onceMore = false;
cb(...args);
}
wait = false;
}, delay);
};
};
async function fetchHTML(href, init) {
try {
const res = await fetch(href, init);
const contentType = res.headers.get("content-type") ?? "";
const mediaType = contentType.split(";", 1)[0].trim();
if (mediaType !== "text/html" && mediaType !== "application/xhtml+xml") {
return null;
}
const html = await res.text();
return {
html,
redirected: res.redirected ? res.url : void 0,
mediaType
};
} catch (err) {
return null;
}
}
function getFallback() {
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
if (el) {
return el.getAttribute("content");
}
return "animate";
}
function runScripts() {
let wait = Promise.resolve();
for (const script of Array.from(document.scripts)) {
if (script.dataset.astroExec === "")
continue;
const type = script.getAttribute("type");
if (type && type !== "module" && type !== "text/javascript")
continue;
const newScript = document.createElement("script");
newScript.innerHTML = script.innerHTML;
for (const attr of script.attributes) {
if (attr.name === "src") {
const p = new Promise((r) => {
newScript.onload = newScript.onerror = r;
});
wait = wait.then(() => p);
}
newScript.setAttribute(attr.name, attr.value);
}
newScript.dataset.astroExec = "";
script.replaceWith(newScript);
}
return wait;
}
const moveToLocation = (to, from, options, pageTitleForBrowserHistory, historyState) => {
const intraPage = samePage(from, to);
const targetPageTitle = document.title;
document.title = pageTitleForBrowserHistory;
let scrolledToTop = false;
if (to.href !== location.href && !historyState) {
if (options.history === "replace") {
const current = history.state;
replaceState(
{
...options.state,
index: current.index,
scrollX: current.scrollX,
scrollY: current.scrollY
},
"",
to.href
);
} else {
pushState(
{ ...options.state, index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
"",
to.href
);
}
}
originalLocation = to;
if (!intraPage) {
scrollTo({ left: 0, top: 0, behavior: "instant" });
scrolledToTop = true;
}
if (historyState) {
scrollTo(historyState.scrollX, historyState.scrollY);
} else {
if (to.hash) {
history.scrollRestoration = "auto";
const savedState = history.state;
location.href = to.href;
history.state || replaceState(savedState, "");
} else {
if (!scrolledToTop) {
scrollTo({ left: 0, top: 0, behavior: "instant" });
}
}
history.scrollRestoration = "manual";
}
document.title = targetPageTitle;
};
function preloadStyleLinks(newDocument) {
const links = [];
for (const el of newDocument.querySelectorAll("head link[rel=stylesheet]")) {
if (!document.querySelector(
`[${PERSIST_ATTR}="${el.getAttribute(
PERSIST_ATTR
)}"], link[rel=stylesheet][href="${el.getAttribute("href")}"]`
)) {
const c = document.createElement("link");
c.setAttribute("rel", "preload");
c.setAttribute("as", "style");
c.setAttribute("href", el.getAttribute("href"));
links.push(
new Promise((resolve) => {
["load", "error"].forEach((evName) => c.addEventListener(evName, resolve));
document.head.append(c);
})
);
}
}
return links;
}
async function updateDOM(preparationEvent, options, historyState, fallback) {
const persistedHeadElement = (el, newDoc) => {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = id && newDoc.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
return newEl;
}
if (el.matches("link[rel=stylesheet]")) {
const href = el.getAttribute("href");
return newDoc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
return null;
};
const saveFocus = () => {
const activeElement = document.activeElement;
if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
const start = activeElement.selectionStart;
const end = activeElement.selectionEnd;
return { activeElement, start, end };
}
return { activeElement };
} else {
return { activeElement: null };
}
};
const restoreFocus = ({ activeElement, start, end }) => {
if (activeElement) {
activeElement.focus();
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
activeElement.selectionStart = start;
activeElement.selectionEnd = end;
}
}
};
const defaultSwap = (beforeSwapEvent) => {
const html = document.documentElement;
const astroAttributes = [...html.attributes].filter(
({ name }) => (html.removeAttribute(name), name.startsWith("data-astro-"))
);
[...beforeSwapEvent.newDocument.documentElement.attributes, ...astroAttributes].forEach(
({ name, value }) => html.setAttribute(name, value)
);
for (const s1 of document.scripts) {
for (const s2 of beforeSwapEvent.newDocument.scripts) {
if (
// Inline
!s1.src && s1.textContent === s2.textContent || // External
s1.src && s1.type === s2.type && s1.src === s2.src
) {
s2.dataset.astroExec = "";
break;
}
}
}
for (const el of Array.from(document.head.children)) {
const newEl = persistedHeadElement(el, beforeSwapEvent.newDocument);
if (newEl) {
newEl.remove();
} else {
el.remove();
}
}
document.head.append(...beforeSwapEvent.newDocument.head.children);
const oldBody = document.body;
const savedFocus = saveFocus();
document.body.replaceWith(beforeSwapEvent.newDocument.body);
for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
newEl.replaceWith(el);
}
}
restoreFocus(savedFocus);
};
async function animate(phase) {
function isInfinite(animation) {
const effect = animation.effect;
if (!effect || !(effect instanceof KeyframeEffect) || !effect.target)
return false;
const style = window.getComputedStyle(effect.target, effect.pseudoElement);
return style.animationIterationCount === "infinite";
}
const currentAnimations = document.getAnimations();
document.documentElement.setAttribute(OLD_NEW_ATTR, phase);
const nextAnimations = document.getAnimations();
const newAnimations = nextAnimations.filter(
(a) => !currentAnimations.includes(a) && !isInfinite(a)
);
return Promise.all(newAnimations.map((a) => a.finished));
}
if (!skipTransition) {
document.documentElement.setAttribute(DIRECTION_ATTR, preparationEvent.direction);
if (fallback === "animate") {
await animate("old");
}
} else {
throw new DOMException("Transition was skipped");
}
const pageTitleForBrowserHistory = document.title;
const swapEvent = await doSwap(preparationEvent, viewTransition, defaultSwap);
moveToLocation(swapEvent.to, swapEvent.from, options, pageTitleForBrowserHistory, historyState);
triggerEvent(TRANSITION_AFTER_SWAP);
if (fallback === "animate" && !skipTransition) {
animate("new").then(() => viewTransitionFinished());
}
}
async function transition(direction, from, to, options, historyState) {
if (!transitionEnabledOnThisPage() || location.origin !== to.origin) {
location.href = to.href;
return;
}
const navigationType = historyState ? "traverse" : options.history === "replace" ? "replace" : "push";
if (navigationType !== "traverse") {
updateScrollPosition({ scrollX, scrollY });
}
if (samePage(from, to) && !!to.hash) {
moveToLocation(to, from, options, document.title, historyState);
return;
}
const prepEvent = await doPreparation(
from,
to,
direction,
navigationType,
options.sourceElement,
options.info,
options.formData,
defaultLoader
);
if (prepEvent.defaultPrevented) {
location.href = to.href;
return;
}
async function defaultLoader(preparationEvent) {
const href = preparationEvent.to.href;
const init = {};
if (preparationEvent.formData) {
init.method = "POST";
const form = preparationEvent.sourceElement instanceof HTMLFormElement ? preparationEvent.sourceElement : preparationEvent.sourceElement instanceof HTMLElement && "form" in preparationEvent.sourceElement ? preparationEvent.sourceElement.form : preparationEvent.sourceElement?.closest("form");
init.body = form?.attributes.getNamedItem("enctype")?.value === "application/x-www-form-urlencoded" ? new URLSearchParams(preparationEvent.formData) : preparationEvent.formData;
}
const response = await fetchHTML(href, init);
if (response === null) {
preparationEvent.preventDefault();
return;
}
if (response.redirected) {
preparationEvent.to = new URL(response.redirected);
}
parser ??= new DOMParser();
preparationEvent.newDocument = parser.parseFromString(response.html, response.mediaType);
preparationEvent.newDocument.querySelectorAll("noscript").forEach((el) => el.remove());
if (!preparationEvent.newDocument.querySelector('[name="astro-view-transitions-enabled"]') && !preparationEvent.formData) {
preparationEvent.preventDefault();
return;
}
const links = preloadStyleLinks(preparationEvent.newDocument);
links.length && await Promise.all(links);
if (import.meta.env.DEV)
await prepareForClientOnlyComponents(preparationEvent.newDocument, preparationEvent.to);
}
skipTransition = false;
if (supportsViewTransitions) {
viewTransition = document.startViewTransition(
async () => await updateDOM(prepEvent, options, historyState)
);
} else {
const updateDone = (async () => {
await new Promise((r) => setTimeout(r));
await updateDOM(prepEvent, options, historyState, getFallback());
})();
viewTransition = {
updateCallbackDone: updateDone,
// this is about correct
ready: updateDone,
// good enough
finished: new Promise((r) => viewTransitionFinished = r),
// see end of updateDOM
skipTransition: () => {
skipTransition = true;
}
};
}
viewTransition.ready.then(async () => {
await runScripts();
onPageLoad();
announce();
});
viewTransition.finished.then(() => {
document.documentElement.removeAttribute(DIRECTION_ATTR);
document.documentElement.removeAttribute(OLD_NEW_ATTR);
});
await viewTransition.ready;
}
let navigateOnServerWarned = false;
async function navigate(href, options) {
if (inBrowser === false) {
if (!navigateOnServerWarned) {
const warning = new Error(
"The view transitions client API was called during a server side render. This may be unintentional as the navigate() function is expected to be called in response to user interactions. Please make sure that your usage is correct."
);
warning.name = "Warning";
console.warn(warning);
navigateOnServerWarned = true;
}
return;
}
await transition("forward", originalLocation, new URL(href, location.href), options ?? {});
}
function onPopState(ev) {
if (!transitionEnabledOnThisPage() && ev.state) {
location.reload();
return;
}
if (ev.state === null) {
return;
}
const state = history.state;
const nextIndex = state.index;
const direction = nextIndex > currentHistoryIndex ? "forward" : "back";
currentHistoryIndex = nextIndex;
transition(direction, originalLocation, new URL(location.href), {}, state);
}
const onScroll = () => {
updateScrollPosition({ scrollX, scrollY });
};
if (inBrowser) {
if (supportsViewTransitions || getFallback() !== "none") {
originalLocation = new URL(location.href);
addEventListener("popstate", onPopState);
addEventListener("load", onPageLoad);
if ("onscrollend" in window)
addEventListener("scrollend", onScroll);
else
addEventListener("scroll", throttle(onScroll, 350), { passive: true });
}
for (const script of document.scripts) {
script.dataset.astroExec = "";
}
}
async function prepareForClientOnlyComponents(newDocument, toLocation) {
if (newDocument.body.querySelector(`astro-island[client='only']`)) {
const nextPage = document.createElement("iframe");
nextPage.src = toLocation.href;
nextPage.style.display = "none";
document.body.append(nextPage);
nextPage.contentWindow.console = Object.keys(console).reduce((acc, key) => {
acc[key] = () => {
};
return acc;
}, {});
await hydrationDone(nextPage);
const nextHead = nextPage.contentDocument?.head;
if (nextHead) {
document.head.querySelectorAll(`style[${PERSIST_ATTR}=""]`).forEach((s) => s.removeAttribute(PERSIST_ATTR));
const viteIds = [...nextHead.querySelectorAll(`style[${VITE_ID}]`)].map(
(style) => style.getAttribute(VITE_ID)
);
viteIds.forEach((id) => {
const style = document.head.querySelector(`style[${VITE_ID}="${id}"]`);
if (style && !newDocument.head.querySelector(`style[${VITE_ID}="${id}"]`)) {
newDocument.head.appendChild(style.cloneNode(true));
}
});
}
async function hydrationDone(loadingPage) {
await new Promise(
(r) => loadingPage.contentWindow?.addEventListener("load", r, { once: true })
);
return new Promise(async (r) => {
for (let count = 0; count <= 20; ++count) {
if (!loadingPage.contentDocument.body.querySelector("astro-island[ssr]"))
break;
await new Promise((r2) => setTimeout(r2, 50));
}
r();
});
}
}
}
export {
navigate,
supportsViewTransitions,
transitionEnabledOnThisPage,
updateScrollPosition
};