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 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
+ [![pnpm](https://img.shields.io/badge/maintained%20with-pnpm-cc00ff.svg?style=for-the-badge&logo=pnpm)](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 };