Skip to content

Commit

Permalink
prepare for 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
martrapp committed Sep 5, 2024
1 parent b365aad commit 91344ea
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 175 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 🚸 ElementCrossing

Transfer selected element states across cross-document view transitions.

![Build Status](https://github.com/vtbag/element-crossing/actions/workflows/run-tests.yml/badge.svg)
Expand All @@ -13,4 +14,20 @@ First official release of this code!

## What is it?

This library provides a robust solution for maintaining HTML elements and their associated state across cross-document view transitions.
Boost the smoothness of your cross-document view transitions with the Element Crossing library! No more resetting to static states! This library preserves the current dynamic state of your DOM and CSS properties, ensuring a seamless user experience.

With Element Crossing, you can automatically retain:

- Current form inputs
- Current scrollbar positions
- Current state of active CSS animations
- Current media playback time
- Current toggle states
- Current values of dynamically added classes and CSS properties
- ...and (current) anything else you'd like to carry over from the previous page to the new one!

Simply annotate your elements in the HTML source or DOM, and let the library handle the rest. Keep your users engaged by preserving the exact state they left off, making transitions across documents smoother than ever!

Address any CSS property or DOM element property, any CSS class, or CSS animation.

[View configuration examples](https://vtbag.pages.dev/tools/element-crossing/#applications-with-real-world-examples) and [see the Element Crossing in action](https://vtbag.pages.dev/crossing/vanilla/1/)
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
],
"exports": {
".": "./lib/vanilla.js",
"./vanilla": "./lib/vanilla.js",
"./over-the-top": "./lib/over-the-top.js"
"./experimental": "./lib/over-the-top.js"
},
"scripts": {
"dev": "bin/bundle dev",
Expand Down
139 changes: 45 additions & 94 deletions src/over-the-top.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,54 @@
import { Spec } from './types';

top!.__vtbag ??= {};
top!.__vtbag.elementCrossing ??= {};
const elementCrossing = top!.__vtbag.elementCrossing!;

console.log('[elc]', 'init');
if (top === self) {
initBorderLands();
} else if (self.parent === top) {
initHeartLand();
} else {
console.log('[elc]', 'neither BorderLands nor HeartLand');
}
const crossing = (top!.__vtbag.elementCrossing ??= {
storageMap: new Map<string, Element>(),
addrWeakMap: new WeakMap<any, string>(),
fun: {
pseudoAddress(object: object): string {
let addr = '0x000000';
if (object === null || object === undefined) return addr;
while (addr === '0x000000') {
addr =
crossing.addrWeakMap.get(object) ??
'0x' + Math.random().toString(16).slice(2, 8).toUpperCase();
}
crossing.addrWeakMap.set(object, addr);
return addr;
},
setItem(id: string, element: Element) {
crossing.storageMap!.set(id, element);
},
getItem(id: string): Element | undefined {
const element = crossing.storageMap!.get(id);
return element;
},
removeItem(id: string): void {
crossing.storageMap!.delete(id);
},
clear(): void {
crossing.storageMap!.clear();
},
},
});

top === self && initBorderLands();

function initBorderLands() {
console.log('[elc]', 'init BorderLands');
addEventListener('pagereveal', () => {
console.log('[elc]', 'DOMContentLoaded');
top!.addEventListener('pagereveal', () => {
const topDoc = top!.document;
const root = topDoc.documentElement;
root.innerHTML = `<body style="margin:0; overflow=clip"><iframe width=${innerWidth} height=${innerHeight} src="${location.href}"/>`;
const lang = topDoc.documentElement.lang;
const colorScheme = topDoc.documentElement.style.colorScheme;
const root = topDoc.createElement('html');
root.innerHTML = `<body style="margin:0; overflow=clip"><iframe width=${innerWidth} height=${innerHeight} style="border:0" src="${location.href}"/>`;
crossing.iframe = root.querySelector<HTMLIFrameElement>('iframe')!;
root.lang = lang;
root.style.overflow = 'clip';
root.style.colorScheme = colorScheme;
topDoc.documentElement.replaceWith(root);
crossing.frameDocument = topDoc.querySelector<HTMLIFrameElement>('iframe')!.contentDocument!;
});
}

function initHeartLand() {
console.log('[elc]', 'init HeartLand');
self.addEventListener('pageswap', pageSwap, { once: true });
self.addEventListener('pagereveal', pageReveal, { once: true });
}

function pageSwap() {
console.log('[elc]', 'pageSwap');

self.document.querySelectorAll<HTMLElement>('[data-vtbag-x]').forEach((el) => {
let id;
const specs: Spec[] = [];
el
.getAttribute('data-vtbag-x')
?.split(' ')
.forEach((value) => {
const [kind, key] = kindAndKey(value);
switch (kind) {
case 'id':
id = key;
break;
case 'class':
specs.push({ kind, key, value: el.classList.contains(key) ? 'true' : 'false' });
break;
case 'style':
specs.push({
kind,
key,
value: '' + (el.style[key as keyof CSSStyleDeclaration] ?? ''),
});
break;
case 'attr':
specs.push({ kind, key, value: el.getAttribute(key) ?? '' });
break;
default:
console.error('[crossing]', 'unknown kind', kind);
break;
}
});
top!.addEventListener('resize', () => {
crossing.iframe!.width = '' + top!.innerWidth;
crossing.iframe!.height = '' + top!.innerHeight;
});
}

function pageReveal() {
console.log('[elc]', 'pageReveal');
const topDoc = top!.document;
topDoc.title = document.title;
top!.history.replaceState({}, '', location.href);
}

function kindAndKey(value: string) {
let [kind, key] = value.split(':');
if (key === undefined) {
key = kind.slice(1);
switch (kind[0]) {
case '#':
kind = 'id';
break;
case '.':
kind = 'class';
break;
case '@':
kind = 'attr';
break;
case '-':
kind = 'style';
break;
default:
console.error(
'[crossing]',
'syntax error:',
value,
'is not recognized as a valid specification'
);
}
}
return [kind, key];
}
17 changes: 15 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
export type ElementCrossing = {
frameDocument?: Document | undefined;
export type CrossingStorage = {
pseudoAddress(object: object): string;
setItem(id: string, object: any): void;
getItem(id: string): any | undefined;
removeItem(id: string): void;
clear(): void;
};

type ElementCrossing = {
addrWeakMap: WeakMap<any, string>;
storageMap?: Map<string, any>;
frameDocument?: Document;
fun: CrossingStorage;
iframe?: HTMLIFrameElement;
};

declare global {
interface Window {
__vtbag: {
elementCrossing?: ElementCrossing | undefined;
};
crossingStorage: ElementCrossing['fun'];
}
}

Expand Down
Loading

0 comments on commit 91344ea

Please sign in to comment.