@keenmate/svelte-spa-router 1.0.2 → 1.0.3
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/.editorconfig +3 -0
- package/Router.svelte +92 -81
- package/package.json +1 -1
package/.editorconfig
CHANGED
|
@@ -7,6 +7,9 @@ trim_trailing_whitespace = true
|
|
|
7
7
|
charset = utf-8
|
|
8
8
|
ij_javascript_use_semicolon_after_statement = false
|
|
9
9
|
ij_typescript_use_semicolon_after_statement = false
|
|
10
|
+
ij_html_quote_style = double
|
|
11
|
+
ij_javascript_use_double_quotes = true
|
|
12
|
+
ij_typescript_use_double_quotes = true
|
|
10
13
|
|
|
11
14
|
[*.{xaml,cshtml,html,xml}]
|
|
12
15
|
indent_style = tab
|
package/Router.svelte
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script context="module">
|
|
2
|
-
import {derived, get, readable, writable} from
|
|
3
|
-
import {tick} from
|
|
4
|
-
import {SvelteSPARouterNavigationEvent} from
|
|
5
|
-
import {joinPaths} from
|
|
2
|
+
import {derived, get, readable, writable} from "svelte/store"
|
|
3
|
+
import {tick} from "svelte"
|
|
4
|
+
import {SvelteSPARouterNavigationEvent} from "./constants.js"
|
|
5
|
+
import {joinPaths} from "./helpers/url-helpers.js"
|
|
6
6
|
|
|
7
7
|
export const HashRoutingEnabled = writable(true)
|
|
8
|
-
export const BasePath = writable(
|
|
8
|
+
export const BasePath = writable("/")
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Returns the current location from the hash.
|
|
@@ -16,13 +16,21 @@
|
|
|
16
16
|
function getLocation() {
|
|
17
17
|
const hashRoutingEnabled = get(HashRoutingEnabled)
|
|
18
18
|
const basePath = get(BasePath)
|
|
19
|
-
let location
|
|
19
|
+
let location = ""
|
|
20
|
+
let querystring = ""
|
|
20
21
|
|
|
21
22
|
if (hashRoutingEnabled) {
|
|
22
|
-
const hashPosition = window.location.href.indexOf(
|
|
23
|
+
const hashPosition = window.location.href.indexOf("#/")
|
|
23
24
|
location = (hashPosition > -1) ?
|
|
24
25
|
window.location.href.substr(hashPosition + 1) :
|
|
25
|
-
|
|
26
|
+
"/"
|
|
27
|
+
|
|
28
|
+
// Check if there's a querystring
|
|
29
|
+
const qsPosition = location.indexOf("?")
|
|
30
|
+
if (qsPosition > -1) {
|
|
31
|
+
querystring = location.substr(qsPosition + 1)
|
|
32
|
+
location = location.substr(0, qsPosition)
|
|
33
|
+
}
|
|
26
34
|
}
|
|
27
35
|
else {
|
|
28
36
|
const startsWithPrefix = window.location.pathname.startsWith(basePath)
|
|
@@ -31,15 +39,11 @@
|
|
|
31
39
|
BasePath)}"`)
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
location =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let querystring = ''
|
|
40
|
-
if (qsPosition > -1) {
|
|
41
|
-
querystring = location.substr(qsPosition + 1)
|
|
42
|
-
location = location.substr(0, qsPosition)
|
|
42
|
+
location = "/" + window.location.pathname.substring(basePath.length).replace(/^\//, "")
|
|
43
|
+
// console.log("SAPA ROUTER Parsing querystring", window.location.search)
|
|
44
|
+
if (window.location.search) {
|
|
45
|
+
querystring = window.location.search.substring(1)
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
return {location, querystring}
|
|
@@ -55,11 +59,11 @@
|
|
|
55
59
|
set(getLocation())
|
|
56
60
|
|
|
57
61
|
const eventName = get(HashRoutingEnabled) ?
|
|
58
|
-
|
|
62
|
+
"hashchange" :
|
|
59
63
|
SvelteSPARouterNavigationEvent
|
|
60
|
-
console.log(
|
|
64
|
+
// console.log("Setting loc")
|
|
61
65
|
const update = () => {
|
|
62
|
-
console.log(
|
|
66
|
+
console.log("Updating location", getLocation())
|
|
63
67
|
set(getLocation())
|
|
64
68
|
}
|
|
65
69
|
window.addEventListener(eventName, update, false)
|
|
@@ -125,18 +129,18 @@
|
|
|
125
129
|
return jediForcePush(location, true)
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
async function jediForcePush(
|
|
132
|
+
async function jediForcePush(location_, shouldReplace = false) {
|
|
129
133
|
const hashRoutingEnabled = get(HashRoutingEnabled)
|
|
130
134
|
const basePath = get(BasePath)
|
|
131
135
|
|
|
132
136
|
if (hashRoutingEnabled) {
|
|
133
|
-
if (!
|
|
134
|
-
throw Error(
|
|
137
|
+
if (!location_ || location_.length < 1 || !/^(\/|#\/)/.test(location_)) {
|
|
138
|
+
throw Error("Invalid parameter location")
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
else {
|
|
138
|
-
if (!
|
|
139
|
-
throw Error(
|
|
142
|
+
if (!location_ || location_.length < 1 || !/^\//.test(location_)) {
|
|
143
|
+
throw Error("Invalid parameter location")
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
|
|
@@ -158,28 +162,29 @@
|
|
|
158
162
|
doNavigate(newHistoryState, undefined)
|
|
159
163
|
|
|
160
164
|
if (!shouldReplace) {
|
|
161
|
-
window.location.hash = (
|
|
165
|
+
window.location.hash = (location_.charAt(0) === "#" ? "" : "#") + location_
|
|
162
166
|
}
|
|
163
167
|
else {
|
|
164
|
-
window.dispatchEvent(new Event(
|
|
168
|
+
window.dispatchEvent(new Event("hashchange"))
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
if (location_ !== get(location)) {
|
|
172
|
+
if (!location_.startsWith(basePath)) {
|
|
173
|
+
location_ = joinPaths(basePath, location_)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// console.log(
|
|
177
|
+
// "Before navigate",
|
|
178
|
+
// window.history,
|
|
179
|
+
// window.history.pushState,
|
|
180
|
+
// doNavigate,
|
|
181
|
+
// newHistoryState,
|
|
182
|
+
// undefined,
|
|
183
|
+
// location_
|
|
184
|
+
// )
|
|
185
|
+
// window.history.pushState(newHistoryState, undefined, location)
|
|
186
|
+
doNavigate(newHistoryState, undefined, location_)
|
|
165
187
|
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
if (!location.startsWith(basePath)) {
|
|
169
|
-
location = joinPaths(basePath, location)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
console.log(
|
|
173
|
-
'Before navigate',
|
|
174
|
-
window.history,
|
|
175
|
-
window.history.pushState,
|
|
176
|
-
doNavigate,
|
|
177
|
-
newHistoryState,
|
|
178
|
-
undefined,
|
|
179
|
-
location
|
|
180
|
-
)
|
|
181
|
-
// window.history.pushState(newHistoryState, undefined, location)
|
|
182
|
-
doNavigate(newHistoryState, undefined, location)
|
|
183
188
|
|
|
184
189
|
window.dispatchEvent(new Event(SvelteSPARouterNavigationEvent))
|
|
185
190
|
}
|
|
@@ -209,21 +214,27 @@
|
|
|
209
214
|
opts = linkOpts(opts)
|
|
210
215
|
|
|
211
216
|
// Only apply to <a> tags
|
|
212
|
-
if (!node || !node.tagName || node.tagName.toLowerCase() !=
|
|
213
|
-
throw Error(
|
|
217
|
+
if (!node || !node.tagName || node.tagName.toLowerCase() != "a") {
|
|
218
|
+
throw Error("Action 'link' can only be used with <a> tags")
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
updateLink(node, opts)
|
|
217
222
|
|
|
218
223
|
if (!get(HashRoutingEnabled)) {
|
|
219
|
-
node.addEventListener(
|
|
224
|
+
node.addEventListener("click", ev => {
|
|
225
|
+
const linkTarget = ev.target.getAttribute("target")
|
|
226
|
+
if (linkTarget && linkTarget !== "_self") {
|
|
227
|
+
// prevent pushState when link is perhaps going outside of this window
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
220
231
|
ev.stopImmediatePropagation()
|
|
221
232
|
ev.preventDefault()
|
|
222
233
|
|
|
223
|
-
const shouldReplace = typeof opts !==
|
|
234
|
+
const shouldReplace = typeof opts !== "string" && opts.shouldReplace
|
|
224
235
|
|
|
225
|
-
jediForcePush(node.getAttribute(
|
|
226
|
-
window.dispatchEvent(new Event(SvelteSPARouterNavigationEvent))
|
|
236
|
+
jediForcePush(node.getAttribute("href"), shouldReplace)
|
|
237
|
+
// window.dispatchEvent(new Event(SvelteSPARouterNavigationEvent))
|
|
227
238
|
}, {capture: true})
|
|
228
239
|
}
|
|
229
240
|
|
|
@@ -255,15 +266,15 @@
|
|
|
255
266
|
function updateLink(node, opts) {
|
|
256
267
|
const basePath = get(BasePath)
|
|
257
268
|
|
|
258
|
-
let href = opts.href || node.getAttribute(
|
|
269
|
+
let href = opts.href || node.getAttribute("href")
|
|
259
270
|
|
|
260
271
|
if (get(HashRoutingEnabled)) {
|
|
261
|
-
if (href && href.charAt(0) ==
|
|
272
|
+
if (href && href.charAt(0) == "/") {
|
|
262
273
|
// Add # to the href attribute
|
|
263
|
-
href =
|
|
274
|
+
href = "#" + href
|
|
264
275
|
}
|
|
265
|
-
else if (!href || href.length < 2 || href.slice(0, 2) !=
|
|
266
|
-
throw Error(
|
|
276
|
+
else if (!href || href.length < 2 || href.slice(0, 2) != "#/") {
|
|
277
|
+
throw Error("Invalid value for \"href\" attribute: " + href)
|
|
267
278
|
}
|
|
268
279
|
}
|
|
269
280
|
else {
|
|
@@ -272,19 +283,19 @@
|
|
|
272
283
|
}
|
|
273
284
|
}
|
|
274
285
|
|
|
275
|
-
node.setAttribute(
|
|
276
|
-
node.addEventListener(
|
|
286
|
+
node.setAttribute("href", href)
|
|
287
|
+
node.addEventListener("click", (event) => {
|
|
277
288
|
// Prevent default anchor onclick behaviour
|
|
278
289
|
event.preventDefault()
|
|
279
290
|
if (!opts.disabled) {
|
|
280
|
-
scrollstateHistoryHandler(event.currentTarget.getAttribute(
|
|
291
|
+
scrollstateHistoryHandler(event.currentTarget.getAttribute("href"))
|
|
281
292
|
}
|
|
282
293
|
})
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
// Internal function that ensures the argument of the link action is always an object
|
|
286
297
|
function linkOpts(val) {
|
|
287
|
-
if (val && typeof val ==
|
|
298
|
+
if (val && typeof val == "string") {
|
|
288
299
|
return {
|
|
289
300
|
href: val
|
|
290
301
|
}
|
|
@@ -328,8 +339,8 @@
|
|
|
328
339
|
{/if}
|
|
329
340
|
|
|
330
341
|
<script>
|
|
331
|
-
import {onDestroy, createEventDispatcher, afterUpdate} from
|
|
332
|
-
import {parse} from
|
|
342
|
+
import {onDestroy, createEventDispatcher, afterUpdate} from "svelte"
|
|
343
|
+
import {parse} from "regexparam"
|
|
333
344
|
|
|
334
345
|
/**
|
|
335
346
|
* Dictionary of all routes, in the format `'/path': component`.
|
|
@@ -351,7 +362,7 @@
|
|
|
351
362
|
/**
|
|
352
363
|
* Optional prefix for the routes in this router. This is useful for example in the case of nested routers.
|
|
353
364
|
*/
|
|
354
|
-
export let prefix =
|
|
365
|
+
export let prefix = ""
|
|
355
366
|
|
|
356
367
|
/**
|
|
357
368
|
* If set to true, the router will restore scroll positions on back navigation
|
|
@@ -370,16 +381,16 @@
|
|
|
370
381
|
* @param {SvelteComponent|WrappedComponent} component - Svelte component for the route, optionally wrapped
|
|
371
382
|
*/
|
|
372
383
|
constructor(path, component) {
|
|
373
|
-
if (!component || (typeof component !=
|
|
374
|
-
throw Error(
|
|
384
|
+
if (!component || (typeof component != "function" && (typeof component != "object" || component._sveltesparouter !== true))) {
|
|
385
|
+
throw Error("Invalid component object")
|
|
375
386
|
}
|
|
376
387
|
|
|
377
388
|
// Path must be a regular or expression, or a string starting with '/' or '*'
|
|
378
389
|
if (!path ||
|
|
379
|
-
(typeof path ==
|
|
380
|
-
(typeof path ==
|
|
390
|
+
(typeof path == "string" && (path.length < 1 || (path.charAt(0) != "/" && path.charAt(0) != "*"))) ||
|
|
391
|
+
(typeof path == "object" && !(path instanceof RegExp))
|
|
381
392
|
) {
|
|
382
|
-
throw Error(
|
|
393
|
+
throw Error("Invalid value for \"path\" argument - strings must start with / or *")
|
|
383
394
|
}
|
|
384
395
|
|
|
385
396
|
const {pattern, keys} = parse(path)
|
|
@@ -387,7 +398,7 @@
|
|
|
387
398
|
this.path = path
|
|
388
399
|
|
|
389
400
|
// Check if the component is wrapped and we have conditions
|
|
390
|
-
if (typeof component ==
|
|
401
|
+
if (typeof component == "object" && component._sveltesparouter === true) {
|
|
391
402
|
this.component = component.component
|
|
392
403
|
this.conditions = component.conditions || []
|
|
393
404
|
this.userData = component.userData
|
|
@@ -416,9 +427,9 @@
|
|
|
416
427
|
// If there's a prefix, check if it matches the start of the path.
|
|
417
428
|
// If not, bail early, else remove it before we run the matching.
|
|
418
429
|
if (prefix) {
|
|
419
|
-
if (typeof prefix ==
|
|
430
|
+
if (typeof prefix == "string") {
|
|
420
431
|
if (path.startsWith(prefix)) {
|
|
421
|
-
path = path.substr(prefix.length) ||
|
|
432
|
+
path = path.substr(prefix.length) || "/"
|
|
422
433
|
}
|
|
423
434
|
else {
|
|
424
435
|
return null
|
|
@@ -427,7 +438,7 @@
|
|
|
427
438
|
else if (prefix instanceof RegExp) {
|
|
428
439
|
const match = path.match(prefix)
|
|
429
440
|
if (match && match[0]) {
|
|
430
|
-
path = path.substr(match[0].length) ||
|
|
441
|
+
path = path.substr(match[0].length) || "/"
|
|
431
442
|
}
|
|
432
443
|
else {
|
|
433
444
|
return null
|
|
@@ -451,7 +462,7 @@
|
|
|
451
462
|
while (i < this._keys.length) {
|
|
452
463
|
// In the match parameters, URL-decode all values
|
|
453
464
|
try {
|
|
454
|
-
out[this._keys[i]] = decodeURIComponent(matches[i + 1] ||
|
|
465
|
+
out[this._keys[i]] = decodeURIComponent(matches[i + 1] || "") || null
|
|
455
466
|
} catch (e) {
|
|
456
467
|
out[this._keys[i]] = null
|
|
457
468
|
}
|
|
@@ -522,7 +533,7 @@
|
|
|
522
533
|
let previousScrollState = null
|
|
523
534
|
|
|
524
535
|
// Update history.scrollRestoration depending on restoreScrollState
|
|
525
|
-
$: history.scrollRestoration = restoreScrollState ?
|
|
536
|
+
$: history.scrollRestoration = restoreScrollState ? "manual" : "auto"
|
|
526
537
|
let popStateChanged = null
|
|
527
538
|
if (restoreScrollState) {
|
|
528
539
|
popStateChanged = (event) => {
|
|
@@ -537,7 +548,7 @@
|
|
|
537
548
|
}
|
|
538
549
|
}
|
|
539
550
|
// This is removed in the destroy() invocation below
|
|
540
|
-
window.addEventListener(
|
|
551
|
+
window.addEventListener("popstate", popStateChanged)
|
|
541
552
|
|
|
542
553
|
afterUpdate(() => {
|
|
543
554
|
restoreScroll(previousScrollState)
|
|
@@ -570,7 +581,7 @@
|
|
|
570
581
|
location: newLoc.location,
|
|
571
582
|
querystring: newLoc.querystring,
|
|
572
583
|
userData: routesList[i].userData,
|
|
573
|
-
params: (match && typeof match ==
|
|
584
|
+
params: (match && typeof match == "object" && Object.keys(match).length) ? match : null
|
|
574
585
|
}
|
|
575
586
|
|
|
576
587
|
// Check if the route can be loaded - if all conditions succeed
|
|
@@ -579,13 +590,13 @@
|
|
|
579
590
|
component = null
|
|
580
591
|
componentObj = null
|
|
581
592
|
// Trigger an event to notify the user, then exit
|
|
582
|
-
dispatchNextTick(
|
|
593
|
+
dispatchNextTick("conditionsFailed", detail)
|
|
583
594
|
return
|
|
584
595
|
}
|
|
585
596
|
|
|
586
597
|
// Trigger an event to alert that we're loading the route
|
|
587
598
|
// We need to clone the object on every event invocation so we don't risk the object to be modified in the next tick
|
|
588
|
-
dispatchNextTick(
|
|
599
|
+
dispatchNextTick("routeLoading", Object.assign({}, detail))
|
|
589
600
|
|
|
590
601
|
// If there's a component to show while we're loading the route, display it
|
|
591
602
|
const obj = routesList[i].component
|
|
@@ -599,7 +610,7 @@
|
|
|
599
610
|
|
|
600
611
|
// Trigger the routeLoaded event for the loading component
|
|
601
612
|
// Create a copy of detail so we don't modify the object for the dynamic route (and the dynamic route doesn't modify our object too)
|
|
602
|
-
dispatchNextTick(
|
|
613
|
+
dispatchNextTick("routeLoaded", Object.assign({}, detail, {
|
|
603
614
|
component: component,
|
|
604
615
|
name: component.name,
|
|
605
616
|
params: componentParams
|
|
@@ -626,7 +637,7 @@
|
|
|
626
637
|
|
|
627
638
|
// Set componentParams only if we have a match, to avoid a warning similar to `<Component> was created with unknown prop 'params'`
|
|
628
639
|
// Of course, this assumes that developers always add a "params" prop when they are expecting parameters
|
|
629
|
-
if (match && typeof match ==
|
|
640
|
+
if (match && typeof match == "object" && Object.keys(match).length) {
|
|
630
641
|
componentParams = match
|
|
631
642
|
}
|
|
632
643
|
else {
|
|
@@ -638,7 +649,7 @@
|
|
|
638
649
|
|
|
639
650
|
// Dispatch the routeLoaded event then exit
|
|
640
651
|
// We need to clone the object on every event invocation so we don't risk the object to be modified in the next tick
|
|
641
|
-
dispatchNextTick(
|
|
652
|
+
dispatchNextTick("routeLoaded", Object.assign({}, detail, {
|
|
642
653
|
component: component,
|
|
643
654
|
name: component.name,
|
|
644
655
|
params: componentParams
|
|
@@ -656,6 +667,6 @@
|
|
|
656
667
|
|
|
657
668
|
onDestroy(() => {
|
|
658
669
|
unsubscribeLoc()
|
|
659
|
-
popStateChanged && window.removeEventListener(
|
|
670
|
+
popStateChanged && window.removeEventListener("popstate", popStateChanged)
|
|
660
671
|
})
|
|
661
672
|
</script>
|