@ecopages/browser-router 0.2.0-alpha.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +132 -0
  4. package/package.json +39 -0
  5. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Initialization-should-create-router-instance-1.png +0 -0
  6. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Initialization-should-start-and-stop-without-errors-1.png +0 -0
  7. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Lifecycle-Events-should-dispatch-eco-before-swap--eco-after-swap--and-eco-page-load-events-1.png +0 -0
  8. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Lifecycle-Events-should-dispatch-eco-page-load-event-after-animation-frame-1.png +0 -0
  9. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Lifecycle-Events-should-provide-event-details-with-url-and-direction-1.png +0 -0
  10. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Custom-Link-Selector-should-work-with-data-attribute-selector-1.png +0 -0
  11. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Custom-Reload-Attribute-should-intercept-links-with-default-reload-attribute-when-custom-is-set-1.png +0 -0
  12. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Custom-link-selector-should-only-intercept-links-matching-custom-selector-1.png +0 -0
  13. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-External-Links--should-NOT-intercept--should-NOT-intercept-external-links-1.png +0 -0
  14. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Internal-Links-should-intercept-clicks-on-relative-path-links-1.png +0 -0
  15. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Internal-Links-should-intercept-clicks-on-same-origin-absolute-URLs-1.png +0 -0
  16. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Link-Attributes--should-NOT-intercept--should-NOT-intercept-links-with-download-attribute-1.png +0 -0
  17. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Link-Attributes--should-NOT-intercept--should-intercept-links-with-target---self--1.png +0 -0
  18. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-alt-click-1.png +0 -0
  19. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-ctrl-click-1.png +0 -0
  20. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-meta-click--cmd-on-Mac--1.png +0 -0
  21. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-middle-mouse-button-click-1.png +0 -0
  22. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-right-mouse-button-click-1.png +0 -0
  23. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Modifier-keys-should-NOT-intercept-shift-click-1.png +0 -0
  24. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-external-links--different-origin--1.png +0 -0
  25. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-hash-only-links-1.png +0 -0
  26. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-javascript--links-1.png +0 -0
  27. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-custom-reload-attribute-1.png +0 -0
  28. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-data-eco-reload-attribute-1.png +0 -0
  29. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-download-attribute-1.png +0 -0
  30. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-empty-href-1.png +0 -0
  31. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-target---blank--1.png +0 -0
  32. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-with-target---parent--1.png +0 -0
  33. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-NOT-intercept-links-without-href-attribute-1.png +0 -0
  34. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-intercept-internal-links-with-absolute-same-origin-paths-1.png +0 -0
  35. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-intercept-internal-links-with-relative-paths-1.png +0 -0
  36. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-Should-intercept-nested-elements-inside-links-1.png +0 -0
  37. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Link-Interception-should-NOT-intercept-external-links-1.png +0 -0
  38. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Navigation-Abort-should-abort-previous-navigation-when-new-one-starts-1.png +0 -0
  39. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Programmatic-Navigation-should-navigate-and-update-history-with-pushState-1.png +0 -0
  40. package/src/client/__screenshots__/eco-router.test.browser.ts/EcoRouter-Programmatic-Navigation-should-use-replaceState-when-replace-option-is-true-1.png +0 -0
  41. package/src/client/__screenshots__/eco-router.test.ts/EcoRouter-Error-Handling-should-fall-back-to-full-page-navigation-on-fetch-error-1.png +0 -0
  42. package/src/client/__screenshots__/eco-router.test.ts/EcoRouter-Error-Handling-should-log-error-and-attempt-fallback-navigation-on-fetch-error-1.png +0 -0
  43. package/src/client/eco-router.d.ts +98 -0
  44. package/src/client/eco-router.js +228 -0
  45. package/src/client/eco-router.ts +290 -0
  46. package/src/client/services/dom-swapper.d.ts +65 -0
  47. package/src/client/services/dom-swapper.js +237 -0
  48. package/src/client/services/dom-swapper.ts +325 -0
  49. package/src/client/services/index.d.ts +8 -0
  50. package/src/client/services/index.js +10 -0
  51. package/src/client/services/index.ts +9 -0
  52. package/src/client/services/prefetch-manager.d.ts +169 -0
  53. package/src/client/services/prefetch-manager.js +374 -0
  54. package/src/client/services/prefetch-manager.ts +451 -0
  55. package/src/client/services/scroll-manager.d.ts +19 -0
  56. package/src/client/services/scroll-manager.js +36 -0
  57. package/src/client/services/scroll-manager.ts +48 -0
  58. package/src/client/services/view-transition-manager.d.ts +23 -0
  59. package/src/client/services/view-transition-manager.js +38 -0
  60. package/src/client/services/view-transition-manager.ts +75 -0
  61. package/src/client/types.d.ts +84 -0
  62. package/src/client/types.js +19 -0
  63. package/src/client/types.ts +109 -0
  64. package/src/client/view-transition-utils.d.ts +14 -0
  65. package/src/client/view-transition-utils.js +60 -0
  66. package/src/client/view-transition-utils.ts +98 -0
  67. package/src/index.d.ts +9 -0
  68. package/src/index.js +11 -0
  69. package/src/index.ts +19 -0
  70. package/src/styles.css +218 -0
  71. package/src/types.d.ts +15 -0
  72. package/src/types.js +4 -0
  73. package/src/types.ts +19 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared types for the EcoPages transitions package
