@matthesketh/utopia-router 0.3.1 → 0.5.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/dist/index.cjs +100 -21
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +94 -20
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -31,33 +31,49 @@ __export(index_exports, {
|
|
|
31
31
|
destroy: () => destroy,
|
|
32
32
|
filePathToRoute: () => filePathToRoute,
|
|
33
33
|
forward: () => forward,
|
|
34
|
+
getQueryParam: () => getQueryParam,
|
|
35
|
+
getRouteParam: () => getRouteParam,
|
|
34
36
|
isNavigating: () => isNavigating,
|
|
35
37
|
matchRoute: () => matchRoute,
|
|
36
|
-
navigate: () => navigate
|
|
38
|
+
navigate: () => navigate,
|
|
39
|
+
queryParams: () => queryParams,
|
|
40
|
+
setQueryParam: () => setQueryParam,
|
|
41
|
+
setQueryParams: () => setQueryParams
|
|
37
42
|
});
|
|
38
43
|
module.exports = __toCommonJS(index_exports);
|
|
39
44
|
|
|
40
45
|
// src/matcher.ts
|
|
46
|
+
var BACKSLASH_RE = /\\/g;
|
|
47
|
+
var PAGE_FILE_RE = /\/?\+page\.\w+$/;
|
|
48
|
+
var LAYOUT_OR_ERROR_FILE_RE = /\/?\+(layout|error)\.\w+$/;
|
|
49
|
+
var GROUP_SEGMENT_RE = /^\(.+\)$/;
|
|
50
|
+
var REST_PARAM_RE = /^\[\.\.\..+\]$/;
|
|
51
|
+
var DYNAMIC_PARAM_RE = /^\[.+\]$/;
|
|
52
|
+
var ROOT_ROUTE_RE = /^\/$/;
|
|
53
|
+
var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
|
|
54
|
+
var PAGE_FILE_TEST_RE = /\+page\.\w+$/;
|
|
55
|
+
var LAYOUT_FILE_TEST_RE = /\+layout\.\w+$/;
|
|
56
|
+
var ERROR_FILE_TEST_RE = /\+error\.\w+$/;
|
|
41
57
|
function filePathToRoute(filePath) {
|
|
42
|
-
let normalized = filePath.replace(
|
|
58
|
+
let normalized = filePath.replace(BACKSLASH_RE, "/");
|
|
43
59
|
const routesIdx = normalized.indexOf("routes/");
|
|
44
60
|
if (routesIdx !== -1) {
|
|
45
61
|
normalized = normalized.slice(routesIdx + "routes/".length);
|
|
46
62
|
}
|
|
47
|
-
normalized = normalized.replace(
|
|
48
|
-
normalized = normalized.replace(
|
|
63
|
+
normalized = normalized.replace(PAGE_FILE_RE, "");
|
|
64
|
+
normalized = normalized.replace(LAYOUT_OR_ERROR_FILE_RE, "");
|
|
49
65
|
const segments = normalized.split("/").filter(Boolean);
|
|
50
66
|
const routeSegments = [];
|
|
51
67
|
for (const segment of segments) {
|
|
52
|
-
if (
|
|
68
|
+
if (GROUP_SEGMENT_RE.test(segment)) {
|
|
53
69
|
continue;
|
|
54
70
|
}
|
|
55
|
-
if (
|
|
71
|
+
if (REST_PARAM_RE.test(segment)) {
|
|
56
72
|
const paramName = segment.slice(4, -1);
|
|
57
73
|
routeSegments.push(`*${paramName}`);
|
|
58
74
|
continue;
|
|
59
75
|
}
|
|
60
|
-
if (
|
|
76
|
+
if (DYNAMIC_PARAM_RE.test(segment)) {
|
|
61
77
|
const paramName = segment.slice(1, -1);
|
|
62
78
|
routeSegments.push(`:${paramName}`);
|
|
63
79
|
continue;
|
|
@@ -71,7 +87,7 @@ function compilePattern(pattern) {
|
|
|
71
87
|
const params = [];
|
|
72
88
|
if (pattern === "/") {
|
|
73
89
|
return {
|
|
74
|
-
regex:
|
|
90
|
+
regex: ROOT_ROUTE_RE,
|
|
75
91
|
params: []
|
|
76
92
|
};
|
|
77
93
|
}
|
|
@@ -93,12 +109,13 @@ function compilePattern(pattern) {
|
|
|
93
109
|
}
|
|
94
110
|
regexStr = "^" + regexStr + "/?$";
|
|
95
111
|
return {
|
|
112
|
+
// Dynamic regex — built from sanitized input (segments are escaped via escapeRegex).
|
|
96
113
|
regex: new RegExp(regexStr),
|
|
97
114
|
params
|
|
98
115
|
};
|
|
99
116
|
}
|
|
100
117
|
function escapeRegex(str) {
|
|
101
|
-
return str.replace(
|
|
118
|
+
return str.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
|
|
102
119
|
}
|
|
103
120
|
function matchRoute(url, routes2) {
|
|
104
121
|
let pathname = url.pathname;
|
|
@@ -122,12 +139,12 @@ function buildRouteTable(manifest) {
|
|
|
122
139
|
const layouts = /* @__PURE__ */ new Map();
|
|
123
140
|
const errors = /* @__PURE__ */ new Map();
|
|
124
141
|
for (const [filePath, importFn] of Object.entries(manifest)) {
|
|
125
|
-
const normalized = filePath.replace(
|
|
126
|
-
if (
|
|
142
|
+
const normalized = filePath.replace(BACKSLASH_RE, "/");
|
|
143
|
+
if (PAGE_FILE_TEST_RE.test(normalized)) {
|
|
127
144
|
pages.set(normalized, importFn);
|
|
128
|
-
} else if (
|
|
145
|
+
} else if (LAYOUT_FILE_TEST_RE.test(normalized)) {
|
|
129
146
|
layouts.set(normalized, importFn);
|
|
130
|
-
} else if (
|
|
147
|
+
} else if (ERROR_FILE_TEST_RE.test(normalized)) {
|
|
131
148
|
errors.set(normalized, importFn);
|
|
132
149
|
}
|
|
133
150
|
}
|
|
@@ -174,12 +191,12 @@ function routeSpecificity(path) {
|
|
|
174
191
|
return score;
|
|
175
192
|
}
|
|
176
193
|
function findNearestSpecialFile(pageFilePath, specialFiles) {
|
|
177
|
-
const normalized = pageFilePath.replace(
|
|
194
|
+
const normalized = pageFilePath.replace(BACKSLASH_RE, "/");
|
|
178
195
|
const lastSlash = normalized.lastIndexOf("/");
|
|
179
196
|
let dir = lastSlash !== -1 ? normalized.slice(0, lastSlash) : "";
|
|
180
197
|
while (dir) {
|
|
181
198
|
for (const [specialPath, importFn] of specialFiles) {
|
|
182
|
-
const specialNorm = specialPath.replace(
|
|
199
|
+
const specialNorm = specialPath.replace(BACKSLASH_RE, "/");
|
|
183
200
|
const specialLastSlash = specialNorm.lastIndexOf("/");
|
|
184
201
|
const specialDir = specialLastSlash !== -1 ? specialNorm.slice(0, specialLastSlash) : "";
|
|
185
202
|
if (specialDir === dir) {
|
|
@@ -197,6 +214,7 @@ function findNearestSpecialFile(pageFilePath, specialFiles) {
|
|
|
197
214
|
|
|
198
215
|
// src/router.ts
|
|
199
216
|
var import_utopia_core = require("@matthesketh/utopia-core");
|
|
217
|
+
var VALID_DOM_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
200
218
|
var currentRoute = (0, import_utopia_core.signal)(null);
|
|
201
219
|
var isNavigating = (0, import_utopia_core.signal)(false);
|
|
202
220
|
var routes = [];
|
|
@@ -319,10 +337,13 @@ async function navigate(url, options = {}) {
|
|
|
319
337
|
currentRoute.set(match);
|
|
320
338
|
requestAnimationFrame(() => {
|
|
321
339
|
if (fullUrl.hash) {
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
324
|
-
el.
|
|
325
|
-
|
|
340
|
+
const hashId = fullUrl.hash.slice(1);
|
|
341
|
+
if (hashId && VALID_DOM_ID_RE.test(hashId)) {
|
|
342
|
+
const el = document.getElementById(hashId);
|
|
343
|
+
if (el) {
|
|
344
|
+
el.scrollIntoView();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
326
347
|
}
|
|
327
348
|
}
|
|
328
349
|
window.scrollTo(0, 0);
|
|
@@ -567,8 +588,61 @@ function clearContainer(container) {
|
|
|
567
588
|
container.removeChild(container.firstChild);
|
|
568
589
|
}
|
|
569
590
|
}
|
|
591
|
+
var AMPERSAND_RE = /&/g;
|
|
592
|
+
var LESS_THAN_RE = /</g;
|
|
593
|
+
var GREATER_THAN_RE = />/g;
|
|
594
|
+
var DOUBLE_QUOTE_RE = /"/g;
|
|
595
|
+
var SINGLE_QUOTE_RE = /'/g;
|
|
570
596
|
function escapeHtml(str) {
|
|
571
|
-
return str.replace(
|
|
597
|
+
return str.replace(AMPERSAND_RE, "&").replace(LESS_THAN_RE, "<").replace(GREATER_THAN_RE, ">").replace(DOUBLE_QUOTE_RE, """).replace(SINGLE_QUOTE_RE, "'");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/query.ts
|
|
601
|
+
var import_utopia_core3 = require("@matthesketh/utopia-core");
|
|
602
|
+
var queryParams = (0, import_utopia_core3.computed)(() => {
|
|
603
|
+
const match = currentRoute();
|
|
604
|
+
if (!match) return {};
|
|
605
|
+
const result = {};
|
|
606
|
+
match.url.searchParams.forEach((value, key) => {
|
|
607
|
+
result[key] = value;
|
|
608
|
+
});
|
|
609
|
+
return result;
|
|
610
|
+
});
|
|
611
|
+
function getQueryParam(name) {
|
|
612
|
+
return (0, import_utopia_core3.computed)(() => {
|
|
613
|
+
const match = currentRoute();
|
|
614
|
+
if (!match) return null;
|
|
615
|
+
return match.url.searchParams.get(name);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function setQueryParam(name, value) {
|
|
619
|
+
if (typeof window === "undefined") return;
|
|
620
|
+
const url = new URL(window.location.href);
|
|
621
|
+
if (value === null) {
|
|
622
|
+
url.searchParams.delete(name);
|
|
623
|
+
} else {
|
|
624
|
+
url.searchParams.set(name, value);
|
|
625
|
+
}
|
|
626
|
+
navigate(url.pathname + url.search + url.hash, { replace: true });
|
|
627
|
+
}
|
|
628
|
+
function setQueryParams(params) {
|
|
629
|
+
if (typeof window === "undefined") return;
|
|
630
|
+
const url = new URL(window.location.href);
|
|
631
|
+
for (const [key, value] of Object.entries(params)) {
|
|
632
|
+
if (value === null) {
|
|
633
|
+
url.searchParams.delete(key);
|
|
634
|
+
} else {
|
|
635
|
+
url.searchParams.set(key, value);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
navigate(url.pathname + url.search + url.hash, { replace: true });
|
|
639
|
+
}
|
|
640
|
+
function getRouteParam(name) {
|
|
641
|
+
return (0, import_utopia_core3.computed)(() => {
|
|
642
|
+
const match = currentRoute();
|
|
643
|
+
if (!match) return null;
|
|
644
|
+
return match.params[name] ?? null;
|
|
645
|
+
});
|
|
572
646
|
}
|
|
573
647
|
// Annotate the CommonJS export names for ESM import in node:
|
|
574
648
|
0 && (module.exports = {
|
|
@@ -583,7 +657,12 @@ function escapeHtml(str) {
|
|
|
583
657
|
destroy,
|
|
584
658
|
filePathToRoute,
|
|
585
659
|
forward,
|
|
660
|
+
getQueryParam,
|
|
661
|
+
getRouteParam,
|
|
586
662
|
isNavigating,
|
|
587
663
|
matchRoute,
|
|
588
|
-
navigate
|
|
664
|
+
navigate,
|
|
665
|
+
queryParams,
|
|
666
|
+
setQueryParam,
|
|
667
|
+
setQueryParams
|
|
589
668
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -214,4 +214,29 @@ declare function createLink(props: {
|
|
|
214
214
|
activeClass?: string;
|
|
215
215
|
}): HTMLAnchorElement;
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Reactive computed signal returning all current query parameters as a plain object.
|
|
219
|
+
*/
|
|
220
|
+
declare const queryParams: _matthesketh_utopia_core.ReadonlySignal<Record<string, string>>;
|
|
221
|
+
/**
|
|
222
|
+
* Returns a computed signal for a specific query parameter.
|
|
223
|
+
* Returns null if the parameter is not present.
|
|
224
|
+
*/
|
|
225
|
+
declare function getQueryParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
|
|
226
|
+
/**
|
|
227
|
+
* Update a single query parameter and navigate (replace mode).
|
|
228
|
+
* Pass null to remove the parameter.
|
|
229
|
+
*/
|
|
230
|
+
declare function setQueryParam(name: string, value: string | null): void;
|
|
231
|
+
/**
|
|
232
|
+
* Update multiple query parameters in a single navigation.
|
|
233
|
+
* Pass null for a value to remove that parameter.
|
|
234
|
+
*/
|
|
235
|
+
declare function setQueryParams(params: Record<string, string | null>): void;
|
|
236
|
+
/**
|
|
237
|
+
* Returns a computed signal for a specific route path parameter.
|
|
238
|
+
* Returns null if the parameter is not present.
|
|
239
|
+
*/
|
|
240
|
+
declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
|
|
241
|
+
|
|
242
|
+
export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
|
package/dist/index.d.ts
CHANGED
|
@@ -214,4 +214,29 @@ declare function createLink(props: {
|
|
|
214
214
|
activeClass?: string;
|
|
215
215
|
}): HTMLAnchorElement;
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Reactive computed signal returning all current query parameters as a plain object.
|
|
219
|
+
*/
|
|
220
|
+
declare const queryParams: _matthesketh_utopia_core.ReadonlySignal<Record<string, string>>;
|
|
221
|
+
/**
|
|
222
|
+
* Returns a computed signal for a specific query parameter.
|
|
223
|
+
* Returns null if the parameter is not present.
|
|
224
|
+
*/
|
|
225
|
+
declare function getQueryParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
|
|
226
|
+
/**
|
|
227
|
+
* Update a single query parameter and navigate (replace mode).
|
|
228
|
+
* Pass null to remove the parameter.
|
|
229
|
+
*/
|
|
230
|
+
declare function setQueryParam(name: string, value: string | null): void;
|
|
231
|
+
/**
|
|
232
|
+
* Update multiple query parameters in a single navigation.
|
|
233
|
+
* Pass null for a value to remove that parameter.
|
|
234
|
+
*/
|
|
235
|
+
declare function setQueryParams(params: Record<string, string | null>): void;
|
|
236
|
+
/**
|
|
237
|
+
* Returns a computed signal for a specific route path parameter.
|
|
238
|
+
* Returns null if the parameter is not present.
|
|
239
|
+
*/
|
|
240
|
+
declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
|
|
241
|
+
|
|
242
|
+
export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
// src/matcher.ts
|
|
2
|
+
var BACKSLASH_RE = /\\/g;
|
|
3
|
+
var PAGE_FILE_RE = /\/?\+page\.\w+$/;
|
|
4
|
+
var LAYOUT_OR_ERROR_FILE_RE = /\/?\+(layout|error)\.\w+$/;
|
|
5
|
+
var GROUP_SEGMENT_RE = /^\(.+\)$/;
|
|
6
|
+
var REST_PARAM_RE = /^\[\.\.\..+\]$/;
|
|
7
|
+
var DYNAMIC_PARAM_RE = /^\[.+\]$/;
|
|
8
|
+
var ROOT_ROUTE_RE = /^\/$/;
|
|
9
|
+
var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
|
|
10
|
+
var PAGE_FILE_TEST_RE = /\+page\.\w+$/;
|
|
11
|
+
var LAYOUT_FILE_TEST_RE = /\+layout\.\w+$/;
|
|
12
|
+
var ERROR_FILE_TEST_RE = /\+error\.\w+$/;
|
|
2
13
|
function filePathToRoute(filePath) {
|
|
3
|
-
let normalized = filePath.replace(
|
|
14
|
+
let normalized = filePath.replace(BACKSLASH_RE, "/");
|
|
4
15
|
const routesIdx = normalized.indexOf("routes/");
|
|
5
16
|
if (routesIdx !== -1) {
|
|
6
17
|
normalized = normalized.slice(routesIdx + "routes/".length);
|
|
7
18
|
}
|
|
8
|
-
normalized = normalized.replace(
|
|
9
|
-
normalized = normalized.replace(
|
|
19
|
+
normalized = normalized.replace(PAGE_FILE_RE, "");
|
|
20
|
+
normalized = normalized.replace(LAYOUT_OR_ERROR_FILE_RE, "");
|
|
10
21
|
const segments = normalized.split("/").filter(Boolean);
|
|
11
22
|
const routeSegments = [];
|
|
12
23
|
for (const segment of segments) {
|
|
13
|
-
if (
|
|
24
|
+
if (GROUP_SEGMENT_RE.test(segment)) {
|
|
14
25
|
continue;
|
|
15
26
|
}
|
|
16
|
-
if (
|
|
27
|
+
if (REST_PARAM_RE.test(segment)) {
|
|
17
28
|
const paramName = segment.slice(4, -1);
|
|
18
29
|
routeSegments.push(`*${paramName}`);
|
|
19
30
|
continue;
|
|
20
31
|
}
|
|
21
|
-
if (
|
|
32
|
+
if (DYNAMIC_PARAM_RE.test(segment)) {
|
|
22
33
|
const paramName = segment.slice(1, -1);
|
|
23
34
|
routeSegments.push(`:${paramName}`);
|
|
24
35
|
continue;
|
|
@@ -32,7 +43,7 @@ function compilePattern(pattern) {
|
|
|
32
43
|
const params = [];
|
|
33
44
|
if (pattern === "/") {
|
|
34
45
|
return {
|
|
35
|
-
regex:
|
|
46
|
+
regex: ROOT_ROUTE_RE,
|
|
36
47
|
params: []
|
|
37
48
|
};
|
|
38
49
|
}
|
|
@@ -54,12 +65,13 @@ function compilePattern(pattern) {
|
|
|
54
65
|
}
|
|
55
66
|
regexStr = "^" + regexStr + "/?$";
|
|
56
67
|
return {
|
|
68
|
+
// Dynamic regex — built from sanitized input (segments are escaped via escapeRegex).
|
|
57
69
|
regex: new RegExp(regexStr),
|
|
58
70
|
params
|
|
59
71
|
};
|
|
60
72
|
}
|
|
61
73
|
function escapeRegex(str) {
|
|
62
|
-
return str.replace(
|
|
74
|
+
return str.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
|
|
63
75
|
}
|
|
64
76
|
function matchRoute(url, routes2) {
|
|
65
77
|
let pathname = url.pathname;
|
|
@@ -83,12 +95,12 @@ function buildRouteTable(manifest) {
|
|
|
83
95
|
const layouts = /* @__PURE__ */ new Map();
|
|
84
96
|
const errors = /* @__PURE__ */ new Map();
|
|
85
97
|
for (const [filePath, importFn] of Object.entries(manifest)) {
|
|
86
|
-
const normalized = filePath.replace(
|
|
87
|
-
if (
|
|
98
|
+
const normalized = filePath.replace(BACKSLASH_RE, "/");
|
|
99
|
+
if (PAGE_FILE_TEST_RE.test(normalized)) {
|
|
88
100
|
pages.set(normalized, importFn);
|
|
89
|
-
} else if (
|
|
101
|
+
} else if (LAYOUT_FILE_TEST_RE.test(normalized)) {
|
|
90
102
|
layouts.set(normalized, importFn);
|
|
91
|
-
} else if (
|
|
103
|
+
} else if (ERROR_FILE_TEST_RE.test(normalized)) {
|
|
92
104
|
errors.set(normalized, importFn);
|
|
93
105
|
}
|
|
94
106
|
}
|
|
@@ -135,12 +147,12 @@ function routeSpecificity(path) {
|
|
|
135
147
|
return score;
|
|
136
148
|
}
|
|
137
149
|
function findNearestSpecialFile(pageFilePath, specialFiles) {
|
|
138
|
-
const normalized = pageFilePath.replace(
|
|
150
|
+
const normalized = pageFilePath.replace(BACKSLASH_RE, "/");
|
|
139
151
|
const lastSlash = normalized.lastIndexOf("/");
|
|
140
152
|
let dir = lastSlash !== -1 ? normalized.slice(0, lastSlash) : "";
|
|
141
153
|
while (dir) {
|
|
142
154
|
for (const [specialPath, importFn] of specialFiles) {
|
|
143
|
-
const specialNorm = specialPath.replace(
|
|
155
|
+
const specialNorm = specialPath.replace(BACKSLASH_RE, "/");
|
|
144
156
|
const specialLastSlash = specialNorm.lastIndexOf("/");
|
|
145
157
|
const specialDir = specialLastSlash !== -1 ? specialNorm.slice(0, specialLastSlash) : "";
|
|
146
158
|
if (specialDir === dir) {
|
|
@@ -158,6 +170,7 @@ function findNearestSpecialFile(pageFilePath, specialFiles) {
|
|
|
158
170
|
|
|
159
171
|
// src/router.ts
|
|
160
172
|
import { signal } from "@matthesketh/utopia-core";
|
|
173
|
+
var VALID_DOM_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
161
174
|
var currentRoute = signal(null);
|
|
162
175
|
var isNavigating = signal(false);
|
|
163
176
|
var routes = [];
|
|
@@ -280,10 +293,13 @@ async function navigate(url, options = {}) {
|
|
|
280
293
|
currentRoute.set(match);
|
|
281
294
|
requestAnimationFrame(() => {
|
|
282
295
|
if (fullUrl.hash) {
|
|
283
|
-
const
|
|
284
|
-
if (
|
|
285
|
-
el.
|
|
286
|
-
|
|
296
|
+
const hashId = fullUrl.hash.slice(1);
|
|
297
|
+
if (hashId && VALID_DOM_ID_RE.test(hashId)) {
|
|
298
|
+
const el = document.getElementById(hashId);
|
|
299
|
+
if (el) {
|
|
300
|
+
el.scrollIntoView();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
287
303
|
}
|
|
288
304
|
}
|
|
289
305
|
window.scrollTo(0, 0);
|
|
@@ -528,8 +544,61 @@ function clearContainer(container) {
|
|
|
528
544
|
container.removeChild(container.firstChild);
|
|
529
545
|
}
|
|
530
546
|
}
|
|
547
|
+
var AMPERSAND_RE = /&/g;
|
|
548
|
+
var LESS_THAN_RE = /</g;
|
|
549
|
+
var GREATER_THAN_RE = />/g;
|
|
550
|
+
var DOUBLE_QUOTE_RE = /"/g;
|
|
551
|
+
var SINGLE_QUOTE_RE = /'/g;
|
|
531
552
|
function escapeHtml(str) {
|
|
532
|
-
return str.replace(
|
|
553
|
+
return str.replace(AMPERSAND_RE, "&").replace(LESS_THAN_RE, "<").replace(GREATER_THAN_RE, ">").replace(DOUBLE_QUOTE_RE, """).replace(SINGLE_QUOTE_RE, "'");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/query.ts
|
|
557
|
+
import { computed } from "@matthesketh/utopia-core";
|
|
558
|
+
var queryParams = computed(() => {
|
|
559
|
+
const match = currentRoute();
|
|
560
|
+
if (!match) return {};
|
|
561
|
+
const result = {};
|
|
562
|
+
match.url.searchParams.forEach((value, key) => {
|
|
563
|
+
result[key] = value;
|
|
564
|
+
});
|
|
565
|
+
return result;
|
|
566
|
+
});
|
|
567
|
+
function getQueryParam(name) {
|
|
568
|
+
return computed(() => {
|
|
569
|
+
const match = currentRoute();
|
|
570
|
+
if (!match) return null;
|
|
571
|
+
return match.url.searchParams.get(name);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function setQueryParam(name, value) {
|
|
575
|
+
if (typeof window === "undefined") return;
|
|
576
|
+
const url = new URL(window.location.href);
|
|
577
|
+
if (value === null) {
|
|
578
|
+
url.searchParams.delete(name);
|
|
579
|
+
} else {
|
|
580
|
+
url.searchParams.set(name, value);
|
|
581
|
+
}
|
|
582
|
+
navigate(url.pathname + url.search + url.hash, { replace: true });
|
|
583
|
+
}
|
|
584
|
+
function setQueryParams(params) {
|
|
585
|
+
if (typeof window === "undefined") return;
|
|
586
|
+
const url = new URL(window.location.href);
|
|
587
|
+
for (const [key, value] of Object.entries(params)) {
|
|
588
|
+
if (value === null) {
|
|
589
|
+
url.searchParams.delete(key);
|
|
590
|
+
} else {
|
|
591
|
+
url.searchParams.set(key, value);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
navigate(url.pathname + url.search + url.hash, { replace: true });
|
|
595
|
+
}
|
|
596
|
+
function getRouteParam(name) {
|
|
597
|
+
return computed(() => {
|
|
598
|
+
const match = currentRoute();
|
|
599
|
+
if (!match) return null;
|
|
600
|
+
return match.params[name] ?? null;
|
|
601
|
+
});
|
|
533
602
|
}
|
|
534
603
|
export {
|
|
535
604
|
back,
|
|
@@ -543,7 +612,12 @@ export {
|
|
|
543
612
|
destroy,
|
|
544
613
|
filePathToRoute,
|
|
545
614
|
forward,
|
|
615
|
+
getQueryParam,
|
|
616
|
+
getRouteParam,
|
|
546
617
|
isNavigating,
|
|
547
618
|
matchRoute,
|
|
548
|
-
navigate
|
|
619
|
+
navigate,
|
|
620
|
+
queryParams,
|
|
621
|
+
setQueryParam,
|
|
622
|
+
setQueryParams
|
|
549
623
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthesketh/utopia-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "File-based routing for UtopiaJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@matthesketh/utopia-core": "0.
|
|
44
|
-
"@matthesketh/utopia-runtime": "0.
|
|
43
|
+
"@matthesketh/utopia-core": "0.5.0",
|
|
44
|
+
"@matthesketh/utopia-runtime": "0.5.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|