@symbo.ls/router 3.5.1 → 3.14.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/LICENSE +34 -0
- package/README.md +234 -0
- package/dist/cjs/index.js +281 -0
- package/dist/esm/index.js +261 -0
- package/dist/iife/index.js +754 -0
- package/index.js +186 -260
- package/package.json +32 -7
- package/location.js +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Creative Commons Attribution-NonCommercial 4.0 International License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 symbo.ls
|
|
4
|
+
|
|
5
|
+
This work is licensed under the Creative Commons Attribution-NonCommercial
|
|
6
|
+
4.0 International License. To view a copy of this license, visit
|
|
7
|
+
https://creativecommons.org/licenses/by-nc/4.0/ or send a letter to
|
|
8
|
+
Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
|
|
9
|
+
|
|
10
|
+
You are free to:
|
|
11
|
+
|
|
12
|
+
Share — copy and redistribute the material in any medium or format
|
|
13
|
+
Adapt — remix, transform, and build upon the material
|
|
14
|
+
|
|
15
|
+
Under the following terms:
|
|
16
|
+
|
|
17
|
+
Attribution — You must give appropriate credit, provide a link to the
|
|
18
|
+
license, and indicate if changes were made. You may do so in any
|
|
19
|
+
reasonable manner, but not in any way that suggests the licensor endorses
|
|
20
|
+
you or your use.
|
|
21
|
+
|
|
22
|
+
NonCommercial — You may not use the material for commercial purposes.
|
|
23
|
+
|
|
24
|
+
No additional restrictions — You may not apply legal terms or
|
|
25
|
+
technological measures that legally restrict others from doing anything
|
|
26
|
+
the license permits.
|
|
27
|
+
|
|
28
|
+
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
29
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
31
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
32
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
33
|
+
OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
34
|
+
WORK.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @domql/router
|
|
2
|
+
|
|
3
|
+
Client-side router plugin for DOMQL. Handles route matching, navigation, scroll management, and state updates within DOMQL elements.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @domql/router
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { router } from '@domql/router'
|
|
15
|
+
|
|
16
|
+
// Navigate to a path
|
|
17
|
+
router('/about', element)
|
|
18
|
+
|
|
19
|
+
// With state and options
|
|
20
|
+
router('/dashboard', element, { userId: 1 }, { scrollToTop: true })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Define routes on your DOMQL element:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const App = {
|
|
27
|
+
routes: {
|
|
28
|
+
'/': HomePage,
|
|
29
|
+
'/about': AboutPage,
|
|
30
|
+
'/contact': ContactPage,
|
|
31
|
+
'/*': NotFoundPage
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Dynamic Route Params
|
|
37
|
+
|
|
38
|
+
Match routes with `:param` segments. Enable with `useParamsMatching: true`.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const App = {
|
|
42
|
+
routes: {
|
|
43
|
+
'/': HomePage,
|
|
44
|
+
'/:id': UserPage,
|
|
45
|
+
'/:category/:slug': ArticlePage,
|
|
46
|
+
'/*': NotFoundPage
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
router('/users/42', element, {}, { useParamsMatching: true })
|
|
51
|
+
// state.params = { id: '42' }
|
|
52
|
+
|
|
53
|
+
router('/tech/my-article', element, {}, { useParamsMatching: true })
|
|
54
|
+
// state.params = { category: 'tech', slug: 'my-article' }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Exact segments score higher than params, so `/about` will match a literal `/about` route before `/:id`.
|
|
58
|
+
|
|
59
|
+
## Query String Parsing
|
|
60
|
+
|
|
61
|
+
Query parameters are automatically parsed and stored in state.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
router('/search?q=hello&tag=a&tag=b', element)
|
|
65
|
+
// state.query = { q: 'hello', tag: ['a', 'b'] }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Duplicate keys are collected into arrays.
|
|
69
|
+
|
|
70
|
+
## Guards / Middleware
|
|
71
|
+
|
|
72
|
+
Run async guard functions before navigation. Return `true` to allow, `false` to block, or a string to redirect.
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const authGuard = ({ element }) => {
|
|
76
|
+
if (!element.state.root.isLoggedIn) return '/login'
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const roleGuard = ({ params }) => {
|
|
81
|
+
if (params.section === 'admin') return false
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
router('/dashboard', element, {}, {
|
|
86
|
+
guards: [authGuard, roleGuard]
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Guard functions receive a context object:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
{
|
|
94
|
+
pathname, // full pathname
|
|
95
|
+
route, // matched route key
|
|
96
|
+
params, // dynamic route params
|
|
97
|
+
query, // parsed query string
|
|
98
|
+
hash, // URL hash
|
|
99
|
+
element, // DOMQL element
|
|
100
|
+
state // navigation state
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 404 Handling
|
|
105
|
+
|
|
106
|
+
Provide an `onNotFound` callback for unmatched routes:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
router('/unknown', element, {}, {
|
|
110
|
+
onNotFound: ({ pathname, route, element }) => {
|
|
111
|
+
console.warn(`No route found for ${pathname}`)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
You can also define a `/*` wildcard route as a catch-all fallback.
|
|
117
|
+
|
|
118
|
+
## Custom Router Element
|
|
119
|
+
|
|
120
|
+
Use `customRouterElement` in your `config.js` to route pages into a specific element within the component tree instead of the root. This is useful for persistent layouts where only the content area changes between routes.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
// symbols/config.js
|
|
124
|
+
export default {
|
|
125
|
+
router: {
|
|
126
|
+
customRouterElement: 'Folder.Content'
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The value is a dot-separated path resolved from the root element. For example, `'Folder.Content'` means the router will find `root.Folder.Content` and render page content inside it.
|
|
132
|
+
|
|
133
|
+
This allows you to define a layout once in the `/` (main) page and have sub-pages render inside a specific container:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
// pages/main.js — defines the persistent layout
|
|
137
|
+
export const main = {
|
|
138
|
+
extends: 'Layout',
|
|
139
|
+
Folder: {
|
|
140
|
+
Content: {
|
|
141
|
+
// default content for '/' route
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// pages/team.js — only defines content (rendered inside Folder.Content)
|
|
147
|
+
export const team = {
|
|
148
|
+
state: 'team',
|
|
149
|
+
extends: 'Grid',
|
|
150
|
+
childExtends: 'TeamItem',
|
|
151
|
+
childrenAs: 'state',
|
|
152
|
+
children: (el, s) => s.data,
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Options
|
|
157
|
+
|
|
158
|
+
| Option | Type | Default | Description |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `level` | `number` | `0` | Route nesting level (which path segment to match) |
|
|
161
|
+
| `pushState` | `boolean` | `true` | Push to browser history |
|
|
162
|
+
| `initialRender` | `boolean` | `false` | Whether this is the initial page render |
|
|
163
|
+
| `scrollToTop` | `boolean` | `true` | Scroll to top after navigation |
|
|
164
|
+
| `scrollToNode` | `boolean` | `false` | Scroll within the element node |
|
|
165
|
+
| `scrollNode` | `Element` | `document.documentElement` | Node to scroll |
|
|
166
|
+
| `scrollToOffset` | `number` | `0` | Offset when scrolling to hash anchors |
|
|
167
|
+
| `scrollToOptions` | `object` | `{ behavior: 'smooth' }` | Options passed to `scrollTo()` |
|
|
168
|
+
| `useFragment` | `boolean` | `false` | Use fragment tag for content |
|
|
169
|
+
| `updateState` | `boolean` | `true` | Update element state on navigation |
|
|
170
|
+
| `contentElementKey` | `string` | `'content'` | Key for the content element slot |
|
|
171
|
+
| `removeOldElement` | `boolean` | `false` | Remove old content element before setting new |
|
|
172
|
+
| `useParamsMatching` | `boolean` | `false` | Enable dynamic `:param` route matching |
|
|
173
|
+
| `guards` | `function[]` | `undefined` | Array of guard/middleware functions |
|
|
174
|
+
| `onNotFound` | `function` | `undefined` | Callback when no route matches |
|
|
175
|
+
|
|
176
|
+
## Exported Utilities
|
|
177
|
+
|
|
178
|
+
### `getActiveRoute(level, route)`
|
|
179
|
+
|
|
180
|
+
Returns the active route segment at the given nesting level.
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
import { getActiveRoute } from '@domql/router'
|
|
184
|
+
|
|
185
|
+
getActiveRoute(0, '/users/42') // '/users'
|
|
186
|
+
getActiveRoute(1, '/users/42') // '/42'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `parseQuery(search)`
|
|
190
|
+
|
|
191
|
+
Parses a query string into an object.
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
import { parseQuery } from '@domql/router'
|
|
195
|
+
|
|
196
|
+
parseQuery('?page=1&sort=name') // { page: '1', sort: 'name' }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `matchRoute(pathname, routes, level)`
|
|
200
|
+
|
|
201
|
+
Matches a pathname against a routes object. Returns `{ key, content, params }`.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
import { matchRoute } from '@domql/router'
|
|
205
|
+
|
|
206
|
+
const routes = { '/': Home, '/:id': Detail, '/*': NotFound }
|
|
207
|
+
const result = matchRoute('/42', routes)
|
|
208
|
+
// { key: '/:id', content: Detail, params: { id: '42' } }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `parseRoutePattern(pattern)`
|
|
212
|
+
|
|
213
|
+
Parses a route pattern string into segments, param definitions, and wildcard flag. Results are cached.
|
|
214
|
+
|
|
215
|
+
### `runGuards(guards, context)`
|
|
216
|
+
|
|
217
|
+
Runs an array of async guard functions sequentially. Returns `true`, `false`, or a redirect path string.
|
|
218
|
+
|
|
219
|
+
## Events
|
|
220
|
+
|
|
221
|
+
The router triggers an `onRouteChanged` event on the element after navigation completes. Listen for it in your element definition:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
const App = {
|
|
225
|
+
routes: { ... },
|
|
226
|
+
onRouteChanged: (element, options) => {
|
|
227
|
+
console.log('Route changed:', element.state.route)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var index_exports = {};
|
|
20
|
+
__export(index_exports, {
|
|
21
|
+
default: () => index_default,
|
|
22
|
+
getActiveRoute: () => getActiveRoute,
|
|
23
|
+
lastLevel: () => lastLevel,
|
|
24
|
+
lastPathname: () => lastPathname,
|
|
25
|
+
matchRoute: () => matchRoute,
|
|
26
|
+
parseQuery: () => parseQuery,
|
|
27
|
+
parseRoutePattern: () => parseRoutePattern,
|
|
28
|
+
router: () => router,
|
|
29
|
+
runGuards: () => runGuards
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var import_utils = require("@symbo.ls/utils");
|
|
33
|
+
const paramPattern = /^:(.+)/;
|
|
34
|
+
const wildcardPattern = /^\*$/;
|
|
35
|
+
const routeCache = /* @__PURE__ */ new Map();
|
|
36
|
+
const parseRoutePattern = (pattern) => {
|
|
37
|
+
const cached = routeCache.get(pattern);
|
|
38
|
+
if (cached) return cached;
|
|
39
|
+
const segments = pattern.replace(/^\//, "").split("/");
|
|
40
|
+
const params = [];
|
|
41
|
+
let hasWildcard = false;
|
|
42
|
+
for (let i = 0; i < segments.length; i++) {
|
|
43
|
+
const match = segments[i].match(paramPattern);
|
|
44
|
+
if (match) {
|
|
45
|
+
params.push({ index: i, name: match[1] });
|
|
46
|
+
} else if (wildcardPattern.test(segments[i])) {
|
|
47
|
+
hasWildcard = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const result = { segments, params, hasWildcard, pattern };
|
|
51
|
+
routeCache.set(pattern, result);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
const matchRoute = (pathname, routes, level = 0) => {
|
|
55
|
+
const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
|
|
56
|
+
const relevantSegments = pathSegments.slice(level);
|
|
57
|
+
const routePath = "/" + (relevantSegments[0] || "");
|
|
58
|
+
let bestMatch = null;
|
|
59
|
+
let bestScore = -1;
|
|
60
|
+
let matchedParams = {};
|
|
61
|
+
for (const key in routes) {
|
|
62
|
+
if (key === "/*") continue;
|
|
63
|
+
const parsed = parseRoutePattern(key);
|
|
64
|
+
const score = scoreMatch(relevantSegments, parsed);
|
|
65
|
+
if (score > bestScore) {
|
|
66
|
+
bestScore = score;
|
|
67
|
+
bestMatch = key;
|
|
68
|
+
matchedParams = extractParams(relevantSegments, parsed);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!bestMatch && routes["/*"]) {
|
|
72
|
+
bestMatch = "/*";
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
key: bestMatch,
|
|
76
|
+
content: bestMatch ? routes[bestMatch] : null,
|
|
77
|
+
params: matchedParams,
|
|
78
|
+
routePath
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const scoreMatch = (pathSegments, parsed) => {
|
|
82
|
+
const { segments, hasWildcard } = parsed;
|
|
83
|
+
if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
|
|
84
|
+
if (segments.length > pathSegments.length) return -1;
|
|
85
|
+
}
|
|
86
|
+
let score = 0;
|
|
87
|
+
const len = Math.min(segments.length, pathSegments.length);
|
|
88
|
+
for (let i = 0; i < len; i++) {
|
|
89
|
+
if (segments[i] === pathSegments[i]) {
|
|
90
|
+
score += 3;
|
|
91
|
+
} else if (paramPattern.test(segments[i])) {
|
|
92
|
+
score += 1;
|
|
93
|
+
} else if (wildcardPattern.test(segments[i])) {
|
|
94
|
+
score += 0.5;
|
|
95
|
+
} else {
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return score;
|
|
100
|
+
};
|
|
101
|
+
const extractParams = (pathSegments, parsed) => {
|
|
102
|
+
const params = {};
|
|
103
|
+
for (const { index, name } of parsed.params) {
|
|
104
|
+
if (pathSegments[index]) {
|
|
105
|
+
params[name] = decodeURIComponent(pathSegments[index]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return params;
|
|
109
|
+
};
|
|
110
|
+
const parseQuery = (search) => {
|
|
111
|
+
if (!search || search === "?") return {};
|
|
112
|
+
const params = {};
|
|
113
|
+
const searchParams = new URLSearchParams(search);
|
|
114
|
+
searchParams.forEach((value, key) => {
|
|
115
|
+
if (params[key] !== void 0) {
|
|
116
|
+
if (!Array.isArray(params[key])) params[key] = [params[key]];
|
|
117
|
+
params[key].push(value);
|
|
118
|
+
} else {
|
|
119
|
+
params[key] = value;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return params;
|
|
123
|
+
};
|
|
124
|
+
const runGuards = async (guards, context) => {
|
|
125
|
+
if (!guards || !guards.length) return true;
|
|
126
|
+
for (const guard of guards) {
|
|
127
|
+
const result = await guard(context);
|
|
128
|
+
if (result === false) return false;
|
|
129
|
+
if (typeof result === "string") return result;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
};
|
|
133
|
+
const getActiveRoute = (level = 0, route) => {
|
|
134
|
+
if (!route) route = typeof import_utils.window !== "undefined" ? import_utils.window.location.pathname : "/";
|
|
135
|
+
const routeArray = route.split("/");
|
|
136
|
+
const activeRoute = routeArray[level + 1];
|
|
137
|
+
if (activeRoute) return `/${activeRoute}`;
|
|
138
|
+
};
|
|
139
|
+
let lastPathname;
|
|
140
|
+
let lastLevel = 0;
|
|
141
|
+
const normalizePath = (p) => !p || p === "srcdoc" || p === "about:srcdoc" ? "/" : p;
|
|
142
|
+
const defaultOptions = {
|
|
143
|
+
level: lastLevel,
|
|
144
|
+
pushState: true,
|
|
145
|
+
initialRender: false,
|
|
146
|
+
scrollToTop: true,
|
|
147
|
+
scrollToNode: false,
|
|
148
|
+
scrollNode: import_utils.document && import_utils.document.documentElement,
|
|
149
|
+
scrollBody: false,
|
|
150
|
+
useFragment: false,
|
|
151
|
+
updateState: true,
|
|
152
|
+
scrollToOffset: 0,
|
|
153
|
+
contentElementKey: "content",
|
|
154
|
+
scrollToOptions: { behavior: "smooth" },
|
|
155
|
+
useParamsMatching: true
|
|
156
|
+
};
|
|
157
|
+
const router = async (path, el, state = {}, options = {}) => {
|
|
158
|
+
const element = el || void 0;
|
|
159
|
+
const win = element?.context?.window || import_utils.window;
|
|
160
|
+
const doc = element?.context?.document || import_utils.document;
|
|
161
|
+
const opts = {
|
|
162
|
+
...defaultOptions,
|
|
163
|
+
...element.context.routerOptions,
|
|
164
|
+
...options
|
|
165
|
+
};
|
|
166
|
+
lastLevel = opts.lastLevel;
|
|
167
|
+
const ref = element.__ref;
|
|
168
|
+
if (opts.contentElementKey !== "content" && opts.contentElementKey !== ref.contentElementKey || !ref.contentElementKey) {
|
|
169
|
+
ref.contentElementKey = opts.contentElementKey || "content";
|
|
170
|
+
}
|
|
171
|
+
const contentElementKey = ref.contentElementKey || opts.contentElementKey || "content";
|
|
172
|
+
const origin = win.location.origin !== "null" ? win.location.origin : "http://localhost";
|
|
173
|
+
path = normalizePath(path);
|
|
174
|
+
const urlObj = new win.URL(origin + path);
|
|
175
|
+
const { pathname, search, hash } = urlObj;
|
|
176
|
+
const query = parseQuery(search);
|
|
177
|
+
const rootNode = element.node;
|
|
178
|
+
const hashChanged = hash && hash !== win.location.hash.slice(1);
|
|
179
|
+
const pathChanged = pathname !== lastPathname;
|
|
180
|
+
lastPathname = pathname;
|
|
181
|
+
let route, content, params;
|
|
182
|
+
if (opts.useParamsMatching) {
|
|
183
|
+
const match = matchRoute(pathname, element.routes, opts.level);
|
|
184
|
+
route = match.routePath;
|
|
185
|
+
content = match.content;
|
|
186
|
+
params = match.params;
|
|
187
|
+
} else {
|
|
188
|
+
route = getActiveRoute(opts.level, pathname);
|
|
189
|
+
content = element.routes[route || "/"] || element.routes["/*"];
|
|
190
|
+
params = {};
|
|
191
|
+
}
|
|
192
|
+
const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
|
|
193
|
+
if (element.state?.root?.debugging) {
|
|
194
|
+
element.state.root.debugging = false;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!content) {
|
|
198
|
+
if (opts.onNotFound) {
|
|
199
|
+
opts.onNotFound({ pathname, route, element });
|
|
200
|
+
} else if (!opts.silent) {
|
|
201
|
+
console.warn("[smbls/router] no content matched for path", pathname, "\u2014 available routes:", Object.keys(element.routes || {}));
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (opts.guards && opts.guards.length) {
|
|
206
|
+
const guardContext = { pathname, route, params, query, hash, element, state };
|
|
207
|
+
const guardResult = await runGuards(opts.guards, guardContext);
|
|
208
|
+
if (guardResult === false) return;
|
|
209
|
+
if (typeof guardResult === "string") {
|
|
210
|
+
return router(guardResult, el, state, { ...options, guards: [] });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (opts.pushState) {
|
|
214
|
+
try {
|
|
215
|
+
win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
|
|
216
|
+
} catch (e) {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (pathChanged || !hashChanged) {
|
|
220
|
+
const stateUpdate = { route, hash, debugging: false };
|
|
221
|
+
if (Object.keys(params).length) stateUpdate.params = params;
|
|
222
|
+
if (Object.keys(query).length) stateUpdate.query = query;
|
|
223
|
+
if (opts.updateState) {
|
|
224
|
+
element.state.update(
|
|
225
|
+
stateUpdate,
|
|
226
|
+
{ preventContentUpdate: true }
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (contentElementKey && opts.removeOldElement) {
|
|
230
|
+
element[contentElementKey].remove();
|
|
231
|
+
}
|
|
232
|
+
const originContent = element.__ref?.origin?.content;
|
|
233
|
+
const contentStyles = {};
|
|
234
|
+
if (originContent) {
|
|
235
|
+
for (const k in originContent) {
|
|
236
|
+
const v = originContent[k];
|
|
237
|
+
if (k === "__ref" || k === "props" || k === "node" || k === "parent" || k === "key") continue;
|
|
238
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "object" && v !== null && !v.node && !v.__ref) {
|
|
239
|
+
contentStyles[k] = v;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const nextContent = {
|
|
244
|
+
...contentStyles,
|
|
245
|
+
...typeof content === "object" ? content : { extends: content }
|
|
246
|
+
};
|
|
247
|
+
if (opts.useFragment) nextContent.tag = "fragment";
|
|
248
|
+
try {
|
|
249
|
+
element.set(nextContent, { contentElementKey });
|
|
250
|
+
} catch (err) {
|
|
251
|
+
console.error("[smbls/router] failed to render route content", pathname, err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (opts.scrollToTop && scrollNode?.scrollTo) {
|
|
255
|
+
scrollNode.scrollTo({
|
|
256
|
+
...opts.scrollToOptions || {},
|
|
257
|
+
top: 0,
|
|
258
|
+
left: 0
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (opts.scrollToNode && content[contentElementKey]?.node?.scrollTo) {
|
|
262
|
+
content[contentElementKey].node.scrollTo({
|
|
263
|
+
...opts.scrollToOptions || {},
|
|
264
|
+
top: 0,
|
|
265
|
+
left: 0
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (hash) {
|
|
269
|
+
const activeNode = doc.getElementById(hash);
|
|
270
|
+
if (activeNode && scrollNode?.scrollTo) {
|
|
271
|
+
const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
|
|
272
|
+
scrollNode.scrollTo({
|
|
273
|
+
...opts.scrollToOptions || {},
|
|
274
|
+
top,
|
|
275
|
+
left: 0
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
(0, import_utils.triggerEventOn)("routeChanged", element, opts);
|
|
280
|
+
};
|
|
281
|
+
var index_default = router;
|