evolit 0.1.0-alpha.22 → 0.1.0-alpha.24
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 +18 -2
- package/package.json +1 -1
- package/src/navigation-client.js +56 -13
- package/src/navigation-server.js +2 -0
- package/src/route-segments.js +3 -0
package/README.md
CHANGED
|
@@ -105,8 +105,10 @@ export default function CollectionControls() {
|
|
|
105
105
|
|
|
106
106
|
The hook returns `{ status, url, pendingUrl, error, push, replace, refresh, createHref }`:
|
|
107
107
|
|
|
108
|
-
- `push(target)` adds a browser-history entry.
|
|
109
|
-
|
|
108
|
+
- `push(target, { scroll: false })` adds a browser-history entry. Pass
|
|
109
|
+
`scroll: false` to keep the current viewport position.
|
|
110
|
+
- `replace(target, { scroll: false })` updates the current entry, useful for
|
|
111
|
+
visual-only query state. It accepts the same scroll option.
|
|
110
112
|
- `refresh()` bypasses the client delta cache for the current URL.
|
|
111
113
|
- `createHref(pathname, searchParams)` creates a relative internal URL. It accepts standard
|
|
112
114
|
`URLSearchParams`, preserving repeated keys such as `facet=brand&facet=material`.
|
|
@@ -115,6 +117,20 @@ The hook returns `{ status, url, pendingUrl, error, push, replace, refresh, crea
|
|
|
115
117
|
share with server-evaluated route code. `useNavigation()` itself is browser-only and must only run
|
|
116
118
|
from a connected client component.
|
|
117
119
|
|
|
120
|
+
Client components can also read the active route state with browser-only hooks:
|
|
121
|
+
|
|
122
|
+
```jsx
|
|
123
|
+
import { useParams, useSearchParams } from "evolit/navigation";
|
|
124
|
+
|
|
125
|
+
const { slug = [] } = useParams();
|
|
126
|
+
const searchParams = useSearchParams();
|
|
127
|
+
const selectedFacets = searchParams.getAll("facet");
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Both hooks update after client navigation. `useParams()` returns a read-only snapshot; the
|
|
131
|
+
`URLSearchParams` from `useSearchParams()` is a local snapshot, so build a new href and navigate to
|
|
132
|
+
it to update the URL.
|
|
133
|
+
|
|
118
134
|
### Progressive links and forms
|
|
119
135
|
|
|
120
136
|
Links work without JavaScript. With JavaScript, Evolit intercepts ordinary same-origin left-clicks.
|
package/package.json
CHANGED
package/src/navigation-client.js
CHANGED
|
@@ -18,6 +18,20 @@ function readRoute(documentRef) {
|
|
|
18
18
|
try { return source ? JSON.parse(source) : null; } catch { return null; }
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function readRouteParams(documentRef) {
|
|
22
|
+
const params = readRoute(documentRef)?.params;
|
|
23
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
24
|
+
return Object.freeze({});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Object.freeze(Object.fromEntries(
|
|
28
|
+
Object.entries(params).map(([key, value]) => [
|
|
29
|
+
key,
|
|
30
|
+
Array.isArray(value) ? Object.freeze([...value]) : value,
|
|
31
|
+
]),
|
|
32
|
+
));
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
function findMarkers(root, segmentId, matches = []) {
|
|
22
36
|
const documentRef = root.ownerDocument ?? root;
|
|
23
37
|
const walker = documentRef.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
|
|
@@ -309,14 +323,16 @@ function findHashTarget(documentRef, href) {
|
|
|
309
323
|
}
|
|
310
324
|
}
|
|
311
325
|
|
|
312
|
-
function restoreScrollAndFocus(windowRef, href, position) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
target
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
326
|
+
function restoreScrollAndFocus(windowRef, href, position, preserveScroll = false) {
|
|
327
|
+
if (!preserveScroll) {
|
|
328
|
+
const target = findHashTarget(windowRef.document, href);
|
|
329
|
+
if (target?.scrollIntoView) {
|
|
330
|
+
target.scrollIntoView();
|
|
331
|
+
} else if (position) {
|
|
332
|
+
windowRef.scrollTo?.(position.x, position.y);
|
|
333
|
+
} else {
|
|
334
|
+
windowRef.scrollTo?.(0, 0);
|
|
335
|
+
}
|
|
320
336
|
}
|
|
321
337
|
|
|
322
338
|
const main = windowRef.document.querySelector?.("main");
|
|
@@ -410,6 +426,9 @@ export function createBrowserNavigation(options = {}) {
|
|
|
410
426
|
async function navigate(target, mode = "push", fromPopState = false, options = {}) {
|
|
411
427
|
const href = toHref(target, windowRef.location);
|
|
412
428
|
const cacheKey = toCacheKey(href);
|
|
429
|
+
const scrollPosition = options.scroll === false
|
|
430
|
+
? currentScrollPosition(windowRef)
|
|
431
|
+
: options.scrollPosition;
|
|
413
432
|
controller?.abort();
|
|
414
433
|
const navigationController = new AbortController();
|
|
415
434
|
controller = navigationController;
|
|
@@ -475,14 +494,19 @@ export function createBrowserNavigation(options = {}) {
|
|
|
475
494
|
{
|
|
476
495
|
...(mode === "replace" ? windowRef.history.state : {}),
|
|
477
496
|
__evolitNavigationEntry: entryId,
|
|
478
|
-
__evolitScroll: { x: 0, y: 0 },
|
|
497
|
+
__evolitScroll: scrollPosition ?? { x: 0, y: 0 },
|
|
479
498
|
},
|
|
480
499
|
"",
|
|
481
500
|
canonicalHref,
|
|
482
501
|
);
|
|
483
502
|
}
|
|
484
503
|
cacheDelta(entryId, toCacheKey(canonicalHref), delta);
|
|
485
|
-
restoreScrollAndFocus(
|
|
504
|
+
restoreScrollAndFocus(
|
|
505
|
+
windowRef,
|
|
506
|
+
canonicalHref,
|
|
507
|
+
scrollPosition,
|
|
508
|
+
options.scroll === false,
|
|
509
|
+
);
|
|
486
510
|
state = { status: "idle", url: canonicalHref, pendingUrl: null, error: null };
|
|
487
511
|
emit();
|
|
488
512
|
return delta;
|
|
@@ -519,9 +543,9 @@ export function createBrowserNavigation(options = {}) {
|
|
|
519
543
|
return {
|
|
520
544
|
getState: () => state,
|
|
521
545
|
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
522
|
-
push: (target) => navigate(target, "push"),
|
|
523
|
-
replace: (target) => navigate(target, "replace"),
|
|
524
|
-
refresh: () => navigate(windowRef.location.href, "replace", false, { force: true }),
|
|
546
|
+
push: (target, options) => navigate(target, "push", false, options),
|
|
547
|
+
replace: (target, options) => navigate(target, "replace", false, options),
|
|
548
|
+
refresh: (options) => navigate(windowRef.location.href, "replace", false, { ...options, force: true }),
|
|
525
549
|
createHref,
|
|
526
550
|
};
|
|
527
551
|
}
|
|
@@ -544,3 +568,22 @@ export function useNavigation() {
|
|
|
544
568
|
createHref: navigation.createHref,
|
|
545
569
|
};
|
|
546
570
|
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Returns the dynamic route parameters for the active client route.
|
|
574
|
+
* The returned object is a read-only snapshot and updates after navigation.
|
|
575
|
+
*/
|
|
576
|
+
export function useParams() {
|
|
577
|
+
useNavigation();
|
|
578
|
+
return readRouteParams(globalThis.document);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Returns a URLSearchParams snapshot for the active client route.
|
|
583
|
+
* Mutating it is local only; use createHref() and navigation.push()/replace()
|
|
584
|
+
* to update the URL.
|
|
585
|
+
*/
|
|
586
|
+
export function useSearchParams() {
|
|
587
|
+
const navigation = useNavigation();
|
|
588
|
+
return new URL(navigation.url).searchParams;
|
|
589
|
+
}
|
package/src/navigation-server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export function getNavigation() { throw new Error("Navigation is only available in a browser context."); }
|
|
2
2
|
export { createHref } from "./navigation-url.js";
|
|
3
3
|
export function useNavigation() { throw new Error("useNavigation() is only available in a browser component."); }
|
|
4
|
+
export function useParams() { throw new Error("useParams() is only available in a browser component."); }
|
|
5
|
+
export function useSearchParams() { throw new Error("useSearchParams() is only available in a browser component."); }
|
package/src/route-segments.js
CHANGED
|
@@ -73,6 +73,9 @@ export function createRouteSegmentPayload(routeResult) {
|
|
|
73
73
|
version: 1,
|
|
74
74
|
url: routeResult.cacheKey ?? routeResult.route?.pathname ?? null,
|
|
75
75
|
pathname: routeResult.cacheKey?.split("?")[0] ?? routeResult.route?.pathname ?? null,
|
|
76
|
+
params: routeResult.params && typeof routeResult.params === "object"
|
|
77
|
+
? routeResult.params
|
|
78
|
+
: {},
|
|
76
79
|
cachePolicy: routeResult.cachePolicy
|
|
77
80
|
? {
|
|
78
81
|
mode: routeResult.cachePolicy.mode,
|