aboutsummaryrefslogtreecommitdiff
path: root/packages/astro/src/transitions/events.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/src/transitions/events.ts')
-rw-r--r--packages/astro/src/transitions/events.ts186
1 files changed, 186 insertions, 0 deletions
diff --git a/packages/astro/src/transitions/events.ts b/packages/astro/src/transitions/events.ts
new file mode 100644
index 000000000..39abd46bb
--- /dev/null
+++ b/packages/astro/src/transitions/events.ts
@@ -0,0 +1,186 @@
+import { updateScrollPosition } from './router.js';
+import { swap } from './swap-functions.js';
+import type { Direction, NavigationTypeString } from './types.js';
+
+export const TRANSITION_BEFORE_PREPARATION = 'astro:before-preparation';
+export const TRANSITION_AFTER_PREPARATION = 'astro:after-preparation';
+export const TRANSITION_BEFORE_SWAP = 'astro:before-swap';
+export const TRANSITION_AFTER_SWAP = 'astro:after-swap';
+export const TRANSITION_PAGE_LOAD = 'astro:page-load';
+
+type Events =
+ | typeof TRANSITION_AFTER_PREPARATION
+ | typeof TRANSITION_AFTER_SWAP
+ | typeof TRANSITION_PAGE_LOAD;
+export const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
+export const onPageLoad = () => triggerEvent(TRANSITION_PAGE_LOAD);
+
+/*
+ * Common stuff
+ */
+class BeforeEvent extends Event {
+ readonly from: URL;
+ to: URL;
+ direction: Direction | string;
+ readonly navigationType: NavigationTypeString;
+ readonly sourceElement: Element | undefined;
+ readonly info: any;
+ newDocument: Document;
+ readonly signal: AbortSignal;
+
+ constructor(
+ type: string,
+ eventInitDict: EventInit | undefined,
+ from: URL,
+ to: URL,
+ direction: Direction | string,
+ navigationType: NavigationTypeString,
+ sourceElement: Element | undefined,
+ info: any,
+ newDocument: Document,
+ signal: AbortSignal,
+ ) {
+ super(type, eventInitDict);
+ this.from = from;
+ this.to = to;
+ this.direction = direction;
+ this.navigationType = navigationType;
+ this.sourceElement = sourceElement;
+ this.info = info;
+ this.newDocument = newDocument;
+ this.signal = signal;
+
+ Object.defineProperties(this, {
+ from: { enumerable: true },
+ to: { enumerable: true, writable: true },
+ direction: { enumerable: true, writable: true },
+ navigationType: { enumerable: true },
+ sourceElement: { enumerable: true },
+ info: { enumerable: true },
+ newDocument: { enumerable: true, writable: true },
+ signal: { enumerable: true },
+ });
+ }
+}
+
+/*
+ * TransitionBeforePreparationEvent
+
+ */
+export const isTransitionBeforePreparationEvent = (
+ value: any,
+): value is TransitionBeforePreparationEvent => value.type === TRANSITION_BEFORE_PREPARATION;
+export class TransitionBeforePreparationEvent extends BeforeEvent {
+ formData: FormData | undefined;
+ loader: () => Promise<void>;
+ constructor(
+ from: URL,
+ to: URL,
+ direction: Direction | string,
+ navigationType: NavigationTypeString,
+ sourceElement: Element | undefined,
+ info: any,
+ newDocument: Document,
+ signal: AbortSignal,
+ formData: FormData | undefined,
+ loader: (event: TransitionBeforePreparationEvent) => Promise<void>,
+ ) {
+ super(
+ TRANSITION_BEFORE_PREPARATION,
+ { cancelable: true },
+ from,
+ to,
+ direction,
+ navigationType,
+ sourceElement,
+ info,
+ newDocument,
+ signal,
+ );
+ this.formData = formData;
+ this.loader = loader.bind(this, this);
+ Object.defineProperties(this, {
+ formData: { enumerable: true },
+ loader: { enumerable: true, writable: true },
+ });
+ }
+}
+
+/*
+ * TransitionBeforeSwapEvent
+ */
+
+export const isTransitionBeforeSwapEvent = (value: any): value is TransitionBeforeSwapEvent =>
+ value.type === TRANSITION_BEFORE_SWAP;
+export class TransitionBeforeSwapEvent extends BeforeEvent {
+ readonly direction: Direction | string;
+ readonly viewTransition: ViewTransition;
+ swap: () => void;
+
+ constructor(afterPreparation: BeforeEvent, viewTransition: ViewTransition) {
+ super(
+ TRANSITION_BEFORE_SWAP,
+ undefined,
+ afterPreparation.from,
+ afterPreparation.to,
+ afterPreparation.direction,
+ afterPreparation.navigationType,
+ afterPreparation.sourceElement,
+ afterPreparation.info,
+ afterPreparation.newDocument,
+ afterPreparation.signal,
+ );
+ this.direction = afterPreparation.direction;
+ this.viewTransition = viewTransition;
+ this.swap = () => swap(this.newDocument);
+
+ Object.defineProperties(this, {
+ direction: { enumerable: true },
+ viewTransition: { enumerable: true },
+ swap: { enumerable: true, writable: true },
+ });
+ }
+}
+
+export async function doPreparation(
+ from: URL,
+ to: URL,
+ direction: Direction | string,
+ navigationType: NavigationTypeString,
+ sourceElement: Element | undefined,
+ info: any,
+ signal: AbortSignal,
+ formData: FormData | undefined,
+ defaultLoader: (event: TransitionBeforePreparationEvent) => Promise<void>,
+) {
+ const event = new TransitionBeforePreparationEvent(
+ from,
+ to,
+ direction,
+ navigationType,
+ sourceElement,
+ info,
+ window.document,
+ signal,
+ formData,
+ defaultLoader,
+ );
+ if (document.dispatchEvent(event)) {
+ await event.loader();
+ if (!event.defaultPrevented) {
+ triggerEvent(TRANSITION_AFTER_PREPARATION);
+ if (event.navigationType !== 'traverse') {
+ // save the current scroll position before we change the DOM and transition to the new page
+ updateScrollPosition({ scrollX, scrollY });
+ }
+ }
+ }
+ return event;
+}
+
+export function doSwap(afterPreparation: BeforeEvent, viewTransition: ViewTransition) {
+ const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition);
+ document.dispatchEvent(event);
+ event.swap();
+ return event;
+}