solid-navigator 0.0.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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/dev.js +314 -0
- package/dist/dev.jsx +319 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +311 -0
- package/dist/index.jsx +316 -0
- package/package.json +82 -0
package/dist/index.jsx
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// src/Router.tsx
|
|
2
|
+
import {
|
|
3
|
+
children,
|
|
4
|
+
createContext,
|
|
5
|
+
createMemo as createMemo2,
|
|
6
|
+
createSignal,
|
|
7
|
+
onCleanup,
|
|
8
|
+
onMount,
|
|
9
|
+
useContext
|
|
10
|
+
} from "solid-js";
|
|
11
|
+
import { createStore as createStore2, reconcile as reconcile2 } from "solid-js/store";
|
|
12
|
+
|
|
13
|
+
// src/utils/matcher.ts
|
|
14
|
+
function createMatcher(path, partial, matchFilters) {
|
|
15
|
+
const [pattern, splat] = path.split("/*", 2);
|
|
16
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
17
|
+
const len = segments.length;
|
|
18
|
+
return (location2) => {
|
|
19
|
+
const locSegments = location2.split("/").filter(Boolean);
|
|
20
|
+
const lenDiff = locSegments.length - len;
|
|
21
|
+
if (lenDiff < 0 || lenDiff > 0 && splat === void 0 && !partial) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const match = {
|
|
25
|
+
path: len ? "" : "/",
|
|
26
|
+
params: {}
|
|
27
|
+
};
|
|
28
|
+
const matchFilter = (s) => matchFilters === void 0 ? void 0 : matchFilters[s];
|
|
29
|
+
for (let i = 0; i < len; i++) {
|
|
30
|
+
const segment = segments[i];
|
|
31
|
+
const locSegment = locSegments[i];
|
|
32
|
+
const dynamic = segment[0] === ":";
|
|
33
|
+
const key = dynamic ? segment.slice(1) : segment;
|
|
34
|
+
if (dynamic && matchSegment(locSegment, matchFilter(key))) {
|
|
35
|
+
match.params[key] = locSegment;
|
|
36
|
+
} else if (dynamic || !matchSegment(locSegment, segment)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
match.path += `/${locSegment}`;
|
|
40
|
+
}
|
|
41
|
+
if (splat) {
|
|
42
|
+
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
43
|
+
if (matchSegment(remainder, matchFilter(splat))) {
|
|
44
|
+
match.params[splat] = remainder;
|
|
45
|
+
} else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return match;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function matchSegment(input, filter) {
|
|
53
|
+
const isEqual = (s) => s.localeCompare(input, void 0, { sensitivity: "base" }) === 0;
|
|
54
|
+
if (filter === void 0) {
|
|
55
|
+
return true;
|
|
56
|
+
} else if (typeof filter === "string") {
|
|
57
|
+
return isEqual(filter);
|
|
58
|
+
} else if (typeof filter === "function") {
|
|
59
|
+
return filter(input);
|
|
60
|
+
} else if (Array.isArray(filter)) {
|
|
61
|
+
return filter.some(isEqual);
|
|
62
|
+
} else if (filter instanceof RegExp) {
|
|
63
|
+
return filter.test(input);
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/createLocation.ts
|
|
69
|
+
import { createEffect, createMemo, on } from "solid-js";
|
|
70
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
71
|
+
var createLocation = (path) => {
|
|
72
|
+
const [query, setQuery] = createStore({});
|
|
73
|
+
const url = createMemo(() => {
|
|
74
|
+
return new URL(path(), "http://owo");
|
|
75
|
+
});
|
|
76
|
+
createEffect(
|
|
77
|
+
on(url, () => {
|
|
78
|
+
const newQuery = {};
|
|
79
|
+
url().searchParams.forEach((value, key) => {
|
|
80
|
+
newQuery[key] = value;
|
|
81
|
+
});
|
|
82
|
+
setQuery(reconcile(newQuery));
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
const search = createMemo(() => url().search);
|
|
86
|
+
const pathname = createMemo(() => url().pathname);
|
|
87
|
+
const hash = createMemo(() => url().hash);
|
|
88
|
+
return {
|
|
89
|
+
query,
|
|
90
|
+
get search() {
|
|
91
|
+
return search();
|
|
92
|
+
},
|
|
93
|
+
get pathname() {
|
|
94
|
+
return pathname();
|
|
95
|
+
},
|
|
96
|
+
get hash() {
|
|
97
|
+
return hash();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/utils/utils.ts
|
|
103
|
+
var isValidPath = (routes, pathname) => {
|
|
104
|
+
return routes().find((route) => {
|
|
105
|
+
const matcher = createMatcher(route.path);
|
|
106
|
+
return matcher(pathname);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
var getHashAndSearch = () => location.hash + location.search;
|
|
110
|
+
|
|
111
|
+
// src/Router.tsx
|
|
112
|
+
var RouterContext = createContext();
|
|
113
|
+
function Router(props) {
|
|
114
|
+
const childRoutes = children(() => props.children).toArray;
|
|
115
|
+
const routes = createMemo2(() => flattenedRoutes(childRoutes()));
|
|
116
|
+
if (!props.children) {
|
|
117
|
+
throw new Error("Router: No children provided.");
|
|
118
|
+
}
|
|
119
|
+
const [pathname, setPathname] = createSignal(location.pathname);
|
|
120
|
+
const [hashAndSearch, setHashAndSearch] = createSignal(getHashAndSearch());
|
|
121
|
+
const [params, setParams] = createStore2({});
|
|
122
|
+
const pathnameWithHashAndSearch = createMemo2(() => pathname() + hashAndSearch());
|
|
123
|
+
const loc = createLocation(pathnameWithHashAndSearch);
|
|
124
|
+
const matched = createMemo2(() => {
|
|
125
|
+
if (!routes())
|
|
126
|
+
return;
|
|
127
|
+
let pathMatch = null;
|
|
128
|
+
let matchedRoute;
|
|
129
|
+
for (const route of routes()) {
|
|
130
|
+
const matcher = createMatcher(route.path);
|
|
131
|
+
const match = matcher(pathname());
|
|
132
|
+
if (match) {
|
|
133
|
+
pathMatch = match;
|
|
134
|
+
matchedRoute = route;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
setParams(reconcile2(pathMatch?.params || {}));
|
|
139
|
+
if (!matchedRoute || !pathMatch) {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
return { match: pathMatch, route: matchedRoute };
|
|
143
|
+
});
|
|
144
|
+
const navigate = createNavigate(routes, pathname, setPathname, setHashAndSearch);
|
|
145
|
+
const onPopState = (_event) => {
|
|
146
|
+
setPathname(location.pathname);
|
|
147
|
+
setHashAndSearch(getHashAndSearch());
|
|
148
|
+
};
|
|
149
|
+
const onClick = (event) => {
|
|
150
|
+
const target = event.target;
|
|
151
|
+
if (target.tagName !== "A")
|
|
152
|
+
return;
|
|
153
|
+
if (!target.hasAttribute("sn-link"))
|
|
154
|
+
return;
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
const href = target.getAttribute("href") || "";
|
|
157
|
+
navigate(href, {
|
|
158
|
+
replace: target.hasAttribute("replace")
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
onMount(() => {
|
|
162
|
+
window.addEventListener("popstate", onPopState);
|
|
163
|
+
document.addEventListener("click", onClick);
|
|
164
|
+
onCleanup(() => {
|
|
165
|
+
window.removeEventListener("popstate", onPopState);
|
|
166
|
+
document.removeEventListener("click", onClick);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
return <RouterContext.Provider
|
|
170
|
+
value={{ routes, matched, navigate, params, location: loc, setHashAndSearch, setPathname }}
|
|
171
|
+
>{props.root?.()}</RouterContext.Provider>;
|
|
172
|
+
}
|
|
173
|
+
var useRouterContext = () => {
|
|
174
|
+
const context = useContext(RouterContext);
|
|
175
|
+
if (!context) {
|
|
176
|
+
throw new Error("Router: cannot find a RouterContext");
|
|
177
|
+
}
|
|
178
|
+
return context;
|
|
179
|
+
};
|
|
180
|
+
function useParams() {
|
|
181
|
+
const context = useRouterContext();
|
|
182
|
+
return context.params;
|
|
183
|
+
}
|
|
184
|
+
var useLocation = () => useRouterContext().location;
|
|
185
|
+
var flattenedRoutes = (routes) => {
|
|
186
|
+
return routes.map((route) => flattenedRoute(route)).flat();
|
|
187
|
+
};
|
|
188
|
+
var flattenedRoute = (route) => {
|
|
189
|
+
const routes = [];
|
|
190
|
+
const components = route.components || {};
|
|
191
|
+
let lastComponent = void 0;
|
|
192
|
+
if (route.component) {
|
|
193
|
+
lastComponent = route.component;
|
|
194
|
+
}
|
|
195
|
+
routes.push({
|
|
196
|
+
...route,
|
|
197
|
+
components: { ...components },
|
|
198
|
+
mergedComponents: components,
|
|
199
|
+
component: route.component || lastComponent
|
|
200
|
+
});
|
|
201
|
+
if (!route.children)
|
|
202
|
+
return routes;
|
|
203
|
+
for (let i = 0; i < route.children.length; i++) {
|
|
204
|
+
const child = route.children[i];
|
|
205
|
+
if (!child)
|
|
206
|
+
continue;
|
|
207
|
+
if (child.components) {
|
|
208
|
+
Object.assign(components, child.components);
|
|
209
|
+
}
|
|
210
|
+
if (child.component) {
|
|
211
|
+
lastComponent = child.component;
|
|
212
|
+
}
|
|
213
|
+
if (!child.path.startsWith("/")) {
|
|
214
|
+
if (!child.component) {
|
|
215
|
+
throw new Error("Route: No component for " + child.path);
|
|
216
|
+
}
|
|
217
|
+
components[child.path] = child.component;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
routes.push(
|
|
221
|
+
...flattenedRoute({
|
|
222
|
+
...child,
|
|
223
|
+
path: route.path + child.path,
|
|
224
|
+
components: { ...components },
|
|
225
|
+
mergedComponents: components,
|
|
226
|
+
component: child.component || lastComponent
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return routes;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/navigator.ts
|
|
234
|
+
var createNavigate = (routes, pathname, setPathname, setHashAndSearch) => {
|
|
235
|
+
return (path, options) => {
|
|
236
|
+
let newPath = path;
|
|
237
|
+
let currentPathname = pathname();
|
|
238
|
+
if (currentPathname.endsWith("/")) {
|
|
239
|
+
currentPathname = currentPathname.slice(0, -1);
|
|
240
|
+
}
|
|
241
|
+
if (newPath.startsWith("./")) {
|
|
242
|
+
newPath = currentPathname + "/" + newPath.slice(2);
|
|
243
|
+
}
|
|
244
|
+
if (options?.replace) {
|
|
245
|
+
history.replaceState({}, "", newPath);
|
|
246
|
+
} else {
|
|
247
|
+
history.pushState({}, "", newPath);
|
|
248
|
+
}
|
|
249
|
+
if (!isValidPath(routes, location.pathname)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
setPathname(location.pathname);
|
|
253
|
+
setHashAndSearch(getHashAndSearch());
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
var useNavigate = () => {
|
|
257
|
+
const context = useRouterContext();
|
|
258
|
+
return context.navigate;
|
|
259
|
+
};
|
|
260
|
+
var Navigate = (props) => {
|
|
261
|
+
const navigate = useNavigate();
|
|
262
|
+
navigate(props.href, { replace: true });
|
|
263
|
+
return null;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/Outlet.tsx
|
|
267
|
+
import { createMemo as createMemo3 } from "solid-js";
|
|
268
|
+
var Fragment = () => <></>;
|
|
269
|
+
var Outlet = (props) => {
|
|
270
|
+
const context = useRouterContext();
|
|
271
|
+
const matched = () => context.matched();
|
|
272
|
+
const getName = () => props.name || props.children;
|
|
273
|
+
const component = createMemo3(() => {
|
|
274
|
+
const name = getName();
|
|
275
|
+
if (!name) {
|
|
276
|
+
const rootComponent = matched()?.route.component;
|
|
277
|
+
if (!rootComponent) {
|
|
278
|
+
return Fragment;
|
|
279
|
+
}
|
|
280
|
+
return rootComponent;
|
|
281
|
+
}
|
|
282
|
+
const components = context.matched()?.route.components || context.matched()?.route.mergedComponents || {};
|
|
283
|
+
const component2 = components[name];
|
|
284
|
+
if (!component2) {
|
|
285
|
+
return Fragment;
|
|
286
|
+
}
|
|
287
|
+
return component2;
|
|
288
|
+
});
|
|
289
|
+
return <>{component}</>;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// src/Route.ts
|
|
293
|
+
import { children as children2, mergeProps } from "solid-js";
|
|
294
|
+
var Route = (props) => {
|
|
295
|
+
const childRoutes = children2(() => props.children).toArray;
|
|
296
|
+
return mergeProps(props, {
|
|
297
|
+
get children() {
|
|
298
|
+
return childRoutes();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/Link.tsx
|
|
304
|
+
var A = (props) => {
|
|
305
|
+
return <a sn-link {...props} />;
|
|
306
|
+
};
|
|
307
|
+
export {
|
|
308
|
+
A,
|
|
309
|
+
Navigate,
|
|
310
|
+
Outlet,
|
|
311
|
+
Route,
|
|
312
|
+
Router,
|
|
313
|
+
useLocation,
|
|
314
|
+
useNavigate,
|
|
315
|
+
useParams
|
|
316
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "solid-navigator",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Solid Navigator is a library that is inspired by vue router and solid router.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SupertigerDev",
|
|
7
|
+
"contributors": [],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/SupertigerDev/solid-navigator.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/SupertigerDev/solid-navigator#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SupertigerDev/solid-navigator/issues"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"private": false,
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"browser": {},
|
|
26
|
+
"exports": {
|
|
27
|
+
"solid": {
|
|
28
|
+
"development": "./dist/dev.jsx",
|
|
29
|
+
"import": "./dist/index.jsx"
|
|
30
|
+
},
|
|
31
|
+
"development": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/dev.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"typesVersions": {},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"solid-js": "^1.6.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
48
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
49
|
+
"concurrently": "^8.2.2",
|
|
50
|
+
"esbuild": "^0.19.11",
|
|
51
|
+
"esbuild-plugin-solid": "^0.5.0",
|
|
52
|
+
"eslint": "^8.56.0",
|
|
53
|
+
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
54
|
+
"eslint-plugin-no-only-tests": "^3.1.0",
|
|
55
|
+
"jsdom": "^23.2.0",
|
|
56
|
+
"prettier": "3.2.4",
|
|
57
|
+
"solid-js": "^1.8.11",
|
|
58
|
+
"tsup": "^8.0.1",
|
|
59
|
+
"tsup-preset-solid": "^2.2.0",
|
|
60
|
+
"typescript": "^5.3.3",
|
|
61
|
+
"vite": "^5.0.11",
|
|
62
|
+
"vite-plugin-solid": "^2.8.1",
|
|
63
|
+
"vitest": "^1.2.1"
|
|
64
|
+
},
|
|
65
|
+
"keywords": [
|
|
66
|
+
"solid"
|
|
67
|
+
],
|
|
68
|
+
"packageManager": "pnpm@8.6.0",
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18",
|
|
71
|
+
"pnpm": ">=8.6.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "vite serve dev",
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"format": "prettier --ignore-path .gitignore -w \"src/**/*.{js,ts,json,css,tsx,jsx}\" \"dev/**/*.{js,ts,json,css,tsx,jsx}\"",
|
|
77
|
+
"lint": "concurrently pnpm:lint:*",
|
|
78
|
+
"lint:code": "eslint --ignore-path .gitignore --max-warnings 0 src/**/*.{js,ts,tsx,jsx}",
|
|
79
|
+
"lint:types": "tsc --noEmit",
|
|
80
|
+
"update-deps": "pnpm up -Li"
|
|
81
|
+
}
|
|
82
|
+
}
|