3
+ * @module
4
+ */
5
+ /**
6
+ * Prefetch configuration options.
7
+ */
8
+ export interface PrefetchConfig {
9
+ /**
10
+ * Prefetching strategy:
11
+ * - 'viewport': Prefetch when links enter viewport
12
+ * - 'hover': Prefetch on hover/focus intent
13
+ * - 'intent': Viewport + prioritize hover (recommended)
14
+ * @default 'intent'
15
+ */
16
+ strategy?: 'viewport' | 'hover' | 'intent';
17
+ /**
18
+ * Hover intent delay in ms before triggering prefetch.
19
+ * @default 65
20
+ */
21
+ delay?: number;
22
+ /**
23
+ * Attribute to disable prefetch on specific links.
24
+ * @default 'data-eco-no-prefetch'
25
+ */
26
+ noPrefetchAttribute?: string;
27
+ /**
28
+ * Respect navigator.connection.saveData and slow connections.
29
+ * @default true
30
+ */
31
+ respectDataSaver?: boolean;
32
+ }
33
+ /**
34
+ * Configuration options for the EcoRouter
35
+ */
36
+ export interface EcoRouterOptions {
37
+ /** Selector for links to intercept. @default 'a[href]' */
38
+ linkSelector?: string;
39
+ /** Attribute to mark elements for DOM persistence. @default 'data-eco-persist' */
40
+ persistAttribute?: string;
41
+ /** Attribute to force full page reload. @default 'data-eco-reload' */
42
+ reloadAttribute?: string;
43
+ /** Whether to update browser history. @default true */
44
+ updateHistory?: boolean;
45
+ /**
46
+ * Scroll behavior after navigation:
47
+ * - 'top': Always scroll to top (default)
48
+ * - 'preserve': Keep current scroll position
49
+ * - 'auto': Scroll to top only when pathname changes
50
+ */
51
+ scrollBehavior?: 'top' | 'preserve' | 'auto';
52
+ /**
53
+ * Whether to use the View Transition API for animations.
54
+ * Falls back to instant swap if not supported.
55
+ * @default true
56
+ */
57
+ viewTransitions?: boolean;
58
+ /**
59
+ * Whether to use smooth scrolling during navigation.
60
+ * If true, uses 'smooth' behavior. If false, uses 'instant' behavior.
61
+ * @default false
62
+ */
63
+ smoothScroll?: boolean;
64
+ /**
65
+ * Prefetch configuration. Set to false to disable prefetching entirely.
66
+ * @default { strategy: 'intent', delay: 65, noPrefetchAttribute: 'data-eco-no-prefetch', respectDataSaver: true }
67
+ */
68
+ prefetch?: PrefetchConfig | false;
69
+ }
70
+ /** Events emitted during the navigation lifecycle */
71
+ export interface EcoNavigationEvent {
72
+ url: URL;
73
+ direction: 'forward' | 'back' | 'replace';
74
+ }
75
+ /** Event fired before the DOM swap occurs */
76
+ export interface EcoBeforeSwapEvent extends EcoNavigationEvent {
77
+ newDocument: Document;
78
+ reload: () => void;
79
+ }
80
+ /** Event fired after the DOM swap completes */
81
+ export interface EcoAfterSwapEvent extends EcoNavigationEvent {
82
+ }
83
+ /** Default configuration options */
84
+ export declare const DEFAULT_OPTIONS: Required<EcoRouterOptions>;
@@ -0,0 +1,19 @@
1
+ const DEFAULT_PREFETCH_CONFIG = {
2
+ strategy: "intent",
3
+ delay: 65,
4
+ noPrefetchAttribute: "data-eco-no-prefetch",
5
+ respectDataSaver: true
6
+ };
7
+ const DEFAULT_OPTIONS = {
8
+ linkSelector: "a[href]",
9
+ persistAttribute: "data-eco-persist",
10
+ reloadAttribute: "data-eco-reload",
11
+ updateHistory: true,
12
+ scrollBehavior: "top",
13
+ viewTransitions: true,
14
+ smoothScroll: false,
15
+ prefetch: DEFAULT_PREFETCH_CONFIG
16
+ };
17
+ export {
18
+ DEFAULT_OPTIONS
19
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared types for the EcoPages transitions package
3
+ * @module
4
+ */
5
+
6
+ /**
7
+ * Prefetch configuration options.
8
+ */
9
+ export interface PrefetchConfig {
10
+ /**
11
+ * Prefetching strategy:
12
+ * - 'viewport': Prefetch when links enter viewport
13
+ * - 'hover': Prefetch on hover/focus intent
14
+ * - 'intent': Viewport + prioritize hover (recommended)
15
+ * @default 'intent'
16
+ */
17
+ strategy?: 'viewport' | 'hover' | 'intent';
18
+
19
+ /**
20
+ * Hover intent delay in ms before triggering prefetch.
21
+ * @default 65
22
+ */
23
+ delay?: number;
24
+
25
+ /**
26
+ * Attribute to disable prefetch on specific links.
27
+ * @default 'data-eco-no-prefetch'
28
+ */
29
+ noPrefetchAttribute?: string;
30
+
31
+ /**
32
+ * Respect navigator.connection.saveData and slow connections.
33
+ * @default true
34
+ */
35
+ respectDataSaver?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Configuration options for the EcoRouter
40
+ */
41
+ export interface EcoRouterOptions {
42
+ /** Selector for links to intercept. @default 'a[href]' */
43
+ linkSelector?: string;
44
+ /** Attribute to mark elements for DOM persistence. @default 'data-eco-persist' */
45
+ persistAttribute?: string;
46
+ /** Attribute to force full page reload. @default 'data-eco-reload' */
47
+ reloadAttribute?: string;
48
+ /** Whether to update browser history. @default true */
49
+ updateHistory?: boolean;
50
+ /**
51
+ * Scroll behavior after navigation:
52
+ * - 'top': Always scroll to top (default)
53
+ * - 'preserve': Keep current scroll position
54
+ * - 'auto': Scroll to top only when pathname changes
55
+ */
56
+ scrollBehavior?: 'top' | 'preserve' | 'auto';
57
+ /**
58
+ * Whether to use the View Transition API for animations.
59
+ * Falls back to instant swap if not supported.
60
+ * @default true
61
+ */
62
+ viewTransitions?: boolean;
63
+ /**
64
+ * Whether to use smooth scrolling during navigation.
65
+ * If true, uses 'smooth' behavior. If false, uses 'instant' behavior.
66
+ * @default false
67
+ */
68
+ smoothScroll?: boolean;
69
+ /**
70
+ * Prefetch configuration. Set to false to disable prefetching entirely.
71
+ * @default { strategy: 'intent', delay: 65, noPrefetchAttribute: 'data-eco-no-prefetch', respectDataSaver: true }
72
+ */
73
+ prefetch?: PrefetchConfig | false;
74
+ }
75
+
76
+ /** Events emitted during the navigation lifecycle */
77
+ export interface EcoNavigationEvent {
78
+ url: URL;
79
+ direction: 'forward' | 'back' | 'replace';
80
+ }
81
+
82
+ /** Event fired before the DOM swap occurs */
83
+ export interface EcoBeforeSwapEvent extends EcoNavigationEvent {
84
+ newDocument: Document;
85
+ reload: () => void;
86
+ }
87
+
88
+ /** Event fired after the DOM swap completes */
89
+ export interface EcoAfterSwapEvent extends EcoNavigationEvent {}
90
+
91
+ /** Default prefetch configuration */
92
+ const DEFAULT_PREFETCH_CONFIG: Required<PrefetchConfig> = {
93
+ strategy: 'intent',
94
+ delay: 65,
95
+ noPrefetchAttribute: 'data-eco-no-prefetch',
96
+ respectDataSaver: true,
97
+ };
98
+
99
+ /** Default configuration options */
100
+ export const DEFAULT_OPTIONS: Required<EcoRouterOptions> = {
101
+ linkSelector: 'a[href]',
102
+ persistAttribute: 'data-eco-persist',
103
+ reloadAttribute: 'data-eco-reload',
104
+ updateHistory: true,
105
+ scrollBehavior: 'top',
106
+ viewTransitions: true,
107
+ smoothScroll: false,
108
+ prefetch: DEFAULT_PREFETCH_CONFIG,
109
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * View transition utilities for applying transition names from data attributes.
3
+ * @module
4
+ */
5
+ /**
6
+ * Applies view-transition-name CSS property to elements with data-view-transition attribute.
7
+ * By default, it also injects styles to prevent "ghosting" (sets animation: none) for clean morphing,
8
+ * unless data-view-transition-animate="fade" is present.
9
+ */
10
+ export declare function applyViewTransitionNames(): void;
11
+ /**
12
+ * Clears view-transition-name CSS property from all elements.
13
+ */
14
+ export declare function clearViewTransitionNames(): void;
@@ -0,0 +1,60 @@
1
+ const VIEW_TRANSITION_ATTR = "data-view-transition";
2
+ const VIEW_TRANSITION_ANIMATE_ATTR = "data-view-transition-animate";
3
+ const VIEW_TRANSITION_DURATION_ATTR = "data-view-transition-duration";
4
+ function applyViewTransitionNames() {
5
+ const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
6
+ const morphNames = [];
7
+ const customDurations = [];
8
+ elements.forEach((el) => {
9
+ const name = el.getAttribute(VIEW_TRANSITION_ATTR);
10
+ if (name) {
11
+ el.style.viewTransitionName = name;
12
+ const animate = el.getAttribute(VIEW_TRANSITION_ANIMATE_ATTR);
13
+ if (animate !== "fade") {
14
+ morphNames.push(name);
15
+ }
16
+ const duration = el.getAttribute(VIEW_TRANSITION_DURATION_ATTR);
17
+ if (duration) {
18
+ customDurations.push({ name, duration });
19
+ }
20
+ }
21
+ });
22
+ if (morphNames.length > 0 || customDurations.length > 0) {
23
+ injectDynamicStyles(morphNames, customDurations);
24
+ }
25
+ }
26
+ function injectDynamicStyles(morphNames, customDurations) {
27
+ let styleEl = document.getElementById("eco-vt-dynamic-styles");
28
+ if (!styleEl) {
29
+ styleEl = document.createElement("style");
30
+ styleEl.id = "eco-vt-dynamic-styles";
31
+ styleEl.setAttribute("data-eco-persist", "");
32
+ document.head.appendChild(styleEl);
33
+ }
34
+ const morphCss = morphNames.map(
35
+ (name) => `
36
+ ::view-transition-old(${name}) { display: none !important; }
37
+ ::view-transition-new(${name}) { animation: none !important; opacity: 1 !important; }
38
+ `
39
+ ).join("\n");
40
+ const durationCss = customDurations.map(
41
+ ({ name, duration }) => `
42
+ ::view-transition-group(${name}) { animation-duration: ${duration} !important; }
43
+ `
44
+ ).join("\n");
45
+ styleEl.textContent = morphCss + "\n" + durationCss;
46
+ }
47
+ function clearViewTransitionNames() {
48
+ const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
49
+ elements.forEach((el) => {
50
+ el.style.viewTransitionName = "";
51
+ });
52
+ const styleEl = document.getElementById("eco-vt-dynamic-styles");
53
+ if (styleEl) {
54
+ styleEl.textContent = "";
55
+ }
56
+ }
57
+ export {
58
+ applyViewTransitionNames,
59
+ clearViewTransitionNames
60
+ };
@@ -0,0 +1,98 @@
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
+ * By default, it also injects styles to prevent "ghosting" (sets animation: none) for clean morphing,
13
+ * unless data-view-transition-animate="fade" is present.
14
+ */
15
+ export function applyViewTransitionNames(): void {
16
+ const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
17
+ const morphNames: string[] = [];
18
+ const customDurations: { name: string; duration: string }[] = [];
19
+
20
+ elements.forEach((el) => {
21
+ const name = el.getAttribute(VIEW_TRANSITION_ATTR);
22
+ if (name) {
23
+ (el as HTMLElement).style.viewTransitionName = name;
24
+
25
+ /**
26
+ * By default, we apply a clean geometric morph (no cross-fade/ghosting).
27
+ * The 'fade' value is reserved for opting out of this behavior.
28
+ */
29
+ const animate = el.getAttribute(VIEW_TRANSITION_ANIMATE_ATTR);
30
+ if (animate !== 'fade') {
31
+ morphNames.push(name);
32
+ }
33
+
34
+ const duration = el.getAttribute(VIEW_TRANSITION_DURATION_ATTR);
35
+ if (duration) {
36
+ customDurations.push({ name, duration });
37
+ }
38
+ }
39
+ });
40
+
41
+ if (morphNames.length > 0 || customDurations.length > 0) {
42
+ injectDynamicStyles(morphNames, customDurations);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Injects dynamic CSS to hide old snapshots and apply custom durations.
48
+ */
49
+ function injectDynamicStyles(morphNames: string[], customDurations: { name: string; duration: string }[]) {
50
+ let styleEl = document.getElementById('eco-vt-dynamic-styles');
51
+ if (!styleEl) {
52
+ styleEl = document.createElement('style');
53
+ styleEl.id = 'eco-vt-dynamic-styles';
54
+ /**
55
+ * Persistence is required to prevent the head-morpher from removing this style tag during navigation.
56
+ * @see {@link DomSwapper}
57
+ */
58
+ styleEl.setAttribute('data-eco-persist', '');
59
+ document.head.appendChild(styleEl);
60
+ }
61
+
62
+ const morphCss = morphNames
63
+ .map(
64
+ (name) => `
65
+ ::view-transition-old(${name}) { display: none !important; }
66
+ ::view-transition-new(${name}) { animation: none !important; opacity: 1 !important; }
67
+ `,
68
+ )
69
+ .join('\n');
70
+
71
+ const durationCss = customDurations
72
+ .map(
73
+ ({ name, duration }) => `
74
+ ::view-transition-group(${name}) { animation-duration: ${duration} !important; }
75
+ `,
76
+ )
77
+ .join('\n');
78
+
79
+ styleEl.textContent = morphCss + '\n' + durationCss;
80
+ }
81
+
82
+ /**
83
+ * Clears view-transition-name CSS property from all elements.
84
+ */
85
+ export function clearViewTransitionNames(): void {
86
+ const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
87
+ elements.forEach((el) => {
88
+ (el as HTMLElement).style.viewTransitionName = '';
89
+ });
90
+
91
+ /**
92
+ * Cleanup dynamic styles to ensure a clean slate for the next transition.
93
+ */
94
+ const styleEl = document.getElementById('eco-vt-dynamic-styles');
95
+ if (styleEl) {
96
+ styleEl.textContent = '';
97
+ }
98
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @ecopages/browser-router
3
+ * Client-side navigation and view transitions for Ecopages
4
+ * @module
5
+ */
6
+ export type { EcoRouterOptions, EcoNavigationEvent, EcoBeforeSwapEvent, EcoAfterSwapEvent, EcoRouterEventMap, } from './types.js';
7
+ export { DEFAULT_OPTIONS } from './types.js';
8
+ export { EcoRouter, createRouter } from './client/eco-router.js';
9
+ export { DomSwapper, ScrollManager, ViewTransitionManager } from './client/services/index.js';
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { DEFAULT_OPTIONS } from "./types.js";
2
+ import { EcoRouter, createRouter } from "./client/eco-router.js";
3
+ import { DomSwapper, ScrollManager, ViewTransitionManager } from "./client/services/index.js";
4
+ export {
5
+ DEFAULT_OPTIONS,
6
+ DomSwapper,
7
+ EcoRouter,
8
+ ScrollManager,
9
+ ViewTransitionManager,
10
+ createRouter
11
+ };
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @ecopages/browser-router
3
+ * Client-side navigation and view transitions for Ecopages
4
+ * @module
5
+ */
6
+
7
+ export type {
8
+ EcoRouterOptions,
9
+ EcoNavigationEvent,
10
+ EcoBeforeSwapEvent,
11
+ EcoAfterSwapEvent,
12
+ EcoRouterEventMap,
13
+ } from './types.ts';
14
+
15
+ export { DEFAULT_OPTIONS } from './types.ts';
16
+
17
+ export { EcoRouter, createRouter } from './client/eco-router.ts';
18
+
19
+ export { DomSwapper, ScrollManager, ViewTransitionManager } from './client/services/index.ts';
package/src/styles.css ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Default View Transition CSS for EcoPages transitions
3
+ *
4
+ * NOTE: This file is not exported from the package.
5
+ * JSR does not support CSS exports yet: https://github.com/jsr-io/jsr/issues/293
6
+ *
7
+ * Use the ecopages npm package instead:
8
+ * @import 'ecopages/css/view-transitions.css';
9
+ */
10
+
11
+ /* Disable root animation to prevent full page flash during transitions */
12
+ ::view-transition-old(root),
13
+ ::view-transition-new(root) {
14
+ animation: none;
15
+ }
16
+
17
+ /* Shared element transitions animate position/size smoothly */
18
+ ::view-transition-group(*) {
19
+ animation-duration: 180ms;
20
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
21
+ }
22
+
23
+ /* Prevent transitioning elements from affecting layout of non-transitioning elements */
24
+ ::view-transition-image-pair(*) {
25
+ isolation: isolate;
26
+ }
27
+
28
+ /* Respect reduced motion preference */
29
+ @media (prefers-reduced-motion: reduce) {
30
+ ::view-transition-group(*),
31
+ ::view-transition-old(*),
32
+ ::view-transition-new(*) {
33
+ animation: none !important;
34
+ }
35
+ }
36
+
37
+ @keyframes eco-fade-out {
38
+ from {
39
+ opacity: 1;
40
+ }
41
+ to {
42
+ opacity: 0;
43
+ }
44
+ }
45
+
46
+ @keyframes eco-fade-in {
47
+ from {
48
+ opacity: 0;
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ }
53
+ }
54
+
55
+ /* Slide transition for elements with view-transition-name: eco-slide */
56
+ ::view-transition-image-pair(eco-slide) {
57
+ isolation: isolate;
58
+ }
59
+
60
+ ::view-transition-old(eco-slide),
61
+ ::view-transition-new(eco-slide) {
62
+ animation-duration: 180ms;
63
+ animation-timing-function: ease-out;
64
+ mix-blend-mode: normal;
65
+ }
66
+
67
+ ::view-transition-old(eco-slide) {
68
+ animation-name: eco-slide-out;
69
+ }
70
+
71
+ ::view-transition-new(eco-slide) {
72
+ animation-name: eco-slide-in;
73
+ }
74
+
75
+ @keyframes eco-slide-out {
76
+ from {
77
+ opacity: 1;
78
+ transform: translateX(0);
79
+ }
80
+ to {
81
+ opacity: 0;
82
+ transform: translateX(-30px);
83
+ }
84
+ }
85
+
86
+ @keyframes eco-slide-in {
87
+ from {
88
+ opacity: 0;
89
+ transform: translateX(30px);
90
+ }
91
+ to {
92
+ opacity: 1;
93
+ transform: translateX(0);
94
+ }
95
+ }
96
+
97
+ /* Persistent elements should not animate */
98
+ [data-eco-persist] {
99
+ view-transition-name: none;
100
+ }
101
+
102
+ /* Declarative transition attributes */
103
+ [data-eco-transition='slide'] {
104
+ view-transition-name: eco-slide;
105
+ }
106
+ [data-eco-transition='fade'] {
107
+ view-transition-name: eco-fade;
108
+ }
109
+ [data-eco-transition='zoom'] {
110
+ view-transition-name: eco-zoom;
111
+ }
112
+ [data-eco-transition='slide-up'] {
113
+ view-transition-name: eco-slide-up;
114
+ }
115
+ [data-eco-transition='slide-down'] {
116
+ view-transition-name: eco-slide-down;
117
+ }
118
+
119
+ /* FADE */
120
+ ::view-transition-old(eco-fade) {
121
+ animation: eco-fade-out 180ms ease-out both;
122
+ }
123
+ ::view-transition-new(eco-fade) {
124
+ animation: eco-fade-in 180ms ease-out both;
125
+ }
126
+
127
+ /* ZOOM */
128
+ ::view-transition-image-pair(eco-zoom) {
129
+ isolation: isolate;
130
+ }
131
+ ::view-transition-old(eco-zoom) {
132
+ animation: eco-zoom-out 180ms ease-in both;
133
+ }
134
+ ::view-transition-new(eco-zoom) {
135
+ animation: eco-zoom-in 180ms ease-out both;
136
+ }
137
+ @keyframes eco-zoom-out {
138
+ from {
139
+ opacity: 1;
140
+ transform: scale(1);
141
+ }
142
+ to {
143
+ opacity: 0;
144
+ transform: scale(0.9);
145
+ }
146
+ }
147
+ @keyframes eco-zoom-in {
148
+ from {
149
+ opacity: 0;
150
+ transform: scale(0.9);
151
+ }
152
+ to {
153
+ opacity: 1;
154
+ transform: scale(1);
155
+ }
156
+ }
157
+
158
+ /* SLIDE UP */
159
+ ::view-transition-image-pair(eco-slide-up) {
160
+ isolation: isolate;
161
+ }
162
+ ::view-transition-old(eco-slide-up) {
163
+ animation: eco-slide-up-out 180ms ease-in both;
164
+ }
165
+ ::view-transition-new(eco-slide-up) {
166
+ animation: eco-slide-up-in 180ms ease-out both;
167
+ }
168
+ @keyframes eco-slide-up-out {
169
+ from {
170
+ opacity: 1;
171
+ transform: translateY(0);
172
+ }
173
+ to {
174
+ opacity: 0;
175
+ transform: translateY(-30px);
176
+ }
177
+ }
178
+ @keyframes eco-slide-up-in {
179
+ from {
180
+ opacity: 0;
181
+ transform: translateY(30px);
182
+ }
183
+ to {
184
+ opacity: 1;
185
+ transform: translateY(0);
186
+ }
187
+ }
188
+
189
+ /* SLIDE DOWN */
190
+ ::view-transition-image-pair(eco-slide-down) {
191
+ isolation: isolate;
192
+ }
193
+ ::view-transition-old(eco-slide-down) {
194
+ animation: eco-slide-down-out 180ms ease-in both;
195
+ }
196
+ ::view-transition-new(eco-slide-down) {
197
+ animation: eco-slide-down-in 180ms ease-out both;
198
+ }
199
+ @keyframes eco-slide-down-out {
200
+ from {
201
+ opacity: 1;
202
+ transform: translateY(0);
203
+ }
204
+ to {
205
+ opacity: 0;
206
+ transform: translateY(30px);
207
+ }
208
+ }
209
+ @keyframes eco-slide-down-in {
210
+ from {
211
+ opacity: 0;
212
+ transform: translateY(-30px);
213
+ }
214
+ to {
215
+ opacity: 1;
216
+ transform: translateY(0);
217
+ }
218
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Types for the @ecopages/browser-router package
3
+ * @module
4
+ */
5
+ import type { EcoNavigationEvent, EcoBeforeSwapEvent, EcoAfterSwapEvent } from './client/types';
6
+ export type { EcoRouterOptions, EcoNavigationEvent, EcoBeforeSwapEvent, EcoAfterSwapEvent } from './client/types';
7
+ export { DEFAULT_OPTIONS } from './client/types';
8
+ /**
9
+ * Custom event map for navigation lifecycle
10
+ */
11
+ export interface EcoRouterEventMap {
12
+ 'eco:before-swap': CustomEvent<EcoBeforeSwapEvent>;
13
+ 'eco:after-swap': CustomEvent<EcoAfterSwapEvent>;
14
+ 'eco:page-load': CustomEvent<EcoNavigationEvent>;
15
+ }
package/src/types.js ADDED
@@ -0,0 +1,4 @@
1
+ import { DEFAULT_OPTIONS } from "./client/types";
2
+ export {
3
+ DEFAULT_OPTIONS
4
+ };
package/src/types.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Types for the @ecopages/browser-router package
3
+ * @module
4
+ */
5
+
6
+ import type { EcoNavigationEvent, EcoBeforeSwapEvent, EcoAfterSwapEvent } from './client/types';
7
+
8
+ export type { EcoRouterOptions, EcoNavigationEvent, EcoBeforeSwapEvent, EcoAfterSwapEvent } from './client/types';
9
+
10
+ export { DEFAULT_OPTIONS } from './client/types';
11
+
12
+ /**
13
+ * Custom event map for navigation lifecycle
14
+ */
15
+ export interface EcoRouterEventMap {
16
+ 'eco:before-swap': CustomEvent<EcoBeforeSwapEvent>;
17
+ 'eco:after-swap': CustomEvent<EcoAfterSwapEvent>;
18
+ 'eco:page-load': CustomEvent<EcoNavigationEvent>;
19
+ }