@multiplatform.one/router 6.1.0 → 6.3.0
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 +51 -0
- package/dist/cjs/hooks/useUrlState.cjs +30 -9
- package/dist/cjs/hooks/useUrlState.native.js +32 -9
- package/dist/cjs/hooks/useUrlState.native.js.map +1 -1
- package/dist/cjs/seam/DomNavigationHost.cjs +122 -0
- package/dist/cjs/seam/DomNavigationHost.native.js +186 -0
- package/dist/cjs/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/cjs/seam/index.cjs +47 -0
- package/dist/cjs/seam/index.native.js +50 -0
- package/dist/cjs/seam/index.native.js.map +1 -0
- package/dist/cjs/seam/navigator.cjs +309 -0
- package/dist/cjs/seam/navigator.native.js +479 -0
- package/dist/cjs/seam/navigator.native.js.map +1 -0
- package/dist/cjs/seam/oneAdapter.cjs +128 -0
- package/dist/cjs/seam/oneAdapter.native.js +141 -0
- package/dist/cjs/seam/oneAdapter.native.js.map +1 -0
- package/dist/esm/hooks/useUrlState.mjs +31 -10
- package/dist/esm/hooks/useUrlState.mjs.map +1 -1
- package/dist/esm/hooks/useUrlState.native.js +33 -10
- package/dist/esm/hooks/useUrlState.native.js.map +1 -1
- package/dist/esm/seam/DomNavigationHost.mjs +96 -0
- package/dist/esm/seam/DomNavigationHost.mjs.map +1 -0
- package/dist/esm/seam/DomNavigationHost.native.js +157 -0
- package/dist/esm/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/esm/seam/index.mjs +5 -0
- package/dist/esm/seam/index.mjs.map +1 -0
- package/dist/esm/seam/index.native.js +5 -0
- package/dist/esm/seam/index.native.js.map +1 -0
- package/dist/esm/seam/navigator.mjs +279 -0
- package/dist/esm/seam/navigator.mjs.map +1 -0
- package/dist/esm/seam/navigator.native.js +446 -0
- package/dist/esm/seam/navigator.native.js.map +1 -0
- package/dist/esm/seam/oneAdapter.mjs +90 -0
- package/dist/esm/seam/oneAdapter.mjs.map +1 -0
- package/dist/esm/seam/oneAdapter.native.js +100 -0
- package/dist/esm/seam/oneAdapter.native.js.map +1 -0
- package/dist/jsx/hooks/useUrlState.mjs +31 -10
- package/dist/jsx/hooks/useUrlState.mjs.map +1 -1
- package/dist/jsx/hooks/useUrlState.native.js +32 -9
- package/dist/jsx/hooks/useUrlState.native.js.map +1 -1
- package/dist/jsx/seam/DomNavigationHost.mjs +96 -0
- package/dist/jsx/seam/DomNavigationHost.mjs.map +1 -0
- package/dist/jsx/seam/DomNavigationHost.native.js +186 -0
- package/dist/jsx/seam/DomNavigationHost.native.js.map +1 -0
- package/dist/jsx/seam/index.mjs +5 -0
- package/dist/jsx/seam/index.mjs.map +1 -0
- package/dist/jsx/seam/index.native.js +50 -0
- package/dist/jsx/seam/index.native.js.map +1 -0
- package/dist/jsx/seam/navigator.mjs +279 -0
- package/dist/jsx/seam/navigator.mjs.map +1 -0
- package/dist/jsx/seam/navigator.native.js +479 -0
- package/dist/jsx/seam/navigator.native.js.map +1 -0
- package/dist/jsx/seam/oneAdapter.mjs +90 -0
- package/dist/jsx/seam/oneAdapter.mjs.map +1 -0
- package/dist/jsx/seam/oneAdapter.native.js +141 -0
- package/dist/jsx/seam/oneAdapter.native.js.map +1 -0
- package/package.json +26 -9
- package/src/hooks/urlStateEdges.spec.ts +98 -0
- package/src/hooks/useUrlState.ts +34 -8
- package/src/seam/DomNavigationHost.spec.tsx +78 -0
- package/src/seam/DomNavigationHost.tsx +150 -0
- package/src/seam/index.ts +41 -0
- package/src/seam/navigator.spec.ts +189 -0
- package/src/seam/navigator.ts +405 -0
- package/src/seam/oneAdapter.tsx +166 -0
- package/types/hooks/index.d.ts +2 -0
- package/types/hooks/index.d.ts.map +1 -0
- package/types/hooks/useUrlState.d.ts +36 -0
- package/types/hooks/useUrlState.d.ts.map +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.storybook.d.ts +4 -0
- package/types/index.storybook.d.ts.map +1 -0
- package/types/router/EmptyRouter.d.ts +7 -0
- package/types/router/EmptyRouter.d.ts.map +1 -0
- package/types/router/ReactRouter.d.ts +6 -0
- package/types/router/ReactRouter.d.ts.map +1 -0
- package/types/router/index.d.ts +2 -0
- package/types/router/index.d.ts.map +1 -0
- package/types/router/index.storybook-expo.d.ts +2 -0
- package/types/router/index.storybook-expo.d.ts.map +1 -0
- package/types/router/index.storybook.d.ts +2 -0
- package/types/router/index.storybook.d.ts.map +1 -0
- package/types/router/index.webext.d.ts +2 -0
- package/types/router/index.webext.d.ts.map +1 -0
- package/types/seam/DomNavigationHost.d.ts +33 -0
- package/types/seam/DomNavigationHost.d.ts.map +1 -0
- package/types/seam/index.d.ts +7 -0
- package/types/seam/index.d.ts.map +1 -0
- package/types/seam/navigator.d.ts +133 -0
- package/types/seam/navigator.d.ts.map +1 -0
- package/types/seam/oneAdapter.d.ts +57 -0
- package/types/seam/oneAdapter.d.ts.map +1 -0
- package/types/types.d.ts +40 -0
- package/types/types.d.ts.map +1 -0
- package/dist/cjs/hooks/useUrlState.spec.cjs +0 -280
- package/dist/cjs/hooks/useUrlState.spec.native.js +0 -315
- package/dist/cjs/hooks/useUrlState.spec.native.js.map +0 -1
- package/dist/esm/hooks/useUrlState.spec.mjs +0 -257
- package/dist/esm/hooks/useUrlState.spec.mjs.map +0 -1
- package/dist/esm/hooks/useUrlState.spec.native.js +0 -289
- package/dist/esm/hooks/useUrlState.spec.native.js.map +0 -1
- package/dist/jsx/hooks/useUrlState.spec.mjs +0 -257
- package/dist/jsx/hooks/useUrlState.spec.mjs.map +0 -1
- package/dist/jsx/hooks/useUrlState.spec.native.js +0 -315
- package/dist/jsx/hooks/useUrlState.spec.native.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: () => from[key],
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
23
|
+
value: true
|
|
24
|
+
}), mod);
|
|
25
|
+
var oneAdapter_exports = {};
|
|
26
|
+
__export(oneAdapter_exports, {
|
|
27
|
+
Link: () => Link,
|
|
28
|
+
LoadProgressBar: () => LoadProgressBar,
|
|
29
|
+
Slot: () => Slot,
|
|
30
|
+
getUserScheme: () => import_color_scheme.getUserScheme,
|
|
31
|
+
onUserSchemeChange: () => import_color_scheme.onUserSchemeChange,
|
|
32
|
+
setUserScheme: () => import_color_scheme.setUserScheme,
|
|
33
|
+
useBlocker: () => useBlocker,
|
|
34
|
+
useLoader: () => useLoader,
|
|
35
|
+
useLocalSearchParams: () => useLocalSearchParams,
|
|
36
|
+
useMatches: () => useMatches,
|
|
37
|
+
useParams: () => useParams,
|
|
38
|
+
usePathname: () => usePathname,
|
|
39
|
+
useRouter: () => useRouter,
|
|
40
|
+
useUserScheme: () => import_color_scheme.useUserScheme
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(oneAdapter_exports);
|
|
43
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
var import_react = require("react");
|
|
45
|
+
var import_navigator = require("./navigator.native.js");
|
|
46
|
+
var import_color_scheme = require("@vxrn/color-scheme");
|
|
47
|
+
function useLoader(_loader) {
|
|
48
|
+
var tag = (0, import_react.useContext)(import_navigator.RouteTagContext);
|
|
49
|
+
var fromRoute = (0, import_react.useSyncExternalStore)(import_navigator.stackNavigator.subscribe, function () {
|
|
50
|
+
return import_navigator.stackNavigator.loaderSnapshot(tag);
|
|
51
|
+
});
|
|
52
|
+
return fromRoute;
|
|
53
|
+
}
|
|
54
|
+
var ROUTER = {
|
|
55
|
+
push: import_navigator.stackNavigator.push,
|
|
56
|
+
navigate: import_navigator.stackNavigator.push,
|
|
57
|
+
replace: import_navigator.stackNavigator.replace,
|
|
58
|
+
back: import_navigator.stackNavigator.back,
|
|
59
|
+
canGoBack: function canGoBack() {
|
|
60
|
+
return import_navigator.stackNavigator.getSnapshot().canGoBack;
|
|
61
|
+
},
|
|
62
|
+
dismiss: function dismiss(count) {
|
|
63
|
+
for (var i = 0; i < (count !== null && count !== void 0 ? count : 1); i++) import_navigator.stackNavigator.back();
|
|
64
|
+
},
|
|
65
|
+
dismissAll: function dismissAll() {
|
|
66
|
+
var depth = import_navigator.stackNavigator.getSnapshot().depth;
|
|
67
|
+
for (var i = 1; i < depth; i++) import_navigator.stackNavigator.back();
|
|
68
|
+
},
|
|
69
|
+
canDismiss: function canDismiss() {
|
|
70
|
+
return import_navigator.stackNavigator.getSnapshot().canGoBack;
|
|
71
|
+
},
|
|
72
|
+
setParams: import_navigator.stackNavigator.setParams,
|
|
73
|
+
subscribe: function subscribe(listener) {
|
|
74
|
+
listener("success");
|
|
75
|
+
return function () {};
|
|
76
|
+
},
|
|
77
|
+
onLoadState: function onLoadState(listener) {
|
|
78
|
+
listener("loaded");
|
|
79
|
+
return function () {};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function useRouter() {
|
|
83
|
+
return ROUTER;
|
|
84
|
+
}
|
|
85
|
+
function Link(param) {
|
|
86
|
+
var {
|
|
87
|
+
href,
|
|
88
|
+
replace,
|
|
89
|
+
children,
|
|
90
|
+
style,
|
|
91
|
+
...rest
|
|
92
|
+
} = param;
|
|
93
|
+
if (!href || !import_navigator.stackNavigator.isConfigured) return children !== null && children !== void 0 ? children : null;
|
|
94
|
+
var hrefString = (0, import_navigator.hrefToString)(href);
|
|
95
|
+
var onClick = function onClick2(event) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
if (replace) import_navigator.stackNavigator.replace(href);else import_navigator.stackNavigator.push(href);
|
|
98
|
+
};
|
|
99
|
+
return /* @__PURE__ */(0, import_jsx_runtime.jsx)("a", {
|
|
100
|
+
href: hrefString,
|
|
101
|
+
onClick,
|
|
102
|
+
// display:contents — the anchor contributes no box, so wrapping
|
|
103
|
+
// arbitrary flex/grid children never disturbs layout (clicks still
|
|
104
|
+
// bubble through it).
|
|
105
|
+
style: {
|
|
106
|
+
display: "contents",
|
|
107
|
+
color: "inherit",
|
|
108
|
+
textDecoration: "none",
|
|
109
|
+
...style
|
|
110
|
+
},
|
|
111
|
+
"data-seam-link": hrefString,
|
|
112
|
+
...rest,
|
|
113
|
+
children
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function Slot(_props) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function useParams() {
|
|
120
|
+
return (0, import_navigator.useRouteParams)();
|
|
121
|
+
}
|
|
122
|
+
function useLocalSearchParams() {
|
|
123
|
+
return (0, import_navigator.useRouteParams)();
|
|
124
|
+
}
|
|
125
|
+
function usePathname() {
|
|
126
|
+
return (0, import_navigator.useNavState)().pathname;
|
|
127
|
+
}
|
|
128
|
+
function useBlocker(_shouldBlock) {
|
|
129
|
+
return {
|
|
130
|
+
state: "unblocked",
|
|
131
|
+
proceed: function proceed() {},
|
|
132
|
+
reset: function reset() {}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function LoadProgressBar(_props) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function useMatches() {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=oneAdapter.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","oneAdapter_exports","Link","LoadProgressBar","Slot","getUserScheme","import_color_scheme","onUserSchemeChange","setUserScheme","useBlocker","useLoader","useLocalSearchParams","useMatches","useParams","usePathname","useRouter","useUserScheme","module","exports","import_jsx_runtime","require","import_react","import_navigator","_loader","tag","useContext","RouteTagContext","fromRoute","useSyncExternalStore","stackNavigator","subscribe","loaderSnapshot","ROUTER","push","navigate","replace","back","canGoBack","getSnapshot","dismiss","count","i","dismissAll","depth","canDismiss","setParams","listener","onLoadState","param","href","children","style","rest","isConfigured","hrefString","hrefToString","onClick","onClick2","event","preventDefault","jsx"],"sources":["../../../src/seam/oneAdapter.tsx"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/router",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "multiplatform.one router with support for react-router-dom and react-navigation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
"author": "BitSpur <support@risserlabs.com> (https://risserlabs.com)",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://gitlab.com/bitspur/multiplatform.one
|
|
18
|
+
"url": "git+https://gitlab.com/bitspur/frappe/multiplatform.one.git",
|
|
19
|
+
"directory": "public/router"
|
|
19
20
|
},
|
|
20
21
|
"source": "src/index.ts",
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
23
24
|
"src",
|
|
24
|
-
"types"
|
|
25
|
+
"types",
|
|
26
|
+
"!types/.tsbuildinfo"
|
|
25
27
|
],
|
|
26
28
|
"sideEffects": false,
|
|
27
29
|
"main": "src/index.ts",
|
|
@@ -31,21 +33,35 @@
|
|
|
31
33
|
"./package.json": "./package.json",
|
|
32
34
|
".": {
|
|
33
35
|
"react-native": {
|
|
36
|
+
"types": "./types/index.d.ts",
|
|
34
37
|
"import": "./dist/esm/index.native.js",
|
|
35
38
|
"require": "./dist/cjs/index.native.js"
|
|
36
39
|
},
|
|
37
40
|
"source": "./src/index.ts",
|
|
38
41
|
"types": "./types/index.d.ts",
|
|
39
|
-
"import": "./dist/esm/index.
|
|
40
|
-
"require": "./dist/cjs/index.
|
|
42
|
+
"import": "./dist/esm/index.mjs",
|
|
43
|
+
"require": "./dist/cjs/index.cjs",
|
|
41
44
|
"default": "./dist/cjs/index.native.js"
|
|
45
|
+
},
|
|
46
|
+
"./seam": {
|
|
47
|
+
"react-native": {
|
|
48
|
+
"types": "./types/seam/index.d.ts",
|
|
49
|
+
"import": "./dist/esm/seam/index.native.js",
|
|
50
|
+
"require": "./dist/cjs/seam/index.native.js"
|
|
51
|
+
},
|
|
52
|
+
"source": "./src/seam/index.ts",
|
|
53
|
+
"types": "./types/seam/index.d.ts",
|
|
54
|
+
"import": "./dist/esm/seam/index.mjs",
|
|
55
|
+
"require": "./dist/cjs/seam/index.cjs",
|
|
56
|
+
"default": "./dist/cjs/seam/index.native.js"
|
|
42
57
|
}
|
|
43
58
|
},
|
|
44
59
|
"publishConfig": {
|
|
45
60
|
"access": "public"
|
|
46
61
|
},
|
|
47
62
|
"dependencies": {
|
|
48
|
-
"@
|
|
63
|
+
"@vxrn/color-scheme": "^1.16.5",
|
|
64
|
+
"@multiplatform.one/platform": "6.3.0"
|
|
49
65
|
},
|
|
50
66
|
"devDependencies": {
|
|
51
67
|
"@react-navigation/native": "^7.2.2",
|
|
@@ -60,10 +76,11 @@
|
|
|
60
76
|
"typescript": "~5.9.3",
|
|
61
77
|
"vite": "^8.0.10",
|
|
62
78
|
"vitest": "^4.1.5",
|
|
63
|
-
"@multiplatform.one/
|
|
64
|
-
"@multiplatform.one/
|
|
79
|
+
"@multiplatform.one/config": "6.3.0",
|
|
80
|
+
"@multiplatform.one/test-utils": "6.3.0"
|
|
65
81
|
},
|
|
66
82
|
"peerDependencies": {
|
|
83
|
+
"@react-navigation/native": "~7.2.2",
|
|
67
84
|
"one": "^1.4.10",
|
|
68
85
|
"react": "^19.1.0",
|
|
69
86
|
"react-router-dom": "^7.0.0"
|
|
@@ -73,7 +90,7 @@
|
|
|
73
90
|
},
|
|
74
91
|
"module:jsx": "dist/jsx",
|
|
75
92
|
"scripts": {
|
|
76
|
-
"build": "rm -rf dist types 2>/dev/null && tamagui-build --ts-project ./tsconfig.build.json",
|
|
93
|
+
"build": "rm -rf dist types 2>/dev/null && tamagui-build --ts-project ./tsconfig.build.json --exclude '\\.spec\\.|\\.test\\.|\\.stories\\.'",
|
|
77
94
|
"test": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text --coverage.reporter=lcov --coverage.reporter=html",
|
|
78
95
|
"typecheck": "tsc --noEmit"
|
|
79
96
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-state edge contracts not covered by useUrlState.spec.ts:
|
|
3
|
+
* `useShareableUrl` (share-link building from current params) and the
|
|
4
|
+
* defensive deserialize branches of `useUrlState` (malformed JSON falls back
|
|
5
|
+
* to defaults, scalar→array coercion for array-typed defaults).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { renderHook } from "@testing-library/react";
|
|
9
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import * as router from "../router";
|
|
11
|
+
import { useShareableUrl, useUrlState } from "./useUrlState";
|
|
12
|
+
|
|
13
|
+
vi.mock("../router", () => ({
|
|
14
|
+
useParams: vi.fn(() => ({})),
|
|
15
|
+
useRouter: vi.fn(() => ({ setParams: vi.fn() })),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const mockUseParams = router.useParams as unknown as ReturnType<typeof vi.fn>;
|
|
19
|
+
const mockUseRouter = router.useRouter as unknown as ReturnType<typeof vi.fn>;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
mockUseParams.mockReturnValue({});
|
|
24
|
+
mockUseRouter.mockReturnValue({ setParams: vi.fn() });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("useShareableUrl", () => {
|
|
28
|
+
it("builds an absolute URL carrying the current params as query string", () => {
|
|
29
|
+
mockUseParams.mockReturnValue({ tab: "settings", page: "2" });
|
|
30
|
+
const { result } = renderHook(() => useShareableUrl());
|
|
31
|
+
const url = new URL(result.current);
|
|
32
|
+
expect(url.origin).toBe(new URL(window.location.href).origin);
|
|
33
|
+
expect(url.searchParams.get("tab")).toBe("settings");
|
|
34
|
+
expect(url.searchParams.get("page")).toBe("2");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("repeats array params as multiple query entries", () => {
|
|
38
|
+
mockUseParams.mockReturnValue({ tags: ["a", "b"] });
|
|
39
|
+
const { result } = renderHook(() => useShareableUrl());
|
|
40
|
+
const url = new URL(result.current);
|
|
41
|
+
expect(url.searchParams.getAll("tags")).toEqual(["a", "b"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("drops empty and undefined params from the shared link", () => {
|
|
45
|
+
mockUseParams.mockReturnValue({ keep: "yes", empty: "", missing: undefined });
|
|
46
|
+
const { result } = renderHook(() => useShareableUrl());
|
|
47
|
+
const url = new URL(result.current);
|
|
48
|
+
expect(url.searchParams.get("keep")).toBe("yes");
|
|
49
|
+
expect(url.searchParams.has("empty")).toBe(false);
|
|
50
|
+
expect(url.searchParams.has("missing")).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("replaces any existing location query instead of appending to it", () => {
|
|
54
|
+
mockUseParams.mockReturnValue({ only: "param" });
|
|
55
|
+
const { result } = renderHook(() => useShareableUrl());
|
|
56
|
+
const url = new URL(result.current);
|
|
57
|
+
expect([...url.searchParams.keys()]).toEqual(["only"]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("falls back to the bare URL when the router host throws", () => {
|
|
61
|
+
mockUseParams.mockImplementation(() => {
|
|
62
|
+
throw new Error("no router context");
|
|
63
|
+
});
|
|
64
|
+
const { result } = renderHook(() => useShareableUrl());
|
|
65
|
+
const url = new URL(result.current);
|
|
66
|
+
expect([...url.searchParams.keys()]).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("useUrlState deserialize edge cases", () => {
|
|
71
|
+
it("falls back to the default when an object param carries malformed JSON", () => {
|
|
72
|
+
mockUseParams.mockReturnValue({ config: "{not-json" });
|
|
73
|
+
const defaults = { config: { theme: "light" } };
|
|
74
|
+
const { result } = renderHook(() => useUrlState(defaults));
|
|
75
|
+
expect(result.current[0]).toEqual({ config: { theme: "light" } });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("coerces a single string into an array for array-typed defaults", () => {
|
|
79
|
+
mockUseParams.mockReturnValue({ tags: "solo" });
|
|
80
|
+
const defaults = { tags: [] as string[] };
|
|
81
|
+
const { result } = renderHook(() => useUrlState(defaults));
|
|
82
|
+
expect(result.current[0]).toEqual({ tags: ["solo"] });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("parses 'false' strings into boolean false for boolean defaults", () => {
|
|
86
|
+
mockUseParams.mockReturnValue({ active: "false" });
|
|
87
|
+
const defaults = { active: true };
|
|
88
|
+
const { result } = renderHook(() => useUrlState(defaults));
|
|
89
|
+
expect(result.current[0]).toEqual({ active: false });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("ignores params that have no matching default key", () => {
|
|
93
|
+
mockUseParams.mockReturnValue({ unknown: "x", name: "known" });
|
|
94
|
+
const defaults = { name: "" };
|
|
95
|
+
const { result } = renderHook(() => useUrlState(defaults));
|
|
96
|
+
expect(result.current[0]).toEqual({ name: "known" });
|
|
97
|
+
});
|
|
98
|
+
});
|
package/src/hooks/useUrlState.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { isWeb } from "@multiplatform.one/platform";
|
|
3
3
|
import { useParams, useRouter } from "../router";
|
|
4
4
|
|
|
@@ -9,6 +9,27 @@ export interface SetSearchParamsOptions {
|
|
|
9
9
|
debounceMs?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// The router is optional. Hosts that have none (storybook-expo, expo go) resolve these
|
|
13
|
+
// bindings to undefined, so treat a missing or throwing router as "no url sync" instead
|
|
14
|
+
// of crashing at mount. The branch is decided once at module load, so hook order is stable.
|
|
15
|
+
const hasRouter = typeof useRouter === "function" && typeof useParams === "function";
|
|
16
|
+
|
|
17
|
+
const emptyParams: SearchParams = {};
|
|
18
|
+
|
|
19
|
+
interface OptionalRouter {
|
|
20
|
+
router?: ReturnType<typeof useRouter>;
|
|
21
|
+
params?: SearchParams;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function useOptionalRouter(): OptionalRouter {
|
|
25
|
+
if (!hasRouter) return {};
|
|
26
|
+
try {
|
|
27
|
+
return { router: useRouter(), params: useParams() as SearchParams };
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
12
33
|
/**
|
|
13
34
|
* Cross-platform hook for managing URL search parameters
|
|
14
35
|
* Works with both React Router (web) and React Navigation (native)
|
|
@@ -20,8 +41,9 @@ export function useSearchParams(): [
|
|
|
20
41
|
options?: SetSearchParamsOptions,
|
|
21
42
|
) => void,
|
|
22
43
|
] {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
44
|
+
const [localParams, setLocalParams] = useState<SearchParams>({});
|
|
45
|
+
const { router, params } = useOptionalRouter();
|
|
46
|
+
const currentParams = params ?? localParams;
|
|
25
47
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
26
48
|
|
|
27
49
|
// Parse current params into a more usable format
|
|
@@ -53,8 +75,12 @@ export function useSearchParams(): [
|
|
|
53
75
|
}
|
|
54
76
|
});
|
|
55
77
|
|
|
56
|
-
// Use router.setParams to update the URL
|
|
57
|
-
router
|
|
78
|
+
// Use router.setParams to update the URL, or keep it local when routerless
|
|
79
|
+
if (router && params) {
|
|
80
|
+
router.setParams(cleanParams as any);
|
|
81
|
+
} else {
|
|
82
|
+
setLocalParams(cleanParams);
|
|
83
|
+
}
|
|
58
84
|
};
|
|
59
85
|
|
|
60
86
|
if (debounceMs > 0) {
|
|
@@ -66,7 +92,7 @@ export function useSearchParams(): [
|
|
|
66
92
|
updateParams();
|
|
67
93
|
}
|
|
68
94
|
},
|
|
69
|
-
[router, searchParams],
|
|
95
|
+
[router, params, searchParams],
|
|
70
96
|
);
|
|
71
97
|
|
|
72
98
|
// Cleanup debounce timer on unmount
|
|
@@ -172,7 +198,7 @@ export function useUrlState<T extends Record<string, any>>(
|
|
|
172
198
|
* Hook for creating shareable URLs with current state
|
|
173
199
|
*/
|
|
174
200
|
export function useShareableUrl(): string {
|
|
175
|
-
const params =
|
|
201
|
+
const params = useOptionalRouter().params ?? emptyParams;
|
|
176
202
|
|
|
177
203
|
const shareableUrl = useMemo(() => {
|
|
178
204
|
if (!isWeb || typeof window === "undefined") {
|
|
@@ -207,7 +233,7 @@ export function useTypedSearchParams<T extends Record<string, any>>(schema: {
|
|
|
207
233
|
parse?: (value: string | string[] | undefined) => T[K];
|
|
208
234
|
};
|
|
209
235
|
}): T {
|
|
210
|
-
const params =
|
|
236
|
+
const params = useOptionalRouter().params ?? emptyParams;
|
|
211
237
|
|
|
212
238
|
const typedParams = useMemo(() => {
|
|
213
239
|
const result: any = {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { act, render, screen } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { DomNavigationHost } from "./DomNavigationHost";
|
|
4
|
+
import { stackNavigator } from "./navigator";
|
|
5
|
+
import { useParams } from "./oneAdapter";
|
|
6
|
+
|
|
7
|
+
const ROUTES = [
|
|
8
|
+
{ pattern: "/", tag: "home", title: "Home" },
|
|
9
|
+
{ pattern: "/todos", tag: "todos", title: "Todos" },
|
|
10
|
+
{ pattern: "/pokemon/[id]", tag: "pokemon-detail", title: "Detail" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function DetailProbe() {
|
|
14
|
+
const params = useParams();
|
|
15
|
+
return <span data-testid="detail-params">{String(params.id ?? "")}</span>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderHost(initialHref?: string) {
|
|
19
|
+
stackNavigator.detach();
|
|
20
|
+
stackNavigator.configure(ROUTES);
|
|
21
|
+
return render(
|
|
22
|
+
<DomNavigationHost
|
|
23
|
+
initialHref={initialHref}
|
|
24
|
+
routes={[
|
|
25
|
+
{ ...ROUTES[0]!, element: <div data-testid="home-screen" /> },
|
|
26
|
+
{ ...ROUTES[1]!, element: <div data-testid="todos-screen" /> },
|
|
27
|
+
{ ...ROUTES[2]!, element: <DetailProbe /> },
|
|
28
|
+
]}
|
|
29
|
+
/>,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pageByTag(container: HTMLElement, tag: string): HTMLElement {
|
|
34
|
+
const page = container.querySelector(`[data-seam-page="${tag}"]`);
|
|
35
|
+
expect(page).not.toBeNull();
|
|
36
|
+
return page as HTMLElement;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("DomNavigationHost", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
stackNavigator.detach();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("mounts content lazily and keeps backgrounded pages mounted", () => {
|
|
45
|
+
const { container } = renderHost();
|
|
46
|
+
expect(screen.getByTestId("home-screen")).toBeTruthy();
|
|
47
|
+
// Unvisited pages are bare shells.
|
|
48
|
+
expect(screen.queryByTestId("todos-screen")).toBeNull();
|
|
49
|
+
expect(pageByTag(container, "home").dataset.seamActive).toBe("true");
|
|
50
|
+
|
|
51
|
+
act(() => stackNavigator.push("/todos"));
|
|
52
|
+
expect(screen.getByTestId("todos-screen")).toBeTruthy();
|
|
53
|
+
expect(pageByTag(container, "todos").dataset.seamActive).toBe("true");
|
|
54
|
+
expect(pageByTag(container, "home").dataset.seamActive).toBe("false");
|
|
55
|
+
// Backgrounded page keeps its content mounted (One stack semantics).
|
|
56
|
+
expect(screen.getByTestId("home-screen")).toBeTruthy();
|
|
57
|
+
|
|
58
|
+
act(() => stackNavigator.back());
|
|
59
|
+
expect(pageByTag(container, "home").dataset.seamActive).toBe("true");
|
|
60
|
+
expect(screen.getByTestId("todos-screen")).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("provides per-page params through RouteTagContext", () => {
|
|
64
|
+
renderHost();
|
|
65
|
+
act(() => stackNavigator.push("/pokemon/mew"));
|
|
66
|
+
expect(screen.getByTestId("detail-params").textContent).toBe("mew");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("restores an initial route as a root replace", () => {
|
|
70
|
+
const { container } = renderHost("/todos");
|
|
71
|
+
expect(pageByTag(container, "todos").dataset.seamActive).toBe("true");
|
|
72
|
+
expect(stackNavigator.getSnapshot()).toMatchObject({
|
|
73
|
+
tag: "todos",
|
|
74
|
+
depth: 1,
|
|
75
|
+
canGoBack: false,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// DOM navigation host — renders the route manifest as a stack of DOM
|
|
2
|
+
// pages, one per route, driving the same seam the GTK host drives.
|
|
3
|
+
//
|
|
4
|
+
// The DOM equivalent of apps/one/gnome/navigation-host.tsx: every route
|
|
5
|
+
// gets a permanent page wrapper (so the host stack and the seam mirror
|
|
6
|
+
// stay aligned), the page's React CONTENT mounts on the route's first
|
|
7
|
+
// visit (stackNavigator.isVisited) and stays mounted from then on —
|
|
8
|
+
// backgrounded pages keep their state, the same lifecycle One gives stack
|
|
9
|
+
// screens on web/native. Only the top-of-stack page is visible; the rest
|
|
10
|
+
// are display:none.
|
|
11
|
+
//
|
|
12
|
+
// Usage (mirrors the GTK entries):
|
|
13
|
+
//
|
|
14
|
+
// stackNavigator.configure(ROUTES); // before render
|
|
15
|
+
// <DomNavigationHost
|
|
16
|
+
// routes={ROUTES.map((r) => ({ ...r, element: <Screen /> }))}
|
|
17
|
+
// initialHref={restoredRoute} // optional state restore
|
|
18
|
+
// />
|
|
19
|
+
//
|
|
20
|
+
// Stack state flows host → seam: every adapter mutation calls
|
|
21
|
+
// stackNavigator.syncFromHost(), exactly like the GTK pushed/popped/
|
|
22
|
+
// replaced signal handlers.
|
|
23
|
+
|
|
24
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
25
|
+
import { Suspense, useEffect, useRef } from "react";
|
|
26
|
+
import {
|
|
27
|
+
RouteTagContext,
|
|
28
|
+
stackNavigator,
|
|
29
|
+
useNavState,
|
|
30
|
+
type Href,
|
|
31
|
+
type NavStackAdapter,
|
|
32
|
+
type RouteDef,
|
|
33
|
+
} from "./navigator";
|
|
34
|
+
|
|
35
|
+
/** In-memory implementation of the host-stack protocol (the seam's
|
|
36
|
+
* NavStackAdapter mirrors Adw.NavigationView's method surface — see
|
|
37
|
+
* navigator.ts). */
|
|
38
|
+
export class DomStackAdapter implements NavStackAdapter {
|
|
39
|
+
private stack: string[];
|
|
40
|
+
private readonly onChanged: () => void;
|
|
41
|
+
|
|
42
|
+
constructor(rootTag: string, onChanged: () => void) {
|
|
43
|
+
this.stack = rootTag ? [rootTag] : [];
|
|
44
|
+
this.onChanged = onChanged;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
push_by_tag(tag: string): void {
|
|
48
|
+
this.stack.push(tag);
|
|
49
|
+
this.onChanged();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pop(): boolean {
|
|
53
|
+
if (this.stack.length <= 1) return false;
|
|
54
|
+
this.stack.pop();
|
|
55
|
+
this.onChanged();
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pop_to_tag(tag: string): boolean {
|
|
60
|
+
const idx = this.stack.lastIndexOf(tag);
|
|
61
|
+
if (idx === -1 || idx === this.stack.length - 1) return false;
|
|
62
|
+
this.stack = this.stack.slice(0, idx + 1);
|
|
63
|
+
this.onChanged();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
replace_with_tags(tags: string[]): void {
|
|
68
|
+
this.stack = [...tags];
|
|
69
|
+
this.onChanged();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get_navigation_stack(): {
|
|
73
|
+
get_n_items(): number;
|
|
74
|
+
get_item(index: number): { get_tag(): string | null } | null;
|
|
75
|
+
} {
|
|
76
|
+
const snapshot = this.stack;
|
|
77
|
+
return {
|
|
78
|
+
get_n_items: () => snapshot.length,
|
|
79
|
+
get_item: (index: number) =>
|
|
80
|
+
index >= 0 && index < snapshot.length ? { get_tag: () => snapshot[index] ?? null } : null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DomRouteEntry extends RouteDef {
|
|
86
|
+
element: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface DomNavigationHostProps {
|
|
90
|
+
routes: DomRouteEntry[];
|
|
91
|
+
/** Restore a previous route on mount (e.g. from vscode.getState()) —
|
|
92
|
+
* applied as a root replace, matching MemoryRouter initialEntries
|
|
93
|
+
* semantics (no back stack to it). */
|
|
94
|
+
initialHref?: Href;
|
|
95
|
+
style?: CSSProperties;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const PAGE_STYLE: CSSProperties = {
|
|
99
|
+
position: "absolute",
|
|
100
|
+
inset: 0,
|
|
101
|
+
display: "flex",
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
overflow: "auto",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export function DomNavigationHost({
|
|
107
|
+
routes,
|
|
108
|
+
initialHref,
|
|
109
|
+
style,
|
|
110
|
+
}: DomNavigationHostProps): ReactNode {
|
|
111
|
+
const adapterRef = useRef<DomStackAdapter | null>(null);
|
|
112
|
+
adapterRef.current ??= new DomStackAdapter(routes[0]?.tag ?? "", () =>
|
|
113
|
+
stackNavigator.syncFromHost(),
|
|
114
|
+
);
|
|
115
|
+
// Mount-time value only — reattaching on href identity changes would
|
|
116
|
+
// reset the live stack.
|
|
117
|
+
const initialHrefRef = useRef(initialHref);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const adapter = adapterRef.current;
|
|
121
|
+
if (!adapter) return;
|
|
122
|
+
stackNavigator.attach(adapter);
|
|
123
|
+
const restore = initialHrefRef.current;
|
|
124
|
+
if (restore) stackNavigator.replace(restore);
|
|
125
|
+
return () => stackNavigator.detach();
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
const nav = useNavState();
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div style={{ position: "relative", flex: 1, minHeight: 0, height: "100%", ...style }}>
|
|
132
|
+
{routes.map((route) => {
|
|
133
|
+
const active = nav.tag === route.tag;
|
|
134
|
+
const mounted = stackNavigator.isVisited(route.tag);
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
key={route.tag}
|
|
138
|
+
data-seam-page={route.tag}
|
|
139
|
+
data-seam-active={active ? "true" : "false"}
|
|
140
|
+
style={{ ...PAGE_STYLE, display: active ? "flex" : "none" }}
|
|
141
|
+
>
|
|
142
|
+
<RouteTagContext.Provider value={route.tag}>
|
|
143
|
+
<Suspense fallback={null}>{mounted ? route.element : null}</Suspense>
|
|
144
|
+
</RouteTagContext.Provider>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
})}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|