lightweight-router 1.0.10 → 1.0.14
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/README.md +50 -0
- package/dist/router.min.js +1 -1
- package/package.json +2 -2
- package/src/about.html +74 -12
- package/src/index.html +104 -11
- package/src/router.js +58 -10
- package/src/router.html +0 -25
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ PageFlick is a minimal lightweight client-side router with intelligent prefetchi
|
|
|
13
13
|
- 🎨 Built-in loading animations
|
|
14
14
|
- 🕰️ Based on History API so you can use native browser navigation
|
|
15
15
|
- 🤖 Automatic title change
|
|
16
|
+
- 📜 Scroll route sections with automatic URL/title updates
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
@@ -164,6 +165,55 @@ It does exactly what we're talking about automatically by returning full html re
|
|
|
164
165
|
- Implement server-side partial responses for better bandwidth usage
|
|
165
166
|
- Consider using the `prefetch="onHover"` attribute for less important links
|
|
166
167
|
|
|
168
|
+
## Scroll Routes
|
|
169
|
+
|
|
170
|
+
The `scroll` attribute on a `<route>` marks it as a scrollable section. Scroll routes are stacked vertically and always visible when in scroll-route context. Clicking a link to a scroll route smooth-scrolls to that section. As the user scrolls, the URL and page title update automatically to reflect the visible section.
|
|
171
|
+
|
|
172
|
+
Use an optional `<title>` tag inside a scroll route to set the document title when that section is in view.
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
<router>
|
|
176
|
+
<route path="/" scroll>
|
|
177
|
+
<title>Home | My Site</title>
|
|
178
|
+
<section style="min-height: 100vh">Hero content</section>
|
|
179
|
+
</route>
|
|
180
|
+
<route path="/features" scroll>
|
|
181
|
+
<title>Features | My Site</title>
|
|
182
|
+
<section style="min-height: 100vh">Features content</section>
|
|
183
|
+
</route>
|
|
184
|
+
<route path="/pricing" scroll>
|
|
185
|
+
<title>Pricing | My Site</title>
|
|
186
|
+
<section style="min-height: 100vh">Pricing content</section>
|
|
187
|
+
</route>
|
|
188
|
+
</router>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
You can mix scroll routes and regular routes. When navigating to a regular route, scroll routes are hidden. When navigating to a scroll route, regular routes are hidden and all scroll sections are visible.
|
|
192
|
+
|
|
193
|
+
### Server configuration for direct URL access
|
|
194
|
+
|
|
195
|
+
When a user visits a scroll route URL directly (e.g. `/features`), the server must serve the page containing the scroll routes. Configure your server to serve the same HTML file for all scroll route paths:
|
|
196
|
+
|
|
197
|
+
**Nginx:**
|
|
198
|
+
```nginx
|
|
199
|
+
location ~ ^/(features|pricing)$ {
|
|
200
|
+
try_files $uri /home.html;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Express / Node.js:**
|
|
205
|
+
```javascript
|
|
206
|
+
app.get(["/", "/features", "/pricing"], (req, res) => {
|
|
207
|
+
res.sendFile(path.join(__dirname, "home.html"));
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Vite / static dev servers:** Use a rewrite rule or `_redirects` file (Netlify/Vercel):
|
|
212
|
+
```
|
|
213
|
+
/features /home.html 200
|
|
214
|
+
/pricing /home.html 200
|
|
215
|
+
```
|
|
216
|
+
|
|
167
217
|
## Future Development
|
|
168
218
|
- Delay router intialization on first link hover for better performances?
|
|
169
219
|
- Implement html streaming for faster page load
|
package/dist/router.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{let
|
|
1
|
+
(()=>{let i={},a=!1,l=(...e)=>a&&console.log("🚦 Router:",...e),c=(e,t)=>{var e=e?.querySelector("title");return e?e.textContent:(e=t.replace(/\.html$/,"").split("/").filter(Boolean).pop())?e.charAt(0).toUpperCase()+e.slice(1):"Home"},s=e=>{e.querySelectorAll("route[scroll]").forEach(e=>e.style.removeProperty("display")),e.querySelectorAll("route:not([scroll])").forEach(e=>e.style.display="none")},h=async()=>{l("Navigation triggered to:",globalThis.location.pathname),document.body.classList.add("loading");var t=globalThis.location.pathname.replace(/\/$/,"")||"/",e=document.querySelector("router");let r=e.querySelector(`route[path="${t}"]`);if(r?.hasAttribute("scroll"))s(e),document.body.classList.remove("loading"),r.scrollIntoView({behavior:"smooth"}),p&&p(t),l("Scroll route navigation completed");else{if(r||(l("Creating new route element for:",t),(r=document.createElement("route")).setAttribute("path",t),e.appendChild(r)),!r.innerHTML){l("Fetching content for:",globalThis.location.href);let e=i[globalThis.location.href];e||(e=await m(globalThis.location.href),i[globalThis.location.href]=e);var o,n=(new DOMParser).parseFromString(e,"text/html"),n=(document.title=c(n,t),l("Updating page title to:",document.title),r.innerHTML=n.body.innerHTML,Array.from(r.querySelectorAll("script")));l("Executing",n.length,"scripts from fetched content");for(o of n){var a=document.createElement("script");o.src?a.src=o.src:a.textContent=o.textContent,o.parentNode.replaceChild(a,o)}}e.querySelectorAll("route").forEach(e=>e.style.display="none"),r.style.display="contents",document.body.classList.remove("loading"),window.scrollTo(0,0),p&&p(t),l("Route change completed")}},d=async e=>{i[e.href]||(l("Prefetching content for:",e.href),i[e.href]=await m(e.href))},u=(e,r)=>{l("🔍 Intersection Observer triggered for",e.length,"entries"),e.forEach(e=>{var t=e.target;l(`🎯 Link ${t.href} intersection:`,{isIntersecting:e.isIntersecting,intersectionRatio:e.intersectionRatio,alreadyCached:!!i[t.href]}),e.isIntersecting&&(i[t.href]?l("📦 Content already cached for:",t.href):(d(t),l("👁️ Unobserving link after prefetch initiated:",t.href),r.unobserve(t)))})},f=e=>{var t=e.target.closest("A");t&&t.href&&g(t.href)&&t.origin===location.origin?(l("Internal link clicked:",t.href),e.preventDefault(),globalThis.history.pushState(null,null,t.href),globalThis.dispatchEvent(new Event("popstate"))):l("Invalid link click:",t?.href)};function g(e){if(!e||e.startsWith("#")||e.startsWith("javascript:"))return!1;if(e.startsWith("/"))return!0;try{var t=new URL(e,window.location.origin),r=new URL(window.location.href);return r.hostname.replace(/^www\./,"")!==t.hostname.replace(/^www\./,"")?!1:r.pathname!==t.pathname||!t.hash}catch{return!1}}let p,m=async e=>{e=await t(e);return e.ok?e.text():"Couldn't fetch the route - HTTP error! status: "+e.status},t=async e=>{if(!v){var t=await fetch(e,{method:"POST",body:"onlyRoute"});if(t.ok)return t}return fetch(e)},v=!1,e=(e={})=>{let{onRouteChange:r,debug:t}=e;a=t,l("Router starting...",e),r&&(e=r,p=e);e=document.createElement("style");e.textContent=".loading{animation:pulse 1s infinite alternate}@keyframes pulse{from{opacity:.6}to{opacity:.1}}route{content-visibility:auto}",document.head.appendChild(e);let o=document.querySelector("router");var e=globalThis.location.pathname.replace(/\/$/,"")||"/",n=(o||(l("Creating new router element"),o=document.createElement("router"),(n=document.createElement("route")).setAttribute("path",e),n.innerHTML=document.body.innerHTML,o.appendChild(n),document.body.innerHTML="",document.body.appendChild(o),v=!0),globalThis.addEventListener("popstate",h),document.addEventListener("click",f),document.body.addEventListener("mouseover",e=>{"A"===e.target.tagName&&"onHover"===e.target.getAttribute("prefetch")&&(async e=>{e=e.target;!i[e.href]&&g(e.href)&&await d(e)})(e)}),new IntersectionObserver(u,{root:null,threshold:.5})),n=(l("🎭 Created Intersection Observer with config:",{root:"viewport",threshold:.5}),(r=>{let o=navigator.connection&&navigator.connection.saveData;var e=document.querySelectorAll("a");l("🔄 Starting link observation...",{totalLinks:e.length,saveDataMode:o}),e.forEach(e=>{var t="onHover"!==e.getAttribute("prefetch")&&!o&&g(e.href);l("🔗 Link evaluation:",{href:e.href,prefetchAttr:e.getAttribute("prefetch"),isInternal:g(e.href),willObserve:t}),t&&(r.observe(e),l("👀 Now observing link:",e.href))})})(n),o.querySelectorAll("route[scroll]"));if(n.length){e=o.querySelector(`route[path="${e}"][scroll]`);e&&(s(o),e.scrollIntoView({behavior:"instant"}));let t=new IntersectionObserver(e=>{e.forEach(e=>{var t;e.isIntersecting&&(t=e.target.getAttribute("path")||"/",globalThis.history.replaceState(null,null,t),document.title=c(e.target,t),r)&&r(t)})},{threshold:.6});n.forEach(e=>t.observe(e))}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>e()):e()})();
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightweight-router",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"main": "src/router.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "npx http-server src -o",
|
|
7
7
|
"build": "node build.js",
|
|
8
|
-
"deploy": "npm run build && git publish && npm version patch && git publish && npm publish",
|
|
8
|
+
"deploy": "npm run build && git publish && npm version patch && git publish && npm login && npm publish",
|
|
9
9
|
"test": "jest",
|
|
10
10
|
"test:watch": "jest --watch"
|
|
11
11
|
},
|
package/src/about.html
CHANGED
|
@@ -2,22 +2,84 @@
|
|
|
2
2
|
<head>
|
|
3
3
|
<meta charset="utf-8" />
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
5
|
-
<title>
|
|
5
|
+
<title>About | PageFlick Demo</title>
|
|
6
|
+
<style>
|
|
7
|
+
* {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
}
|
|
12
|
+
body {
|
|
13
|
+
font-family: system-ui, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
nav {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
gap: 1rem;
|
|
21
|
+
padding: 1rem 2rem;
|
|
22
|
+
background: rgba(255, 255, 255, 0.9);
|
|
23
|
+
backdrop-filter: blur(8px);
|
|
24
|
+
z-index: 100;
|
|
25
|
+
border-bottom: 1px solid #eee;
|
|
26
|
+
}
|
|
27
|
+
nav a {
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
color: #333;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
}
|
|
32
|
+
nav a:hover {
|
|
33
|
+
color: #000;
|
|
34
|
+
}
|
|
35
|
+
route[scroll] {
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
section {
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 6rem 2rem 2rem;
|
|
45
|
+
gap: 1rem;
|
|
46
|
+
}
|
|
47
|
+
section h1 {
|
|
48
|
+
font-size: 3rem;
|
|
49
|
+
}
|
|
50
|
+
section p {
|
|
51
|
+
font-size: 1.25rem;
|
|
52
|
+
color: #555;
|
|
53
|
+
max-width: 600px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
#hero {
|
|
57
|
+
background: #f0f4ff;
|
|
58
|
+
}
|
|
59
|
+
#features {
|
|
60
|
+
background: #f0fff4;
|
|
61
|
+
}
|
|
62
|
+
#pricing {
|
|
63
|
+
background: #fff0f4;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
6
66
|
</head>
|
|
7
|
-
<body
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
67
|
+
<body>
|
|
68
|
+
<nav>
|
|
69
|
+
<a href="/">Home</a>
|
|
70
|
+
<a href="/features">Features</a>
|
|
71
|
+
<a href="/pricing">Pricing</a>
|
|
72
|
+
<a href="/about.html">About (regular route)</a>
|
|
73
|
+
</nav>
|
|
74
|
+
<section id="about">
|
|
75
|
+
<h1>About</h1>
|
|
76
|
+
<p>This is the about page.</p>
|
|
77
|
+
<a href="/">Back to home ↑</a>
|
|
78
|
+
</section>
|
|
13
79
|
<script type="module">
|
|
14
80
|
import { startRouter } from "./router.js";
|
|
15
|
-
// Initialize the router with a simple onRouteChange callback to test functionality
|
|
16
81
|
startRouter({
|
|
17
|
-
onRouteChange:
|
|
18
|
-
console.log("Route changed:", currentRoute);
|
|
19
|
-
currentRoute.classList.add("fade-in"); // Example: simple CSS class for animation (define it in CSS if needed)
|
|
20
|
-
},
|
|
82
|
+
onRouteChange: path => console.log("Route:", path),
|
|
21
83
|
});
|
|
22
84
|
</script>
|
|
23
85
|
</body>
|
package/src/index.html
CHANGED
|
@@ -2,22 +2,115 @@
|
|
|
2
2
|
<head>
|
|
3
3
|
<meta charset="utf-8" />
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
5
|
-
<title>
|
|
5
|
+
<title>Home | PageFlick Demo</title>
|
|
6
|
+
<style>
|
|
7
|
+
* {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
}
|
|
12
|
+
body {
|
|
13
|
+
font-family: system-ui, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
nav {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
gap: 1rem;
|
|
21
|
+
padding: 1rem 2rem;
|
|
22
|
+
background: rgba(255, 255, 255, 0.9);
|
|
23
|
+
backdrop-filter: blur(8px);
|
|
24
|
+
z-index: 100;
|
|
25
|
+
border-bottom: 1px solid #eee;
|
|
26
|
+
}
|
|
27
|
+
nav a {
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
color: #333;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
}
|
|
32
|
+
nav a:hover {
|
|
33
|
+
color: #000;
|
|
34
|
+
}
|
|
35
|
+
route[scroll] {
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
section {
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 6rem 2rem 2rem;
|
|
45
|
+
gap: 1rem;
|
|
46
|
+
}
|
|
47
|
+
section h1 {
|
|
48
|
+
font-size: 3rem;
|
|
49
|
+
}
|
|
50
|
+
section p {
|
|
51
|
+
font-size: 1.25rem;
|
|
52
|
+
color: #555;
|
|
53
|
+
max-width: 600px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
#hero {
|
|
57
|
+
background: #f0f4ff;
|
|
58
|
+
}
|
|
59
|
+
#features {
|
|
60
|
+
background: #f0fff4;
|
|
61
|
+
}
|
|
62
|
+
#pricing {
|
|
63
|
+
background: #fff0f4;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
6
66
|
</head>
|
|
7
67
|
<body>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
68
|
+
<nav>
|
|
69
|
+
<a href="/">Home</a>
|
|
70
|
+
<a href="/features">Features</a>
|
|
71
|
+
<a href="/pricing">Pricing</a>
|
|
72
|
+
<a href="/about.html">About (regular route)</a>
|
|
73
|
+
</nav>
|
|
74
|
+
|
|
75
|
+
<router>
|
|
76
|
+
<route path="/" scroll>
|
|
77
|
+
<title>Home | PageFlick Demo</title>
|
|
78
|
+
<section id="hero">
|
|
79
|
+
<h1>PageFlick</h1>
|
|
80
|
+
<p>A minimal client-side router with scroll section support. Scroll down or click the nav links above.</p>
|
|
81
|
+
<a href="/features">See Features ↓</a>
|
|
82
|
+
</section>
|
|
83
|
+
</route>
|
|
84
|
+
|
|
85
|
+
<route path="/features" scroll>
|
|
86
|
+
<title>Features | PageFlick Demo</title>
|
|
87
|
+
<section id="features">
|
|
88
|
+
<h1>Features</h1>
|
|
89
|
+
<p>Scroll routes stack vertically and stay visible. The URL and title update as you scroll through sections.</p>
|
|
90
|
+
<ul style="text-align: left; color: #333">
|
|
91
|
+
<li>Smooth scroll navigation</li>
|
|
92
|
+
<li>Automatic URL updates on scroll</li>
|
|
93
|
+
<li>Direct URL access jumps to section</li>
|
|
94
|
+
<li>Mix with regular routes</li>
|
|
95
|
+
</ul>
|
|
96
|
+
<a href="/pricing">See Pricing ↓</a>
|
|
97
|
+
</section>
|
|
98
|
+
</route>
|
|
99
|
+
|
|
100
|
+
<route path="/pricing" scroll>
|
|
101
|
+
<title>Pricing | PageFlick Demo</title>
|
|
102
|
+
<section id="pricing">
|
|
103
|
+
<h1>Pricing</h1>
|
|
104
|
+
<p>Free and open source. Always.</p>
|
|
105
|
+
<a href="/">Back to top ↑</a>
|
|
106
|
+
</section>
|
|
107
|
+
</route>
|
|
108
|
+
</router>
|
|
109
|
+
|
|
13
110
|
<script type="module">
|
|
14
111
|
import { startRouter } from "./router.js";
|
|
15
|
-
// Initialize the router with a simple onRouteChange callback to test functionality
|
|
16
112
|
startRouter({
|
|
17
|
-
onRouteChange:
|
|
18
|
-
console.log("Route changed:", currentRoute);
|
|
19
|
-
currentRoute.classList.add("fade-in"); // Example: simple CSS class for animation (define it in CSS if needed)
|
|
20
|
-
},
|
|
113
|
+
onRouteChange: path => console.log("Route:", path),
|
|
21
114
|
});
|
|
22
115
|
</script>
|
|
23
116
|
</body>
|
package/src/router.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
let linkData = {};
|
|
2
2
|
let debugMode = false;
|
|
3
3
|
const log = (...args) => debugMode && console.log("🚦 Router:", ...args);
|
|
4
|
+
|
|
5
|
+
const getTitle = (el, path) => {
|
|
6
|
+
const tag = el?.querySelector("title");
|
|
7
|
+
if (tag) return tag.textContent;
|
|
8
|
+
const segment = path.replace(/\.html$/, "").split("/").filter(Boolean).pop();
|
|
9
|
+
return segment ? segment.charAt(0).toUpperCase() + segment.slice(1) : "Home";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const showScrollRoutes = router => {
|
|
13
|
+
router.querySelectorAll("route[scroll]").forEach(r => r.style.removeProperty("display"));
|
|
14
|
+
router.querySelectorAll("route:not([scroll])").forEach(r => (r.style.display = "none"));
|
|
15
|
+
};
|
|
16
|
+
|
|
4
17
|
const handlePopState = async () => {
|
|
5
18
|
log("Navigation triggered to:", globalThis.location.pathname);
|
|
6
19
|
document.body.classList.add("loading");
|
|
7
|
-
const currentPath = globalThis.location.pathname.replace(/\/$/, ""); // Normalize path
|
|
20
|
+
const currentPath = globalThis.location.pathname.replace(/\/$/, "") || "/"; // Normalize path, preserve root "/"
|
|
8
21
|
const router = document.querySelector("router");
|
|
9
22
|
|
|
10
23
|
let currentRoute = router.querySelector(`route[path="${currentPath}"]`);
|
|
11
24
|
|
|
25
|
+
// Handle scroll route navigation — show all scroll routes and smooth-scroll to target
|
|
26
|
+
if (currentRoute?.hasAttribute("scroll")) {
|
|
27
|
+
showScrollRoutes(router);
|
|
28
|
+
document.body.classList.remove("loading");
|
|
29
|
+
currentRoute.scrollIntoView({ behavior: "smooth" });
|
|
30
|
+
if (onRouteChange) onRouteChange(currentPath);
|
|
31
|
+
log("Scroll route navigation completed");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
// If the route doesn't exist in DOM, create and append it
|
|
13
36
|
if (!currentRoute) {
|
|
14
37
|
log("Creating new route element for:", currentPath);
|
|
@@ -32,11 +55,8 @@ const handlePopState = async () => {
|
|
|
32
55
|
const doc = parser.parseFromString(content, "text/html");
|
|
33
56
|
|
|
34
57
|
// Update the page title with the new content's title
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
log("Updating page title to:", newTitle.textContent);
|
|
38
|
-
document.title = newTitle.textContent;
|
|
39
|
-
}
|
|
58
|
+
document.title = getTitle(doc, currentPath);
|
|
59
|
+
log("Updating page title to:", document.title);
|
|
40
60
|
|
|
41
61
|
currentRoute.innerHTML = doc.body.innerHTML;
|
|
42
62
|
|
|
@@ -54,9 +74,9 @@ const handlePopState = async () => {
|
|
|
54
74
|
}
|
|
55
75
|
}
|
|
56
76
|
|
|
57
|
-
// Display only the current route
|
|
58
|
-
router.querySelectorAll("route").forEach(route => (route.style.
|
|
59
|
-
currentRoute.style.
|
|
77
|
+
// Display only the current route (hides scroll routes too when on a regular route)
|
|
78
|
+
router.querySelectorAll("route").forEach(route => (route.style.display = "none"));
|
|
79
|
+
currentRoute.style.display = "contents";
|
|
60
80
|
|
|
61
81
|
document.body.classList.remove("loading");
|
|
62
82
|
window.scrollTo(0, 0);
|
|
@@ -211,7 +231,7 @@ const startRouter = (options = {}) => {
|
|
|
211
231
|
document.head.appendChild(style);
|
|
212
232
|
|
|
213
233
|
let router = document.querySelector("router");
|
|
214
|
-
const currentPath = globalThis.location.pathname;
|
|
234
|
+
const currentPath = globalThis.location.pathname.replace(/\/$/, "") || "/";
|
|
215
235
|
|
|
216
236
|
if (!router) {
|
|
217
237
|
log("Creating new router element");
|
|
@@ -244,6 +264,34 @@ const startRouter = (options = {}) => {
|
|
|
244
264
|
});
|
|
245
265
|
|
|
246
266
|
observeLinks(observer);
|
|
267
|
+
|
|
268
|
+
// Initialize scroll routes
|
|
269
|
+
const scrollRoutes = router.querySelectorAll("route[scroll]");
|
|
270
|
+
if (scrollRoutes.length) {
|
|
271
|
+
const currentScrollRoute = router.querySelector(`route[path="${currentPath}"][scroll]`);
|
|
272
|
+
if (currentScrollRoute) {
|
|
273
|
+
// Direct URL visit to a scroll route — show all scroll sections and jump instantly
|
|
274
|
+
showScrollRoutes(router);
|
|
275
|
+
currentScrollRoute.scrollIntoView({ behavior: "instant" });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Update URL and title as scroll routes enter the viewport
|
|
279
|
+
const scrollObserver = new IntersectionObserver(
|
|
280
|
+
entries => {
|
|
281
|
+
entries.forEach(entry => {
|
|
282
|
+
if (entry.isIntersecting) {
|
|
283
|
+
const path = entry.target.getAttribute("path") || "/";
|
|
284
|
+
globalThis.history.replaceState(null, null, path);
|
|
285
|
+
document.title = getTitle(entry.target, path);
|
|
286
|
+
if (onRouteChange) onRouteChange(path);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
{ threshold: 0.6 }
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
scrollRoutes.forEach(route => scrollObserver.observe(route));
|
|
294
|
+
}
|
|
247
295
|
};
|
|
248
296
|
|
|
249
297
|
export { startRouter };
|
package/src/router.html
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
<html lang="en">
|
|
2
|
-
<head>
|
|
3
|
-
<meta charset="utf-8" />
|
|
4
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
5
|
-
<title>Router manual test About</title>
|
|
6
|
-
</head>
|
|
7
|
-
<body style="background-color: bisque">
|
|
8
|
-
<menu class="flex m-0 p-0"
|
|
9
|
-
><a prefetch="onHover" href="/">Home</a><a href="/about.html">About</a><a href="/admin" prefetch="onHover">Admin</a
|
|
10
|
-
><a href="/irmfirmiror" prefetch="onHover">404</a> <a href="https://andreafuturi.com" prefetch="onHover">Google Test</a> </menu
|
|
11
|
-
><router>
|
|
12
|
-
<route path="/about.html" style="content-visibility: auto"><h1>About</h1></route><route path="/onlyServer" style="content-visibility: auto"></route
|
|
13
|
-
></router>
|
|
14
|
-
<script type="module">
|
|
15
|
-
import { startRouter } from "./router.js";
|
|
16
|
-
// Initialize the router with a simple onRouteChange callback to test functionality
|
|
17
|
-
startRouter({
|
|
18
|
-
onRouteChange: currentRoute => {
|
|
19
|
-
console.log("Route changed:", currentRoute);
|
|
20
|
-
currentRoute.classList.add("fade-in"); // Example: simple CSS class for animation (define it in CSS if needed)
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
</script>
|
|
24
|
-
</body>
|
|
25
|
-
</html>
|