+ Page 1
+ This is the content for page 1.
+ Why not check out page 2?
+
+
+
diff --git a/examples/mini-program-example/src/pages/transition/page-2.html b/examples/mini-program-example/src/pages/transition/page-2.html
new file mode 100644
index 000000000000..30dfa0add0d6
--- /dev/null
+++ b/examples/mini-program-example/src/pages/transition/page-2.html
@@ -0,0 +1,32 @@
+
+
+
+
+ Page 2
+ This is the content for page 2.
+
+ - It
+ - also
+ - has
+ - a
+ - list!
+
+ Ok, that's enough fun, you can go back to page 1.
+
+
+
diff --git a/examples/mini-program-example/src/pages/transition/script.js b/examples/mini-program-example/src/pages/transition/script.js
new file mode 100644
index 000000000000..11dc97c9ebb1
--- /dev/null
+++ b/examples/mini-program-example/src/pages/transition/script.js
@@ -0,0 +1,25 @@
+import { getPageContent, onLinkNavigate } from './utils.js';
+
+onLinkNavigate(async ({ toPath }) => {
+ const content = await getPageContent(toPath);
+
+ startViewTransition(() => {
+ // This is a pretty heavy-handed way to update page content.
+ // In production, you'd likely be modifying DOM elements directly,
+ // or using a framework.
+ // innerHTML is used here just to keep the DOM update super simple.
+ document.body.innerHTML = content;
+ });
+});
+
+
+// A little helper function like this is really handy
+// to handle progressive enhancement.
+function startViewTransition(callback) {
+ if (!document.startViewTransition) {
+ callback();
+ return;
+ }
+
+ document.startViewTransition(callback);
+}
diff --git a/examples/mini-program-example/src/pages/transition/styles.css b/examples/mini-program-example/src/pages/transition/styles.css
new file mode 100644
index 000000000000..9b7834881ee7
--- /dev/null
+++ b/examples/mini-program-example/src/pages/transition/styles.css
@@ -0,0 +1,25 @@
+@keyframes fade-in {
+ from { opacity: 0; }
+}
+
+@keyframes fade-out {
+ to { opacity: 0; }
+}
+
+@keyframes slide-from-right {
+ from { transform: translateX(30px); }
+}
+
+@keyframes slide-to-left {
+ to { transform: translateX(-30px); }
+}
+
+::view-transition-old(root) {
+ animation: 900ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
+ 3000ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
+}
+
+::view-transition-new(root) {
+ animation: 2100ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
+ 3000ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
+}
diff --git a/examples/mini-program-example/src/pages/transition/utils.js b/examples/mini-program-example/src/pages/transition/utils.js
new file mode 100644
index 000000000000..ff26046f1030
--- /dev/null
+++ b/examples/mini-program-example/src/pages/transition/utils.js
@@ -0,0 +1,87 @@
+export async function getPageContent(url) {
+ // This is a really scrappy way to do this.
+ // Don't do this in production!
+ const response = await fetch(url);
+ const text = await response.text();
+ // Particularly as it uses regexp
+ return /]*>([\w\W]*)<\/body>/.exec(text)[1];
+}
+
+function isBackNavigation(navigateEvent) {
+ if (navigateEvent.navigationType === 'push' || navigateEvent.navigationType === 'replace') {
+ return false;
+ }
+ if (
+ navigateEvent.destination.index !== -1 &&
+ navigateEvent.destination.index < navigation.currentEntry.index
+ ) {
+ return true;
+ }
+ return false;
+}
+
+// Intercept navigations
+// https://developer.chrome.com/docs/web-platform/navigation-api/
+// This is a naive usage of the navigation API, to keep things simple.
+export async function onLinkNavigate(callback) {
+ navigation.addEventListener('navigate', (event) => {
+ const toUrl = new URL(event.destination.url);
+
+ if (location.origin !== toUrl.origin) return;
+
+ const fromPath = location.pathname;
+ const isBack = isBackNavigation(event);
+
+ event.intercept({
+ async handler() {
+ if (event.info === 'ignore') return;
+
+ await callback({
+ toPath: toUrl.pathname,
+ fromPath,
+ isBack,
+ });
+ },
+ });
+ });
+}
+
+export function getLink(href) {
+ const fullLink = new URL(href, location.href).href;
+
+ return [...document.querySelectorAll('a')].find((link) =>
+ link.href === fullLink
+ );
+}
+
+// This helper function returns a View-Transition-like object, even for browsers that don't support view transitions.
+// It won't do the transition in unsupported browsers, it'll act as if the transition is skipped.
+// It also makes it easier to add class names to the document element.
+export function transitionHelper({
+ skipTransition = false,
+ classNames = '',
+ updateDOM,
+ }) {
+ if (skipTransition || !document.startViewTransition) {
+ const updateCallbackDone = Promise.resolve(updateDOM()).then(() => undefined);
+
+ return {
+ ready: Promise.reject(Error('View transitions unsupported')),
+ domUpdated: updateCallbackDone,
+ updateCallbackDone,
+ finished: updateCallbackDone,
+ };
+ }
+
+ const classNamesArray = classNames.split(/\s+/g).filter(Boolean);
+
+ document.documentElement.classList.add(...classNamesArray);
+
+ const transition = document.startViewTransition(updateDOM);
+
+ transition.finished.finally(() =>
+ document.documentElement.classList.remove(...classNamesArray)
+ );
+
+ return transition;
+}
diff --git a/examples/mini-program-example/tsconfig.json b/examples/mini-program-example/tsconfig.json
new file mode 100644
index 000000000000..efe4ad4abdab
--- /dev/null
+++ b/examples/mini-program-example/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "module": "commonjs",
+ "removeComments": false,
+ "preserveConstEnums": true,
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "noImplicitAny": false,
+ "allowSyntheticDefaultImports": true,
+ "outDir": "lib",
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "strictNullChecks": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "rootDir": ".",
+ "jsx": "react-jsx",
+ "allowJs": true,
+ "resolveJsonModule": true,
+ "typeRoots": ["node_modules/@types"]
+ },
+ "include": ["./src", "./types"],
+ "compileOnSave": false
+}
diff --git a/examples/mini-program-example/types/global.d.ts b/examples/mini-program-example/types/global.d.ts
new file mode 100644
index 000000000000..36087b094c99
--- /dev/null
+++ b/examples/mini-program-example/types/global.d.ts
@@ -0,0 +1,19 @@
+///