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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 SupertigerDev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img width="100%" src="https://assets.solidjs.com/banner?type=solid-navigator&background=tiles&project=%20" alt="solid-navigator">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# solid-navigator
|
|
6
|
+
|
|
7
|
+
[](https://pnpm.io/)
|
|
8
|
+
|
|
9
|
+
Solid Navigator is a library that is inspired by vue router and solid router.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
Install it:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i solid-navigator
|
|
18
|
+
# or
|
|
19
|
+
pnpm add solid-navigator
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use it:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useNavigate, Outlet, Route, Router, useLocation, useParams, A } from 'solid-navigator'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Methods
|
|
30
|
+
|
|
31
|
+
### `useNavigate`
|
|
32
|
+
```js
|
|
33
|
+
const navigate = useNavigate();
|
|
34
|
+
navigate("/app", {replace: true})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `useLocation`
|
|
38
|
+
```js
|
|
39
|
+
// path: /app?id=1
|
|
40
|
+
const location = useLocation();
|
|
41
|
+
{
|
|
42
|
+
query: {id: string}
|
|
43
|
+
search: string
|
|
44
|
+
pathname: string
|
|
45
|
+
hash: string
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `useParams`
|
|
50
|
+
```js
|
|
51
|
+
// path: /chats/:id
|
|
52
|
+
const params = useParams<{id: string}>();
|
|
53
|
+
{
|
|
54
|
+
id: string
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Components
|
|
59
|
+
|
|
60
|
+
### `Router`
|
|
61
|
+
```jsx
|
|
62
|
+
const Root = () => {
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<h1>Header</h1>
|
|
66
|
+
<Outlet/>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const Main = () => {
|
|
72
|
+
return (
|
|
73
|
+
<Router root={Root}>
|
|
74
|
+
// Routes go here
|
|
75
|
+
</Router>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `Outlet`
|
|
81
|
+
```jsx
|
|
82
|
+
const Main = () => {
|
|
83
|
+
|
|
84
|
+
const AppComponent = () => {
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<div><Outlet name="drawer"/></div>
|
|
88
|
+
<div><Outlet name="content"/></div>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Router root={Root}>
|
|
95
|
+
<Route path="/" component={AppComponent}>
|
|
96
|
+
<Route
|
|
97
|
+
components={{
|
|
98
|
+
drawer: Drawer,
|
|
99
|
+
content: Content
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
</Route>
|
|
103
|
+
</Router>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `A`
|
|
109
|
+
```tsx
|
|
110
|
+
const App = () => {
|
|
111
|
+
return (
|
|
112
|
+
<A href="/" replace />
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `Navigate`
|
|
118
|
+
```tsx
|
|
119
|
+
const App = () => {
|
|
120
|
+
return (
|
|
121
|
+
<Navigate to="/" />
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { createComponent, spread, template } from 'solid-js/web';
|
|
2
|
+
import { createContext, children, createMemo, createSignal, onMount, onCleanup, mergeProps, createEffect, on, useContext } from 'solid-js';
|
|
3
|
+
import { createStore, reconcile } from 'solid-js/store';
|
|
4
|
+
|
|
5
|
+
// src/Router.tsx
|
|
6
|
+
|
|
7
|
+
// src/utils/matcher.ts
|
|
8
|
+
function createMatcher(path, partial, matchFilters) {
|
|
9
|
+
const [pattern, splat] = path.split("/*", 2);
|
|
10
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
11
|
+
const len = segments.length;
|
|
12
|
+
return (location2) => {
|
|
13
|
+
const locSegments = location2.split("/").filter(Boolean);
|
|
14
|
+
const lenDiff = locSegments.length - len;
|
|
15
|
+
if (lenDiff < 0 || lenDiff > 0 && splat === void 0 && !partial) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const match = {
|
|
19
|
+
path: len ? "" : "/",
|
|
20
|
+
params: {}
|
|
21
|
+
};
|
|
22
|
+
const matchFilter = (s) => matchFilters === void 0 ? void 0 : matchFilters[s];
|
|
23
|
+
for (let i = 0; i < len; i++) {
|
|
24
|
+
const segment = segments[i];
|
|
25
|
+
const locSegment = locSegments[i];
|
|
26
|
+
const dynamic = segment[0] === ":";
|
|
27
|
+
const key = dynamic ? segment.slice(1) : segment;
|
|
28
|
+
if (dynamic && matchSegment(locSegment, matchFilter(key))) {
|
|
29
|
+
match.params[key] = locSegment;
|
|
30
|
+
} else if (dynamic || !matchSegment(locSegment, segment)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
match.path += `/${locSegment}`;
|
|
34
|
+
}
|
|
35
|
+
if (splat) {
|
|
36
|
+
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
37
|
+
if (matchSegment(remainder, matchFilter(splat))) {
|
|
38
|
+
match.params[splat] = remainder;
|
|
39
|
+
} else {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return match;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function matchSegment(input, filter) {
|
|
47
|
+
const isEqual = (s) => s.localeCompare(input, void 0, { sensitivity: "base" }) === 0;
|
|
48
|
+
if (filter === void 0) {
|
|
49
|
+
return true;
|
|
50
|
+
} else if (typeof filter === "string") {
|
|
51
|
+
return isEqual(filter);
|
|
52
|
+
} else if (typeof filter === "function") {
|
|
53
|
+
return filter(input);
|
|
54
|
+
} else if (Array.isArray(filter)) {
|
|
55
|
+
return filter.some(isEqual);
|
|
56
|
+
} else if (filter instanceof RegExp) {
|
|
57
|
+
return filter.test(input);
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
var createLocation = (path) => {
|
|
62
|
+
const [query, setQuery] = createStore({});
|
|
63
|
+
const url = createMemo(() => {
|
|
64
|
+
return new URL(path(), "http://owo");
|
|
65
|
+
});
|
|
66
|
+
createEffect(
|
|
67
|
+
on(url, () => {
|
|
68
|
+
const newQuery = {};
|
|
69
|
+
url().searchParams.forEach((value, key) => {
|
|
70
|
+
newQuery[key] = value;
|
|
71
|
+
});
|
|
72
|
+
setQuery(reconcile(newQuery));
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
const search = createMemo(() => url().search);
|
|
76
|
+
const pathname = createMemo(() => url().pathname);
|
|
77
|
+
const hash = createMemo(() => url().hash);
|
|
78
|
+
return {
|
|
79
|
+
query,
|
|
80
|
+
get search() {
|
|
81
|
+
return search();
|
|
82
|
+
},
|
|
83
|
+
get pathname() {
|
|
84
|
+
return pathname();
|
|
85
|
+
},
|
|
86
|
+
get hash() {
|
|
87
|
+
return hash();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/utils/utils.ts
|
|
93
|
+
var isValidPath = (routes, pathname) => {
|
|
94
|
+
return routes().find((route) => {
|
|
95
|
+
const matcher = createMatcher(route.path);
|
|
96
|
+
return matcher(pathname);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
var getHashAndSearch = () => location.hash + location.search;
|
|
100
|
+
|
|
101
|
+
// src/Router.tsx
|
|
102
|
+
var RouterContext = createContext();
|
|
103
|
+
function Router(props) {
|
|
104
|
+
const childRoutes = children(() => props.children).toArray;
|
|
105
|
+
const routes = createMemo(() => flattenedRoutes(childRoutes()));
|
|
106
|
+
if (!props.children) {
|
|
107
|
+
throw new Error("Router: No children provided.");
|
|
108
|
+
}
|
|
109
|
+
const [pathname, setPathname] = createSignal(location.pathname);
|
|
110
|
+
const [hashAndSearch, setHashAndSearch] = createSignal(getHashAndSearch());
|
|
111
|
+
const [params, setParams] = createStore({});
|
|
112
|
+
const pathnameWithHashAndSearch = createMemo(() => pathname() + hashAndSearch());
|
|
113
|
+
const loc = createLocation(pathnameWithHashAndSearch);
|
|
114
|
+
const matched = createMemo(() => {
|
|
115
|
+
if (!routes())
|
|
116
|
+
return;
|
|
117
|
+
let pathMatch = null;
|
|
118
|
+
let matchedRoute;
|
|
119
|
+
for (const route of routes()) {
|
|
120
|
+
const matcher = createMatcher(route.path);
|
|
121
|
+
const match = matcher(pathname());
|
|
122
|
+
if (match) {
|
|
123
|
+
pathMatch = match;
|
|
124
|
+
matchedRoute = route;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
setParams(reconcile(pathMatch?.params || {}));
|
|
129
|
+
if (!matchedRoute || !pathMatch) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
match: pathMatch,
|
|
134
|
+
route: matchedRoute
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
const navigate = createNavigate(routes, pathname, setPathname, setHashAndSearch);
|
|
138
|
+
const onPopState = (_event) => {
|
|
139
|
+
setPathname(location.pathname);
|
|
140
|
+
setHashAndSearch(getHashAndSearch());
|
|
141
|
+
};
|
|
142
|
+
const onClick = (event) => {
|
|
143
|
+
const target = event.target;
|
|
144
|
+
if (target.tagName !== "A")
|
|
145
|
+
return;
|
|
146
|
+
if (!target.hasAttribute("sn-link"))
|
|
147
|
+
return;
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
const href = target.getAttribute("href") || "";
|
|
150
|
+
navigate(href, {
|
|
151
|
+
replace: target.hasAttribute("replace")
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
onMount(() => {
|
|
155
|
+
window.addEventListener("popstate", onPopState);
|
|
156
|
+
document.addEventListener("click", onClick);
|
|
157
|
+
onCleanup(() => {
|
|
158
|
+
window.removeEventListener("popstate", onPopState);
|
|
159
|
+
document.removeEventListener("click", onClick);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return createComponent(RouterContext.Provider, {
|
|
163
|
+
value: {
|
|
164
|
+
routes,
|
|
165
|
+
matched,
|
|
166
|
+
navigate,
|
|
167
|
+
params,
|
|
168
|
+
location: loc,
|
|
169
|
+
setHashAndSearch,
|
|
170
|
+
setPathname
|
|
171
|
+
},
|
|
172
|
+
get children() {
|
|
173
|
+
return props.root?.();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
var useRouterContext = () => {
|
|
178
|
+
const context = useContext(RouterContext);
|
|
179
|
+
if (!context) {
|
|
180
|
+
throw new Error("Router: cannot find a RouterContext");
|
|
181
|
+
}
|
|
182
|
+
return context;
|
|
183
|
+
};
|
|
184
|
+
function useParams() {
|
|
185
|
+
const context = useRouterContext();
|
|
186
|
+
return context.params;
|
|
187
|
+
}
|
|
188
|
+
var useLocation = () => useRouterContext().location;
|
|
189
|
+
var flattenedRoutes = (routes) => {
|
|
190
|
+
return routes.map((route) => flattenedRoute(route)).flat();
|
|
191
|
+
};
|
|
192
|
+
var flattenedRoute = (route) => {
|
|
193
|
+
const routes = [];
|
|
194
|
+
const components = route.components || {};
|
|
195
|
+
let lastComponent = void 0;
|
|
196
|
+
if (route.component) {
|
|
197
|
+
lastComponent = route.component;
|
|
198
|
+
}
|
|
199
|
+
routes.push({
|
|
200
|
+
...route,
|
|
201
|
+
components: {
|
|
202
|
+
...components
|
|
203
|
+
},
|
|
204
|
+
mergedComponents: components,
|
|
205
|
+
component: route.component || lastComponent
|
|
206
|
+
});
|
|
207
|
+
if (!route.children)
|
|
208
|
+
return routes;
|
|
209
|
+
for (let i = 0; i < route.children.length; i++) {
|
|
210
|
+
const child = route.children[i];
|
|
211
|
+
if (!child)
|
|
212
|
+
continue;
|
|
213
|
+
if (child.components) {
|
|
214
|
+
Object.assign(components, child.components);
|
|
215
|
+
}
|
|
216
|
+
if (child.component) {
|
|
217
|
+
lastComponent = child.component;
|
|
218
|
+
}
|
|
219
|
+
if (!child.path.startsWith("/")) {
|
|
220
|
+
if (!child.component) {
|
|
221
|
+
throw new Error("Route: No component for " + child.path);
|
|
222
|
+
}
|
|
223
|
+
components[child.path] = child.component;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
routes.push(...flattenedRoute({
|
|
227
|
+
...child,
|
|
228
|
+
path: route.path + child.path,
|
|
229
|
+
components: {
|
|
230
|
+
...components
|
|
231
|
+
},
|
|
232
|
+
mergedComponents: components,
|
|
233
|
+
component: child.component || lastComponent
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
return routes;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/navigator.ts
|
|
240
|
+
var createNavigate = (routes, pathname, setPathname, setHashAndSearch) => {
|
|
241
|
+
return (path, options) => {
|
|
242
|
+
let newPath = path;
|
|
243
|
+
let currentPathname = pathname();
|
|
244
|
+
if (currentPathname.endsWith("/")) {
|
|
245
|
+
currentPathname = currentPathname.slice(0, -1);
|
|
246
|
+
}
|
|
247
|
+
if (newPath.startsWith("./")) {
|
|
248
|
+
newPath = currentPathname + "/" + newPath.slice(2);
|
|
249
|
+
}
|
|
250
|
+
if (options?.replace) {
|
|
251
|
+
history.replaceState({}, "", newPath);
|
|
252
|
+
} else {
|
|
253
|
+
history.pushState({}, "", newPath);
|
|
254
|
+
}
|
|
255
|
+
if (!isValidPath(routes, location.pathname)) {
|
|
256
|
+
console.error("Invalid path: " + path);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
setPathname(location.pathname);
|
|
260
|
+
setHashAndSearch(getHashAndSearch());
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
var useNavigate = () => {
|
|
264
|
+
const context = useRouterContext();
|
|
265
|
+
return context.navigate;
|
|
266
|
+
};
|
|
267
|
+
var Navigate = (props) => {
|
|
268
|
+
const navigate = useNavigate();
|
|
269
|
+
navigate(props.href, { replace: true });
|
|
270
|
+
return null;
|
|
271
|
+
};
|
|
272
|
+
var Fragment = () => [];
|
|
273
|
+
var Outlet = (props) => {
|
|
274
|
+
const context = useRouterContext();
|
|
275
|
+
const matched = () => context.matched();
|
|
276
|
+
const getName = () => props.name || props.children;
|
|
277
|
+
const component = createMemo(() => {
|
|
278
|
+
const name = getName();
|
|
279
|
+
if (!name) {
|
|
280
|
+
const rootComponent = matched()?.route.component;
|
|
281
|
+
if (!rootComponent) {
|
|
282
|
+
console.warn("Outlet: No component for root.");
|
|
283
|
+
return Fragment;
|
|
284
|
+
}
|
|
285
|
+
return rootComponent;
|
|
286
|
+
}
|
|
287
|
+
const components = context.matched()?.route.components || context.matched()?.route.mergedComponents || {};
|
|
288
|
+
const component2 = components[name];
|
|
289
|
+
if (!component2) {
|
|
290
|
+
console.warn("Outlet: No component for " + name);
|
|
291
|
+
return Fragment;
|
|
292
|
+
}
|
|
293
|
+
return component2;
|
|
294
|
+
});
|
|
295
|
+
return component;
|
|
296
|
+
};
|
|
297
|
+
var Route = (props) => {
|
|
298
|
+
const childRoutes = children(() => props.children).toArray;
|
|
299
|
+
return mergeProps(props, {
|
|
300
|
+
get children() {
|
|
301
|
+
return childRoutes();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
var _tmpl$ = /* @__PURE__ */ template(`<a sn-link>`);
|
|
306
|
+
var A = (props) => {
|
|
307
|
+
return (() => {
|
|
308
|
+
var _el$ = _tmpl$();
|
|
309
|
+
spread(_el$, props, false, false);
|
|
310
|
+
return _el$;
|
|
311
|
+
})();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export { A, Navigate, Outlet, Route, Router, useLocation, useNavigate, useParams };
|