@ecopages/browser-router 0.2.0-alpha.5 → 0.2.0-alpha.6

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 CHANGED
@@ -8,19 +8,21 @@ All notable changes to `@ecopages/browser-router` are documented here.
8
8
 
9
9
  ### Features
10
10
 
11
- - **Light-DOM custom element support in `dom-swapper`** `morphdom` morphing process now correctly handles light-DOM custom elements during page transitions (`fce8080c`).
12
- - **Improved `morphHead` script injection** New scripts from the incoming page's `<head>` are now injected and executed correctly during client-side navigation, even when not marked with `data-eco-rerun` (`08e15e99`).
13
- - **Global injector lifecycle management** — Enhanced global injector with structured lifecycle hooks and tests for hydration script handling (`2ba35aa4`).
11
+ - Added cross-router navigation handoff and document adoption hooks so browser-router can exchange control with other runtimes without forcing a reload.
12
+ - Improved head swaps to execute newly introduced scripts and preserve light-DOM custom elements during page transitions.
14
13
 
15
14
  ### Bug Fixes
16
15
 
17
- - Published npm package metadata now includes validated declaration exports for generated dist entrypoints.
18
- - Registered a current-page HMR refresh hook that invalidates cached HTML before re-fetching the active route.
16
+ - Fixed stale delegated navigations and rapid-click races so only the latest browser-router navigation can commit a swap.
17
+ - Re-executed `data-eco-rerun` scripts after body replacement and forced fresh module URLs for external rerun scripts so page bootstraps rebind correctly on every navigation.
18
+ - Synced rendered document ownership markers and live `<html>` attributes during swaps so mixed browser-router and React-router pages preserve the correct hydration owner.
19
+ - Prevented duplicate head-script execution, duplicate `/_hmr_runtime.js` injection, and listener accumulation across repeated navigations.
20
+ - Reset hydrated custom elements from incoming HTML and ignored superseded navigation fetch failures so cross-runtime handoffs no longer leave blank or mixed DOM state behind.
19
21
 
20
22
  ### Refactoring
21
23
 
22
- - Removed unused `@types/morphdom` dev dependency (`ceb243d0`).
24
+ - Routed handoff and current-page reload behavior through the shared navigation coordinator.
23
25
 
24
26
  ### Documentation
25
27
 
26
- - README updated with new API usage examples.
28
+ - Updated the README examples for the current router API.
package/README.md CHANGED
@@ -1,22 +1,25 @@
1
1
  # @ecopages/browser-router
2
2
 
