[ci] format
This commit is contained in:
parent
6a12fcecb0
commit
7a6ca236e8
12 changed files with 198 additions and 148 deletions
|
@ -7,8 +7,9 @@ export interface Props {
|
||||||
|
|
||||||
const { fallback = 'animate' } = Astro.props as Props;
|
const { fallback = 'animate' } = Astro.props as Props;
|
||||||
---
|
---
|
||||||
<meta name="astro-view-transitions-enabled" content="true">
|
|
||||||
<meta name="astro-view-transitions-fallback" content={fallback}>
|
<meta name="astro-view-transitions-enabled" content="true" />
|
||||||
|
<meta name="astro-view-transitions-fallback" content={fallback} />
|
||||||
<script>
|
<script>
|
||||||
type Fallback = 'none' | 'animate' | 'swap';
|
type Fallback = 'none' | 'animate' | 'swap';
|
||||||
type Direction = 'forward' | 'back';
|
type Direction = 'forward' | 'back';
|
||||||
|
@ -17,22 +18,23 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
// you can figure it using an index. On pushState the index is incremented so you
|
// you can figure it using an index. On pushState the index is incremented so you
|
||||||
// can use that to determine popstate if going forward or back.
|
// can use that to determine popstate if going forward or back.
|
||||||
let currentHistoryIndex = history.state?.index || 0;
|
let currentHistoryIndex = history.state?.index || 0;
|
||||||
if(!history.state) {
|
if (!history.state) {
|
||||||
history.replaceState({index: currentHistoryIndex}, document.title);
|
history.replaceState({ index: currentHistoryIndex }, document.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsViewTransitions = !!document.startViewTransition;
|
const supportsViewTransitions = !!document.startViewTransition;
|
||||||
const transitionEnabledOnThisPage = () => !!document.querySelector('[name="astro-view-transitions-enabled"]');
|
const transitionEnabledOnThisPage = () =>
|
||||||
|
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||||
|
|
||||||
async function getHTML(href: string) {
|
async function getHTML(href: string) {
|
||||||
const res = await fetch(href)
|
const res = await fetch(href);
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
return { ok: res.ok, html };
|
return { ok: res.ok, html };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFallback(): Fallback {
|
function getFallback(): Fallback {
|
||||||
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
|
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
|
||||||
if(el) {
|
if (el) {
|
||||||
return el.getAttribute('content') as Fallback;
|
return el.getAttribute('content') as Fallback;
|
||||||
}
|
}
|
||||||
return 'animate';
|
return 'animate';
|
||||||
|
@ -40,14 +42,14 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
||||||
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
|
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
|
||||||
const doc = parser.parseFromString(html, 'text/html');
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
doc.documentElement.dataset.astroTransition = dir;
|
doc.documentElement.dataset.astroTransition = dir;
|
||||||
const swap = () => document.documentElement.replaceWith(doc.documentElement);
|
const swap = () => document.documentElement.replaceWith(doc.documentElement);
|
||||||
|
|
||||||
if(fallback === 'animate') {
|
if (fallback === 'animate') {
|
||||||
let isAnimating = false;
|
let isAnimating = false;
|
||||||
addEventListener('animationstart', () => isAnimating = true, { once: true });
|
addEventListener('animationstart', () => (isAnimating = true), { once: true });
|
||||||
|
|
||||||
// Trigger the animations
|
// Trigger the animations
|
||||||
document.documentElement.dataset.astroTransitionFallback = 'old';
|
document.documentElement.dataset.astroTransitionFallback = 'old';
|
||||||
|
@ -61,17 +63,17 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
} else {
|
} else {
|
||||||
swap();
|
swap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function navigate(dir: Direction, href: string) {
|
async function navigate(dir: Direction, href: string) {
|
||||||
let finished: Promise<void>;
|
let finished: Promise<void>;
|
||||||
const { html, ok } = await getHTML(href);
|
const { html, ok } = await getHTML(href);
|
||||||
// If there is a problem fetching the new page, just do an MPA navigation to it.
|
// If there is a problem fetching the new page, just do an MPA navigation to it.
|
||||||
if(!ok) {
|
if (!ok) {
|
||||||
location.href = href;
|
location.href = href;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(supportsViewTransitions) {
|
if (supportsViewTransitions) {
|
||||||
finished = document.startViewTransition(() => updateDOM(dir, html)).finished;
|
finished = document.startViewTransition(() => updateDOM(dir, html)).finished;
|
||||||
} else {
|
} else {
|
||||||
finished = updateDOM(dir, html, getFallback());
|
finished = updateDOM(dir, html, getFallback());
|
||||||
|
@ -83,12 +85,12 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefetching
|
// Prefetching
|
||||||
function maybePrefetch(pathname: string) {
|
function maybePrefetch(pathname: string) {
|
||||||
if(document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
|
if (document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
|
||||||
if(navigator.connection){
|
if (navigator.connection) {
|
||||||
let conn = navigator.connection;
|
let conn = navigator.connection;
|
||||||
if(conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return;
|
if (conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return;
|
||||||
}
|
}
|
||||||
let link = document.createElement('link');
|
let link = document.createElement('link');
|
||||||
link.setAttribute('rel', 'prefetch');
|
link.setAttribute('rel', 'prefetch');
|
||||||
|
@ -96,51 +98,60 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
document.head.append(link);
|
document.head.append(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(supportsViewTransitions || getFallback() !== 'none') {
|
if (supportsViewTransitions || getFallback() !== 'none') {
|
||||||
document.addEventListener('click', (ev) => {
|
document.addEventListener('click', (ev) => {
|
||||||
let link = ev.target;
|
let link = ev.target;
|
||||||
if(link instanceof Element && link.tagName !== 'A') {
|
if (link instanceof Element && link.tagName !== 'A') {
|
||||||
link = link.closest('a');
|
link = link.closest('a');
|
||||||
}
|
|
||||||
// This check verifies that the click is happening on an anchor
|
|
||||||
// that is going to another page within the same origin. Basically it determines
|
|
||||||
// same-origin navigation, but omits special key combos for new tabs, etc.
|
|
||||||
if (link &&
|
|
||||||
link instanceof HTMLAnchorElement &&
|
|
||||||
link.href &&
|
|
||||||
(!link.target || link.target === '_self') &&
|
|
||||||
link.origin === location.origin &&
|
|
||||||
ev.button === 0 && // left clicks only
|
|
||||||
!ev.metaKey && // new tab (mac)
|
|
||||||
!ev.ctrlKey && // new tab (windows)
|
|
||||||
!ev.altKey && // download
|
|
||||||
!ev.shiftKey &&
|
|
||||||
!ev.defaultPrevented &&
|
|
||||||
transitionEnabledOnThisPage()
|
|
||||||
) {
|
|
||||||
ev.preventDefault();
|
|
||||||
navigate('forward', link.href);
|
|
||||||
currentHistoryIndex++;
|
|
||||||
history.pushState({index: currentHistoryIndex}, '', link.href);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.addEventListener('popstate', () => {
|
|
||||||
if(!transitionEnabledOnThisPage()) return;
|
|
||||||
const nextIndex = history.state?.index ?? (currentHistoryIndex + 1);
|
|
||||||
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
|
|
||||||
navigate(direction, location.href);
|
|
||||||
currentHistoryIndex = nextIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
['mouseenter', 'touchstart', 'focus'].forEach(evName => {
|
|
||||||
document.addEventListener(evName, ev => {
|
|
||||||
if(ev.target instanceof HTMLAnchorElement) {
|
|
||||||
let el = ev.target;
|
|
||||||
if(el.origin === location.origin && el.pathname !== location.pathname && transitionEnabledOnThisPage()) {
|
|
||||||
maybePrefetch(el.pathname);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, { passive: true, capture: true });
|
// This check verifies that the click is happening on an anchor
|
||||||
});
|
// that is going to another page within the same origin. Basically it determines
|
||||||
|
// same-origin navigation, but omits special key combos for new tabs, etc.
|
||||||
|
if (
|
||||||
|
link &&
|
||||||
|
link instanceof HTMLAnchorElement &&
|
||||||
|
link.href &&
|
||||||
|
(!link.target || link.target === '_self') &&
|
||||||
|
link.origin === location.origin &&
|
||||||
|
ev.button === 0 && // left clicks only
|
||||||
|
!ev.metaKey && // new tab (mac)
|
||||||
|
!ev.ctrlKey && // new tab (windows)
|
||||||
|
!ev.altKey && // download
|
||||||
|
!ev.shiftKey &&
|
||||||
|
!ev.defaultPrevented &&
|
||||||
|
transitionEnabledOnThisPage()
|
||||||
|
) {
|
||||||
|
ev.preventDefault();
|
||||||
|
navigate('forward', link.href);
|
||||||
|
currentHistoryIndex++;
|
||||||
|
history.pushState({ index: currentHistoryIndex }, '', link.href);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
if (!transitionEnabledOnThisPage()) return;
|
||||||
|
const nextIndex = history.state?.index ?? currentHistoryIndex + 1;
|
||||||
|
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
|
||||||
|
navigate(direction, location.href);
|
||||||
|
currentHistoryIndex = nextIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
|
||||||
|
document.addEventListener(
|
||||||
|
evName,
|
||||||
|
(ev) => {
|
||||||
|
if (ev.target instanceof HTMLAnchorElement) {
|
||||||
|
let el = ev.target;
|
||||||
|
if (
|
||||||
|
el.origin === location.origin &&
|
||||||
|
el.pathname !== location.pathname &&
|
||||||
|
transitionEnabledOnThisPage()
|
||||||
|
) {
|
||||||
|
maybePrefetch(el.pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ passive: true, capture: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,25 +8,37 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroFadeIn {
|
@keyframes astroFadeIn {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroFadeOut {
|
@keyframes astroFadeOut {
|
||||||
to { opacity: 0; }
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroSlideFromRight {
|
@keyframes astroSlideFromRight {
|
||||||
from { transform: translateX(100%); }
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroSlideFromLeft {
|
@keyframes astroSlideFromLeft {
|
||||||
from { transform: translateX(-100%); }
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroSlideToRight {
|
@keyframes astroSlideToRight {
|
||||||
to { transform: translateX(100%); }
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes astroSlideToLeft {
|
@keyframes astroSlideToLeft {
|
||||||
to { transform: translateX(-100%); }
|
to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ test.afterAll(async () => {
|
||||||
test.describe('View Transitions', () => {
|
test.describe('View Transitions', () => {
|
||||||
test('Moving from page 1 to page 2', async ({ page, astro }) => {
|
test('Moving from page 1 to page 2', async ({ page, astro }) => {
|
||||||
const loads = [];
|
const loads = [];
|
||||||
page.addListener('load', p => {
|
page.addListener('load', (p) => {
|
||||||
loads.push(p.title());
|
loads.push(p.title());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ test.describe('View Transitions', () => {
|
||||||
|
|
||||||
test('Back button is captured', async ({ page, astro }) => {
|
test('Back button is captured', async ({ page, astro }) => {
|
||||||
const loads = [];
|
const loads = [];
|
||||||
page.addListener('load', p => {
|
page.addListener('load', (p) => {
|
||||||
loads.push(p.title());
|
loads.push(p.title());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ test.describe('View Transitions', () => {
|
||||||
|
|
||||||
test('Clicking on a link with nested content', async ({ page, astro }) => {
|
test('Clicking on a link with nested content', async ({ page, astro }) => {
|
||||||
const loads = [];
|
const loads = [];
|
||||||
page.addListener('load', p => {
|
page.addListener('load', (p) => {
|
||||||
loads.push(p.title());
|
loads.push(p.title());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,9 +76,12 @@ test.describe('View Transitions', () => {
|
||||||
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Moving from a page without ViewTransitions triggers a full page navigation', async ({ page, astro }) => {
|
test('Moving from a page without ViewTransitions triggers a full page navigation', async ({
|
||||||
|
page,
|
||||||
|
astro,
|
||||||
|
}) => {
|
||||||
const loads = [];
|
const loads = [];
|
||||||
page.addListener('load', p => {
|
page.addListener('load', (p) => {
|
||||||
loads.push(p.title());
|
loads.push(p.title());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,6 +99,9 @@ test.describe('View Transitions', () => {
|
||||||
p = page.locator('#two');
|
p = page.locator('#two');
|
||||||
await expect(p, 'should have content').toHaveText('Page 2');
|
await expect(p, 'should have content').toHaveText('Page 2');
|
||||||
|
|
||||||
expect(loads.length, 'There should be 2 page loads. The original, then going from 3 to 2').toEqual(2);
|
expect(
|
||||||
|
loads.length,
|
||||||
|
'There should be 2 page loads. The original, then going from 3 to 2'
|
||||||
|
).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,10 +56,10 @@ export interface AstroBuiltinProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransitionAnimation {
|
export interface TransitionAnimation {
|
||||||
name: string; // The name of the keyframe
|
name: string; // The name of the keyframe
|
||||||
delay?: number | string;
|
delay?: number | string;
|
||||||
duration?: number | string;
|
duration?: number | string;
|
||||||
easing?: string;
|
easing?: string;
|
||||||
fillMode?: string;
|
fillMode?: string;
|
||||||
direction?: string;
|
direction?: string;
|
||||||
}
|
}
|
||||||
|
@ -1270,7 +1270,7 @@ export interface AstroUserConfig {
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
viewTransitions?: boolean;
|
viewTransitions?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Legacy options to be removed
|
// Legacy options to be removed
|
||||||
|
|
|
@ -233,7 +233,10 @@ export const AstroConfigSchema = z.object({
|
||||||
experimental: z
|
experimental: z
|
||||||
.object({
|
.object({
|
||||||
assets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.assets),
|
assets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.assets),
|
||||||
viewTransitions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.viewTransitions),
|
viewTransitions: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(ASTRO_CONFIG_DEFAULTS.experimental.viewTransitions),
|
||||||
})
|
})
|
||||||
.passthrough()
|
.passthrough()
|
||||||
.refine(
|
.refine(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
astroContentImportPlugin,
|
astroContentImportPlugin,
|
||||||
astroContentVirtualModPlugin,
|
astroContentVirtualModPlugin,
|
||||||
} from '../content/index.js';
|
} from '../content/index.js';
|
||||||
|
import astroTransitions from '../transitions/vite-plugin-transitions.js';
|
||||||
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
||||||
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
|
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
|
||||||
import astroVitePlugin from '../vite-plugin-astro/index.js';
|
import astroVitePlugin from '../vite-plugin-astro/index.js';
|
||||||
|
@ -27,7 +28,6 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
|
||||||
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
||||||
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
||||||
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
|
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
|
||||||
import astroTransitions from '../transitions/vite-plugin-transitions.js';
|
|
||||||
import { joinPaths } from './path.js';
|
import { joinPaths } from './path.js';
|
||||||
|
|
||||||
interface CreateViteOptions {
|
interface CreateViteOptions {
|
||||||
|
|
|
@ -7,7 +7,11 @@ function validateArgs(args: unknown[]): args is Parameters<AstroComponentFactory
|
||||||
if (!args[0] || typeof args[0] !== 'object') return false;
|
if (!args[0] || typeof args[0] !== 'object') return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string, propagation?: PropagationHint): AstroComponentFactory {
|
function baseCreateComponent(
|
||||||
|
cb: AstroComponentFactory,
|
||||||
|
moduleId?: string,
|
||||||
|
propagation?: PropagationHint
|
||||||
|
): AstroComponentFactory {
|
||||||
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
||||||
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
||||||
if (!validateArgs(args)) {
|
if (!validateArgs(args)) {
|
||||||
|
@ -40,7 +44,7 @@ function createComponentWithOptions(opts: CreateComponentOptions) {
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
arg1: AstroComponentFactory | CreateComponentOptions,
|
arg1: AstroComponentFactory | CreateComponentOptions,
|
||||||
moduleId?: string,
|
moduleId?: string,
|
||||||
propagation?: PropagationHint,
|
propagation?: PropagationHint
|
||||||
) {
|
) {
|
||||||
if (typeof arg1 === 'function') {
|
if (typeof arg1 === 'function') {
|
||||||
return baseCreateComponent(arg1, moduleId, propagation);
|
return baseCreateComponent(arg1, moduleId, propagation);
|
||||||
|
|
|
@ -33,13 +33,13 @@ export {
|
||||||
stringifyChunk,
|
stringifyChunk,
|
||||||
voidElementNames,
|
voidElementNames,
|
||||||
} from './render/index.js';
|
} from './render/index.js';
|
||||||
export { renderTransition } from './transition.js';
|
|
||||||
export type {
|
export type {
|
||||||
AstroComponentFactory,
|
AstroComponentFactory,
|
||||||
AstroComponentInstance,
|
AstroComponentInstance,
|
||||||
ComponentSlots,
|
ComponentSlots,
|
||||||
RenderInstruction,
|
RenderInstruction,
|
||||||
} from './render/index.js';
|
} from './render/index.js';
|
||||||
|
export { renderTransition } from './transition.js';
|
||||||
|
|
||||||
import { markHTMLString } from './escape.js';
|
import { markHTMLString } from './escape.js';
|
||||||
import { addAttribute, Renderer } from './render/index.js';
|
import { addAttribute, Renderer } from './render/index.js';
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import type {
|
import type {
|
||||||
SSRResult,
|
SSRResult,
|
||||||
TransitionAnimation,
|
TransitionAnimation,
|
||||||
TransitionDirectionalAnimations,
|
|
||||||
TransitionAnimationValue,
|
TransitionAnimationValue,
|
||||||
|
TransitionDirectionalAnimations,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
|
import { fade, slide } from '../../transitions/index.js';
|
||||||
import { markHTMLString } from './escape.js';
|
import { markHTMLString } from './escape.js';
|
||||||
import { slide, fade } from '../../transitions/index.js';
|
|
||||||
|
|
||||||
const transitionNameMap = new WeakMap<SSRResult, number>();
|
const transitionNameMap = new WeakMap<SSRResult, number>();
|
||||||
function incrementTransitionNumber(result: SSRResult) {
|
function incrementTransitionNumber(result: SSRResult) {
|
||||||
let num = 1;
|
let num = 1;
|
||||||
if(transitionNameMap.has(result)) {
|
if (transitionNameMap.has(result)) {
|
||||||
num = transitionNameMap.get(result)! + 1;
|
num = transitionNameMap.get(result)! + 1;
|
||||||
}
|
}
|
||||||
transitionNameMap.set(result, num);
|
transitionNameMap.set(result, num);
|
||||||
|
@ -21,9 +21,14 @@ function createTransitionScope(result: SSRResult, hash: string) {
|
||||||
const num = incrementTransitionNumber(result);
|
const num = incrementTransitionNumber(result);
|
||||||
return `astro-${hash}-${num}`;
|
return `astro-${hash}-${num}`;
|
||||||
}
|
}
|
||||||
export function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string) {
|
export function renderTransition(
|
||||||
|
result: SSRResult,
|
||||||
|
hash: string,
|
||||||
|
animationName: TransitionAnimationValue | undefined,
|
||||||
|
transitionName: string
|
||||||
|
) {
|
||||||
let animations: TransitionDirectionalAnimations | null = null;
|
let animations: TransitionDirectionalAnimations | null = null;
|
||||||
switch(animationName) {
|
switch (animationName) {
|
||||||
case 'fade': {
|
case 'fade': {
|
||||||
animations = fade();
|
animations = fade();
|
||||||
break;
|
break;
|
||||||
|
@ -33,7 +38,7 @@ export function renderTransition(result: SSRResult, hash: string, animationName:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if(typeof animationName === 'object') {
|
if (typeof animationName === 'object') {
|
||||||
animations = animationName;
|
animations = animationName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,16 +47,18 @@ export function renderTransition(result: SSRResult, hash: string, animationName:
|
||||||
const scope = createTransitionScope(result, hash);
|
const scope = createTransitionScope(result, hash);
|
||||||
|
|
||||||
// Default transition name is the scope of the element, ie HASH-1
|
// Default transition name is the scope of the element, ie HASH-1
|
||||||
if(!transitionName) {
|
if (!transitionName) {
|
||||||
transitionName = scope;
|
transitionName = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = markHTMLString(`<style>[data-astro-transition-scope="${scope}"] {
|
const styles = markHTMLString(`<style>[data-astro-transition-scope="${scope}"] {
|
||||||
view-transition-name: ${transitionName};
|
view-transition-name: ${transitionName};
|
||||||
}
|
}
|
||||||
${!animations ? `` :
|
${
|
||||||
// Regular animations
|
!animations
|
||||||
`
|
? ``
|
||||||
|
: // Regular animations
|
||||||
|
`
|
||||||
::view-transition-old(${transitionName}) {
|
::view-transition-old(${transitionName}) {
|
||||||
${stringifyAnimation(animations.forwards.old)}
|
${stringifyAnimation(animations.forwards.old)}
|
||||||
}
|
}
|
||||||
|
@ -79,8 +86,9 @@ export function renderTransition(result: SSRResult, hash: string, animationName:
|
||||||
[data-astro-transition=back][data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
[data-astro-transition=back][data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
||||||
${stringifyAnimation(animations.backwards.new)}
|
${stringifyAnimation(animations.backwards.new)}
|
||||||
}
|
}
|
||||||
`.trim()}
|
`.trim()
|
||||||
</style>`)
|
}
|
||||||
|
</style>`);
|
||||||
|
|
||||||
result._metadata.extraHead.push(styles);
|
result._metadata.extraHead.push(styles);
|
||||||
|
|
||||||
|
@ -89,12 +97,12 @@ export function renderTransition(result: SSRResult, hash: string, animationName:
|
||||||
|
|
||||||
type AnimationBuilder = {
|
type AnimationBuilder = {
|
||||||
toString(): string;
|
toString(): string;
|
||||||
[key: string]: string[] | ((k: string) => string);
|
[key: string]: string[] | ((k: string) => string);
|
||||||
}
|
};
|
||||||
|
|
||||||
function addAnimationProperty(builder: AnimationBuilder, prop: string, value: string | number) {
|
function addAnimationProperty(builder: AnimationBuilder, prop: string, value: string | number) {
|
||||||
let arr = builder[prop];
|
let arr = builder[prop];
|
||||||
if(Array.isArray(arr)) {
|
if (Array.isArray(arr)) {
|
||||||
arr.push(value.toString());
|
arr.push(value.toString());
|
||||||
} else {
|
} else {
|
||||||
builder[prop] = [value.toString()];
|
builder[prop] = [value.toString()];
|
||||||
|
@ -105,19 +113,19 @@ function animationBuilder(): AnimationBuilder {
|
||||||
return {
|
return {
|
||||||
toString() {
|
toString() {
|
||||||
let out = '';
|
let out = '';
|
||||||
for(let k in this) {
|
for (let k in this) {
|
||||||
let value = this[k];
|
let value = this[k];
|
||||||
if(Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
out += `\n\t${k}: ${value.join(', ')};`
|
out += `\n\t${k}: ${value.join(', ')};`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]): string {
|
function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]): string {
|
||||||
if(Array.isArray(anim)) {
|
if (Array.isArray(anim)) {
|
||||||
return stringifyAnimations(anim);
|
return stringifyAnimations(anim);
|
||||||
} else {
|
} else {
|
||||||
return stringifyAnimations([anim]);
|
return stringifyAnimations([anim]);
|
||||||
|
@ -127,26 +135,26 @@ function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]):
|
||||||
function stringifyAnimations(anims: TransitionAnimation[]): string {
|
function stringifyAnimations(anims: TransitionAnimation[]): string {
|
||||||
const builder = animationBuilder();
|
const builder = animationBuilder();
|
||||||
|
|
||||||
for(const anim of anims) {
|
for (const anim of anims) {
|
||||||
/*300ms cubic-bezier(0.4, 0, 0.2, 1) both astroSlideFromRight;*/
|
/*300ms cubic-bezier(0.4, 0, 0.2, 1) both astroSlideFromRight;*/
|
||||||
if(anim.duration) {
|
if (anim.duration) {
|
||||||
addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration));
|
addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration));
|
||||||
}
|
}
|
||||||
if(anim.easing) {
|
if (anim.easing) {
|
||||||
addAnimationProperty(builder, 'animation-timing-function', anim.easing);
|
addAnimationProperty(builder, 'animation-timing-function', anim.easing);
|
||||||
}
|
}
|
||||||
if(anim.direction) {
|
if (anim.direction) {
|
||||||
addAnimationProperty(builder, 'animation-direction', anim.direction);
|
addAnimationProperty(builder, 'animation-direction', anim.direction);
|
||||||
}
|
}
|
||||||
if(anim.delay) {
|
if (anim.delay) {
|
||||||
addAnimationProperty(builder, 'animation-delay', anim.delay);
|
addAnimationProperty(builder, 'animation-delay', anim.delay);
|
||||||
}
|
}
|
||||||
if(anim.fillMode) {
|
if (anim.fillMode) {
|
||||||
addAnimationProperty(builder, 'animation-fill-mode', anim.fillMode);
|
addAnimationProperty(builder, 'animation-fill-mode', anim.fillMode);
|
||||||
}
|
}
|
||||||
addAnimationProperty(builder, 'animation-name', anim.name);
|
addAnimationProperty(builder, 'animation-name', anim.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,51 @@
|
||||||
import type { TransitionDirectionalAnimations, TransitionAnimationPair } from '../@types/astro';
|
import type { TransitionDirectionalAnimations } from '../@types/astro';
|
||||||
|
|
||||||
export function slide({
|
export function slide({
|
||||||
duration
|
duration,
|
||||||
}: {
|
}: {
|
||||||
duration?: string | number;
|
duration?: string | number;
|
||||||
} = {}): TransitionDirectionalAnimations {
|
} = {}): TransitionDirectionalAnimations {
|
||||||
return {
|
return {
|
||||||
forwards: {
|
forwards: {
|
||||||
old: [{
|
old: [
|
||||||
name: 'astroFadeOut',
|
{
|
||||||
duration: duration ?? '90ms',
|
name: 'astroFadeOut',
|
||||||
easing: 'cubic-bezier(0.4, 0, 1, 1)',
|
duration: duration ?? '90ms',
|
||||||
fillMode: 'both'
|
easing: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||||
}, {
|
fillMode: 'both',
|
||||||
name: 'astroSlideToLeft',
|
},
|
||||||
duration: duration ?? '300ms',
|
{
|
||||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
name: 'astroSlideToLeft',
|
||||||
fillMode: 'both'
|
duration: duration ?? '300ms',
|
||||||
}],
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
new: [{
|
fillMode: 'both',
|
||||||
name: 'astroFadeIn',
|
},
|
||||||
duration: duration ?? '210ms',
|
],
|
||||||
easing: 'cubic-bezier(0, 0, 0.2, 1)',
|
new: [
|
||||||
delay: '90ms',
|
{
|
||||||
fillMode: 'both'
|
name: 'astroFadeIn',
|
||||||
}, {
|
duration: duration ?? '210ms',
|
||||||
name: 'astroSlideFromRight',
|
easing: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||||
duration: duration ?? '300ms',
|
delay: '90ms',
|
||||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
fillMode: 'both',
|
||||||
fillMode: 'both'
|
},
|
||||||
}]
|
{
|
||||||
|
name: 'astroSlideFromRight',
|
||||||
|
duration: duration ?? '300ms',
|
||||||
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
fillMode: 'both',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
backwards: {
|
backwards: {
|
||||||
old: [{ name: 'astroFadeOut' }, { name: 'astroSlideToRight' }],
|
old: [{ name: 'astroFadeOut' }, { name: 'astroSlideToRight' }],
|
||||||
new: [{ name: 'astroFadeIn' }, { name: 'astroSlideFromLeft' }]
|
new: [{ name: 'astroFadeIn' }, { name: 'astroSlideFromLeft' }],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fade({
|
export function fade({
|
||||||
duration
|
duration,
|
||||||
}: {
|
}: {
|
||||||
duration?: string | number;
|
duration?: string | number;
|
||||||
} = {}): TransitionDirectionalAnimations {
|
} = {}): TransitionDirectionalAnimations {
|
||||||
|
@ -55,7 +61,7 @@ export function fade({
|
||||||
duration: duration ?? '0.3s',
|
duration: duration ?? '0.3s',
|
||||||
easing: 'linear',
|
easing: 'linear',
|
||||||
fillMode: 'backwards',
|
fillMode: 'backwards',
|
||||||
}
|
},
|
||||||
} satisfies TransitionAnimationPair;
|
} satisfies TransitionAnimationPair;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import type { AstroConfig } from '../@types/astro';
|
|
||||||
import * as vite from 'vite';
|
import * as vite from 'vite';
|
||||||
|
import type { AstroConfig } from '../@types/astro';
|
||||||
import { AstroError } from '../core/errors/index.js';
|
import { AstroError } from '../core/errors/index.js';
|
||||||
|
|
||||||
const virtualModuleId = 'astro:transitions';
|
const virtualModuleId = 'astro:transitions';
|
||||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||||
|
|
||||||
// The virtual module for the astro:transitions namespace
|
// The virtual module for the astro:transitions namespace
|
||||||
export default function astroTransitions({ config }: { config: AstroConfig; }): vite.Plugin {
|
export default function astroTransitions({ config }: { config: AstroConfig }): vite.Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'astro:transitions',
|
name: 'astro:transitions',
|
||||||
async resolveId(id) {
|
async resolveId(id) {
|
||||||
|
@ -16,7 +16,7 @@ export default function astroTransitions({ config }: { config: AstroConfig; }):
|
||||||
},
|
},
|
||||||
load(id) {
|
load(id) {
|
||||||
if (id === resolvedVirtualModuleId) {
|
if (id === resolvedVirtualModuleId) {
|
||||||
if(!config.experimental.viewTransitions) {
|
if (!config.experimental.viewTransitions) {
|
||||||
throw new AstroError({
|
throw new AstroError({
|
||||||
title: 'Experimental View Transitions not enabled',
|
title: 'Experimental View Transitions not enabled',
|
||||||
message: `View Transitions support is experimental. To enable update your config to include:
|
message: `View Transitions support is experimental. To enable update your config to include:
|
||||||
|
@ -25,7 +25,7 @@ export default defineConfig({
|
||||||
experimental: {
|
experimental: {
|
||||||
viewTransitions: true
|
viewTransitions: true
|
||||||
}
|
}
|
||||||
})`
|
})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe('astro/src/core/compile', () => {
|
||||||
await cachedCompilation({
|
await cachedCompilation({
|
||||||
astroConfig: {
|
astroConfig: {
|
||||||
root: pathToFileURL('/'),
|
root: pathToFileURL('/'),
|
||||||
experimental: {}
|
experimental: {},
|
||||||
},
|
},
|
||||||
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
|
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
|
||||||
filename: '/src/pages/index.astro',
|
filename: '/src/pages/index.astro',
|
||||||
|
|
Loading…
Reference in a new issue