@ecopages/react-router 0.2.0-alpha.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -5
- package/package.json +3 -3
- package/src/head-morpher.d.ts +6 -2
- package/src/head-morpher.js +67 -7
- package/src/router.js +15 -1
- package/browser.ts +0 -17
- package/src/adapter.ts +0 -48
- package/src/context.ts +0 -25
- package/src/head-morpher.ts +0 -214
- package/src/index.ts +0 -21
- package/src/manage-scroll.ts +0 -47
- package/src/navigation.ts +0 -297
- package/src/props-script.ts +0 -19
- package/src/router.ts +0 -670
- package/src/scroll-persist.ts +0 -96
- package/src/types.ts +0 -64
- package/src/view-transition-manager.ts +0 -30
- package/src/view-transition-utils.ts +0 -95
package/src/scroll-persist.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scroll position persistence for elements marked with `data-eco-persist="scroll"`.
|
|
3
|
-
*
|
|
4
|
-
* Handles two navigation scenarios:
|
|
5
|
-
* - **Forward navigation**: Preserves current scroll positions for shared layout elements
|
|
6
|
-
* - **Back navigation**: Restores positions from when the page was last visited
|
|
7
|
-
*
|
|
8
|
-
* @module scroll-persist
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const PERSIST_SELECTOR = '[data-eco-persist="scroll"]';
|
|
12
|
-
|
|
13
|
-
type ScrollPosition = { top: number; left: number };
|
|
14
|
-
type ScrollMap = Map<string, ScrollPosition>;
|
|
15
|
-
type UrlScrollStore = Map<string, ScrollMap>;
|
|
16
|
-
|
|
17
|
-
const urlScrollStore: UrlScrollStore = new Map();
|
|
18
|
-
let currentScrollSnapshot: ScrollMap | null = null;
|
|
19
|
-
|
|
20
|
-
function getElementKey(el: Element): string | null {
|
|
21
|
-
return el.id || el.getAttribute('data-testid') || null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function captureScrollPositions(): ScrollMap {
|
|
25
|
-
const positions = new Map<string, ScrollPosition>();
|
|
26
|
-
document.querySelectorAll(PERSIST_SELECTOR).forEach((el) => {
|
|
27
|
-
const key = getElementKey(el);
|
|
28
|
-
if (key) {
|
|
29
|
-
positions.set(key, { top: el.scrollTop, left: el.scrollLeft });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
return positions;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Captures and stores scroll positions before navigation.
|
|
37
|
-
*
|
|
38
|
-
* Saves positions to both:
|
|
39
|
-
* - URL-keyed store (for back navigation restoration)
|
|
40
|
-
* - Current snapshot (for forward navigation preservation)
|
|
41
|
-
*/
|
|
42
|
-
export function saveScrollPositions(): void {
|
|
43
|
-
const url = `${window.location.pathname}${window.location.search}`;
|
|
44
|
-
const positions = captureScrollPositions();
|
|
45
|
-
|
|
46
|
-
if (positions.size > 0) {
|
|
47
|
-
urlScrollStore.set(url, positions);
|
|
48
|
-
}
|
|
49
|
-
currentScrollSnapshot = positions;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Restores scroll positions after React render completes.
|
|
54
|
-
*
|
|
55
|
-
* Uses double `requestAnimationFrame` to ensure DOM is fully painted.
|
|
56
|
-
*
|
|
57
|
-
* @param targetUrl - The URL being navigated to
|
|
58
|
-
* @param isPopState - True for back/forward navigation, false for link clicks
|
|
59
|
-
*/
|
|
60
|
-
export function restoreScrollPositions(targetUrl: string, isPopState: boolean): void {
|
|
61
|
-
const positions = isPopState ? urlScrollStore.get(targetUrl) : currentScrollSnapshot;
|
|
62
|
-
|
|
63
|
-
if (!positions || positions.size === 0) {
|
|
64
|
-
currentScrollSnapshot = null;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
requestAnimationFrame(() => {
|
|
69
|
-
requestAnimationFrame(() => {
|
|
70
|
-
document.querySelectorAll(PERSIST_SELECTOR).forEach((el) => {
|
|
71
|
-
const key = getElementKey(el);
|
|
72
|
-
if (key && positions.has(key)) {
|
|
73
|
-
const pos = positions.get(key)!;
|
|
74
|
-
el.scrollTop = pos.top;
|
|
75
|
-
el.scrollLeft = pos.left;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
currentScrollSnapshot = null;
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Returns stored scroll positions for a URL without applying them.
|
|
85
|
-
*/
|
|
86
|
-
export function getScrollPositions(url: string): ScrollMap | undefined {
|
|
87
|
-
return urlScrollStore.get(url);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clears all stored scroll positions.
|
|
92
|
-
*/
|
|
93
|
-
export function clearScrollPositions(): void {
|
|
94
|
-
urlScrollStore.clear();
|
|
95
|
-
currentScrollSnapshot = null;
|
|
96
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration options for the EcoRouter
|
|
3
|
-
*/
|
|
4
|
-
export interface EcoRouterOptions {
|
|
5
|
-
/**
|
|
6
|
-
* CSS selector for links to intercept
|
|
7
|
-
* @default 'a[href]'
|
|
8
|
-
*/
|
|
9
|
-
linkSelector?: string;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Attribute that forces a full page reload when present on a link
|
|
13
|
-
* @default 'data-eco-reload'
|
|
14
|
-
*/
|
|
15
|
-
reloadAttribute?: string;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Whether to use the View Transitions API for page animations.
|
|
19
|
-
* When enabled and supported, page transitions animate using CSS view-transition pseudo-elements.
|
|
20
|
-
* Falls back to instant swap if not supported by the browser.
|
|
21
|
-
* @default true
|
|
22
|
-
*/
|
|
23
|
-
viewTransitions?: boolean;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Enable debug logging to console
|
|
27
|
-
* @default false
|
|
28
|
-
*/
|
|
29
|
-
debug?: boolean;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Scroll behavior after navigation:
|
|
33
|
-
* - 'top': Always scroll to top (default)
|
|
34
|
-
* - 'preserve': Keep current scroll position
|
|
35
|
-
* - 'auto': Scroll to top only when pathname changes
|
|
36
|
-
*/
|
|
37
|
-
scrollBehavior?: 'top' | 'preserve' | 'auto';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Whether to use smooth scrolling during navigation.
|
|
41
|
-
* If true, uses 'smooth' behavior. If false, uses 'instant' behavior.
|
|
42
|
-
* @default false
|
|
43
|
-
*/
|
|
44
|
-
smoothScroll?: boolean;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Keep layouts mounted between navigations.
|
|
48
|
-
* When enabled, if consecutive pages share the same layout component,
|
|
49
|
-
* the layout stays mounted and only the page content re-renders.
|
|
50
|
-
* This preserves layout state including scroll positions.
|
|
51
|
-
* @default true
|
|
52
|
-
*/
|
|
53
|
-
persistLayouts?: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const DEFAULT_OPTIONS: Required<EcoRouterOptions> = {
|
|
57
|
-
linkSelector: 'a[href]',
|
|
58
|
-
reloadAttribute: 'data-eco-reload',
|
|
59
|
-
viewTransitions: true,
|
|
60
|
-
debug: false,
|
|
61
|
-
scrollBehavior: 'top',
|
|
62
|
-
smoothScroll: false,
|
|
63
|
-
persistLayouts: true,
|
|
64
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Manages View Transition API integration
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
type ViewTransitionDocument = Document & {
|
|
7
|
-
startViewTransition: (callback: () => void | Promise<void>) => {
|
|
8
|
-
finished: Promise<void>;
|
|
9
|
-
ready: Promise<void>;
|
|
10
|
-
updateCallbackDone: Promise<void>;
|
|
11
|
-
skipTransition(): void;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function isViewTransitionSupported(): boolean {
|
|
16
|
-
return 'startViewTransition' in document;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function withViewTransition(callback: () => void): Promise<void> {
|
|
20
|
-
if (!isViewTransitionSupported()) {
|
|
21
|
-
callback();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const transition = (document as ViewTransitionDocument).startViewTransition(() => {
|
|
26
|
-
callback();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
await transition.finished;
|
|
30
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* View transition utilities for applying transition names from data attributes.
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const VIEW_TRANSITION_ATTR = 'data-view-transition';
|
|
7
|
-
const VIEW_TRANSITION_ANIMATE_ATTR = 'data-view-transition-animate';
|
|
8
|
-
const VIEW_TRANSITION_DURATION_ATTR = 'data-view-transition-duration';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Applies view-transition-name CSS property to elements with data-view-transition attribute.
|
|
12
|
-
* Also handles data-view-transition-animate="morph" (default) logic and custom duration.
|
|
13
|
-
*/
|
|
14
|
-
export function applyViewTransitionNames(): void {
|
|
15
|
-
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
16
|
-
const morphNames: string[] = [];
|
|
17
|
-
const customDurations: { name: string; duration: string }[] = [];
|
|
18
|
-
|
|
19
|
-
elements.forEach((el) => {
|
|
20
|
-
const name = el.getAttribute(VIEW_TRANSITION_ATTR);
|
|
21
|
-
if (name) {
|
|
22
|
-
(el as HTMLElement).style.viewTransitionName = name;
|
|
23
|
-
/**
|
|
24
|
-
* By default, we apply a clean geometric morph (no cross-fade/ghosting).
|
|
25
|
-
* The 'fade' value is reserved for opting out of this behavior.
|
|
26
|
-
*/
|
|
27
|
-
const animate = el.getAttribute(VIEW_TRANSITION_ANIMATE_ATTR);
|
|
28
|
-
if (animate !== 'fade') {
|
|
29
|
-
morphNames.push(name);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const duration = el.getAttribute(VIEW_TRANSITION_DURATION_ATTR);
|
|
33
|
-
if (duration) {
|
|
34
|
-
customDurations.push({ name, duration });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (morphNames.length > 0 || customDurations.length > 0) {
|
|
40
|
-
injectDynamicStyles(morphNames, customDurations);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Injects dynamic CSS to hide old snapshots and apply custom durations.
|
|
46
|
-
*/
|
|
47
|
-
function injectDynamicStyles(morphNames: string[], customDurations: { name: string; duration: string }[]) {
|
|
48
|
-
let styleEl = document.getElementById('eco-vt-dynamic-styles');
|
|
49
|
-
if (!styleEl) {
|
|
50
|
-
styleEl = document.createElement('style');
|
|
51
|
-
styleEl.id = 'eco-vt-dynamic-styles';
|
|
52
|
-
/**
|
|
53
|
-
* Persistence is required to prevent the head-morpher from removing this style tag during navigation.
|
|
54
|
-
*/
|
|
55
|
-
styleEl.setAttribute('data-eco-persist', '');
|
|
56
|
-
document.head.appendChild(styleEl);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const morphCss = morphNames
|
|
60
|
-
.map(
|
|
61
|
-
(name) => `
|
|
62
|
-
::view-transition-old(${name}) { display: none !important; }
|
|
63
|
-
::view-transition-new(${name}) { animation: none !important; opacity: 1 !important; }
|
|
64
|
-
`,
|
|
65
|
-
)
|
|
66
|
-
.join('\n');
|
|
67
|
-
|
|
68
|
-
const durationCss = customDurations
|
|
69
|
-
.map(
|
|
70
|
-
({ name, duration }) => `
|
|
71
|
-
::view-transition-group(${name}) { animation-duration: ${duration} !important; }
|
|
72
|
-
`,
|
|
73
|
-
)
|
|
74
|
-
.join('\n');
|
|
75
|
-
|
|
76
|
-
styleEl.textContent = morphCss + '\n' + durationCss;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Clears view-transition-name CSS property from all elements.
|
|
81
|
-
*/
|
|
82
|
-
export function clearViewTransitionNames(): void {
|
|
83
|
-
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
84
|
-
elements.forEach((el) => {
|
|
85
|
-
(el as HTMLElement).style.viewTransitionName = '';
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Cleanup dynamic styles to ensure a clean slate for the next transition.
|
|
90
|
-
*/
|
|
91
|
-
const styleEl = document.getElementById('eco-vt-dynamic-styles');
|
|
92
|
-
if (styleEl) {
|
|
93
|
-
styleEl.textContent = '';
|
|
94
|
-
}
|
|
95
|
-
}
|