3
- Client-side navigation and view transitions for Ecopages. Intercepts same-origin link clicks to provide smooth page transitions without full page reloads.
3
+ Client-side navigation and view transitions for Ecopages. It intercepts same-origin link clicks to provide smooth page transitions without full page reloads.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Client-side navigation** - Intercepts `<a>` clicks for fast navigation
8
- - **Efficient DOM diffing** - Uses [morphdom](https://github.com/patrick-steele-idem/morphdom) to update only what changed, preserving scroll positions and internal state
9
- - **State persistence** - Elements with `data-eco-persist` are never recreated, preserving internal state
10
- - **View Transitions** - Optional integration with the View Transition API
11
- - **Lifecycle events** - Hook into navigation with `eco:before-swap`, `eco:after-swap`, `eco:page-load`
7
+ - **Client-side navigation**: Intercepts `<a>` clicks for robust, fast navigation.
8
+ - **Efficient DOM diffing**: Uses [morphdom](https://github.com/patrick-steele-idem/morphdom) to update only what changed, preserving scroll positions and internal state.
9
+ - **State persistence**: Elements with `data-eco-persist` are never recreated, preserving Web Component state, event listeners, and form values.
10
+ - **View Transitions**: Optional integration with the View Transition API.
11
+ - **Lifecycle events**: Hook into navigation with `eco:before-swap`, `eco:after-swap`, `eco:page-load`.
12
12
 
13
13
  ## Compatibility
14
14
 
15
- This package works with MPA-style rendering (KitaJS, Lit, vanilla JS) where the server returns full HTML pages.
15
+ This package is designed for MPA-style rendering where the server returns full HTML pages (e.g., KitaJS, Lit, vanilla JS, or component-level React islands).
16
16
 
17
- **Not compatible with React/Preact** - These frameworks manage their own virtual DOM and component trees. Replacing the DOM breaks hydration, state, and event handlers. For React apps, use a framework-specific routing solution.
17
+ > [!WARNING]
18
+ > **Not compatible with full React applications.**
19
+ > If you are building a React application, use [@ecopages/react-router](../react-router/README.md) instead, as React manages its own virtual DOM and hydration lifecycle.
18
20
 
19
- Component-level islands are a narrower case: small interactive roots emitted by another integration (for example a React island inside an otherwise MPA-style page) can work with `@ecopages/browser-router`, because ownership stays scoped to the island root instead of the full document.
21
+ > [!NOTE]
22
+ > `@ecopages/browser-router` can still participate in mixed sites that contain both React-router pages and non-React pages. In that setup, browser-router remains responsible for DOM-swapping non-React documents, and React-router can hand off already-fetched non-React pages to it through the shared navigation coordinator.
20
23
 
21
24
  ## Installation
22
25
 
@@ -28,7 +31,8 @@ bunx jsr add @ecopages/browser-router
28
31
 
29
32
  Create and start the router in a **global** client-side script (e.g., `src/layouts/base-layout.script.ts`).
30
33
 
31
- > **Important**: Ensure the router script is injected in a **consistent order** within the `<head>` across all pages. Inconsistent ordering (e.g. script between styles on one page but after on another) causes `morphdom` to reload styles, leading to a "Flash of Unstyled Content" (FOUC).
34
+ > [!IMPORTANT]
35
+ > Ensure the router script is injected in a **consistent order** within the `<head>` across all pages. Inconsistent ordering causes `morphdom` to reload styles, leading to a "Flash of Unstyled Content" (FOUC).
32
36
 
33
37
  ```ts
34
38
  import { createRouter } from '@ecopages/browser-router/client';
@@ -48,51 +52,45 @@ const router = createRouter({
48
52
  });
49
53
  ```
50
54
 
55
+ Loading the router script is the opt-in point for browser-router-managed navigation on that page shell. Pages without the router script continue to use normal document navigation.
56
+
51
57
  ## Configuration
52
58
 
53
- | Option | Type | Default | Description |
54
- | :----------------- | :-----------------------------: | :------------------: | :--------------------------------------------- |
55
- | `linkSelector` | `string` | `'a[href]'` | Selector for links to intercept |
56
- | `persistAttribute` | `string` | `'data-eco-persist'` | Attribute to mark elements for DOM persistence |
57
- | `reloadAttribute` | `string` | `'data-eco-reload'` | Attribute to force full page reload |
58
- | `updateHistory` | `boolean` | `true` | Whether to update browser history |
59
- | `scrollBehavior` | `'top' \| 'preserve' \| 'auto'` | `'top'` | Scroll behavior after navigation |
60
- | `viewTransitions` | `boolean` | `false` | Use View Transition API for animations |
61
- | `smoothScroll` | `boolean` | `false` | Use smooth scrolling during navigation |
59
+ | Option | Type | Default | Description |
60
+ | :----------------- | :------------------------------ | :------------------- | :--------------------------------------------- |
61
+ | `linkSelector` | `string` | `'a[href]'` | Selector for links to intercept |
62
+ | `persistAttribute` | `string` | `'data-eco-persist'` | Attribute to mark elements for DOM persistence |
63
+ | `reloadAttribute` | `string` | `'data-eco-reload'` | Attribute to force full page reload |
64
+ | `updateHistory` | `boolean` | `true` | Whether to update browser history |
65
+ | `scrollBehavior` | `'top' \| 'preserve' \| 'auto'` | `'top'` | Scroll behavior after navigation |
66
+ | `viewTransitions` | `boolean` | `false` | Use View Transition API for animations |
67
+ | `smoothScroll` | `boolean` | `false` | Use smooth scrolling during navigation |
62
68
 
63
69
  ## Persistence
64
70
 
65
- Mark elements to preserve across navigations. These elements are never recreated during navigation, morphdom skips them entirely, preserving their internal state (event listeners, web component state, form values, etc.):
71
+ Mark elements to preserve across navigations. These elements are never recreated during navigation; morphdom skips them entirely.
66
72
 
67
73
  ```html
68
- <!-- This counter keeps its state across all navigations -->
74
+ <!-- This counter keeps its internal state across all navigations -->
69
75
  <radiant-counter data-eco-persist="counter"></radiant-counter>
70
76
  ```
71
77
 
72
78
  ## Script Re-execution
73
79
 
74
- To force a script to re-execute on every navigation (e.g. analytics, hydration), add `data-eco-rerun` and `data-eco-script-id`:
80
+ To force a script to re-execute on every navigation (e.g., analytics), add `data-eco-rerun`:
75
81
 
76
82
  ```html
77
- <script data-eco-rerun="true" data-eco-script-id="analytics">
78
- // This runs on every navigation
83
+ <script data-eco-rerun="true">
79
84
  trackPageview();
80
85
  </script>
81
86
  ```
82
87
 
83
- ### React islands with `@ecopages/browser-router`
84
-
85
- When `@ecopages/browser-router` is used in an MPA-style app that also renders component-level React islands:
86
-
87
- - island hydration scripts may need to run again after `eco:after-swap`
88
- - hydration bootstraps should carry stable `data-eco-script-id` metadata
89
- - `data-eco-rerun` allows those bootstraps to be re-executed safely during head reconciliation
90
-
91
- This note is specific to DOM-swapping navigation with `@ecopages/browser-router`. It does **not** apply to full React applications using [@ecopages/react-router](../react-router/README.md), where page routing and hydration are handled by the React router runtime itself.
88
+ > [!NOTE]
89
+ > `data-eco-script-id` is optional. Use it when you want an explicit stable identity for a rerun script. Otherwise the router falls back to matching by original `src` and inline content.
92
90
 
93
91
  ## Force Full Reload
94
92
 
95
- Use `data-eco-reload` to force a full page reload:
93
+ Use `data-eco-reload` on an anchor tag to bypass the router and force a full page reload:
96
94
 
97
95
  ```html
98
96
  <a href="/logout" data-eco-reload>Logout</a>
@@ -100,12 +98,12 @@ Use `data-eco-reload` to force a full page reload:
100
98
 
101
99
  ## Events
102
100
 
103
- Listen to navigation lifecycle events:
101
+ Listen to navigation lifecycle events on the `document`:
104
102
 
105
103
  ```ts
106
104
  document.addEventListener('eco:before-swap', (e) => {
107
105
  console.log('Navigating to:', e.detail.url);
108
- // Call e.detail.reload() to abort and do full reload
106
+ // Call e.detail.reload() to abort client-side navigation and do a full reload
109
107
  });
110
108
 
111
109
  document.addEventListener('eco:after-swap', (e) => {
@@ -119,6 +117,8 @@ document.addEventListener('eco:page-load', (e) => {
119
117
 
120
118
  ## Programmatic Navigation
121
119
 
120
+ Use the router instance to navigate programmatically:
121
+
122
122
  ```ts
123
123
  import { createRouter } from '@ecopages/browser-router/client';
124
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/browser-router",
3
- "version": "0.2.0-alpha.5",
3
+ "version": "0.2.0-alpha.6",
4
4
  "description": "Client-side router for Ecopages with view transitions support",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -32,7 +32,7 @@
32
32
  "directory": "packages/browser-router"
33
33
  },
34
34
  "dependencies": {
35
- "@ecopages/core": "0.2.0-alpha.5",
35
+ "@ecopages/core": "0.2.0-alpha.6",
36
36
  "morphdom": "^2.7.8"
37
37
  },
38
38
  "types": "./src/index.d.ts"
@@ -9,12 +9,36 @@ import type { EcoRouterOptions } from './types.js';
9
9
  */
10
10
  export declare class EcoRouter {
11
11
  private options;
12
- private abortController;
12
+ private unregisterNavigationRuntime;
13
+ private started;
14
+ private pendingNavigations;
15
+ private pendingPointerNavigation;
16
+ private pendingHoverNavigation;
17
+ private queuedNavigationHref;
13
18
  private domSwapper;
14
19
  private scrollManager;
15
20
  private viewTransitionManager;
16
21
  private prefetchManager;
17
22
  constructor(options?: EcoRouterOptions);
23
+ private getLinkFromEvent;
24
+ private canInterceptLink;
25
+ private getRecoveredPointerHref;
26
+ private getRecoveredHoverHref;
27
+ private isAnotherNavigationRuntimeActive;
28
+ private getDocumentOwner;
29
+ private adoptDocumentOwner;
30
+ private syncDocumentElementAttributes;
31
+ private reloadDocument;
32
+ /**
33
+ * Commits a fully fetched document into the live page.
34
+ *
35
+ * When browser-router accepts a handoff from another runtime, it delays source
36
+ * runtime cleanup until the incoming document has been prepared and is ready to
37
+ * commit. That ordering avoids the blank-page window we previously hit when a
38
+ * delegated navigation went stale after the source runtime had already torn
39
+ * itself down.
40
+ */
41
+ private commitDocumentNavigation;
18
42
  /**
19
43
  * Starts the router and begins intercepting navigation.
20
44
  *
@@ -51,6 +75,8 @@ export declare class EcoRouter {
51
75
  * Uses `event.composedPath()` to correctly detect clicks on anchors inside
52
76
  * Shadow DOM boundaries (Web Components).
53
77
  */
78
+ private handlePointerDown;
79
+ private handleHoverIntent;
54
80
  private handleClick;
55
81
  /**
56
82
  * Handles browser back/forward navigation.
@@ -62,6 +88,8 @@ export declare class EcoRouter {
62
88
  * Cross-origin navigation always falls back to full page reload.
63
89
  */
64
90
  private isSameOrigin;
91
+ private cancelNavigationTransaction;
92
+ private beginNavigationTransaction;
65
93
  /**
66
94
  * Executes the core navigation flow.
67
95
  *
@@ -91,6 +119,12 @@ export declare class EcoRouter {
91
119
  }
92
120
  /**
93
121
  * Creates and starts a router instance.
122
+ *
123
+ * Stops the previously active router (if any) before creating a new one so
124
+ * click listeners and coordinator registrations from earlier instances are
125
+ * cleaned up on re-execution (e.g. when the layout script is re-run via
126
+ * `data-eco-rerun` after a browser-router page commit).
127
+ *
94
128
  * @param options - Configuration options for the router
95
129
  * @returns A started EcoRouter instance
96
130
  */