@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 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 'svelte/store'
3
- import {tick} from 'svelte'
4
- import {SvelteSPARouterNavigationEvent} from './constants.js'
5
- import {joinPaths} from './helpers/url-helpers.js'
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 = '/' + window.location.pathname.substring(basePath.length)
35
- }
36
-
37
- // Check if there's a querystring
38
- const qsPosition = location.indexOf('?')
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
- 'hashchange' :
62
+ "hashchange" :
59
63
  SvelteSPARouterNavigationEvent
60
- console.log('Setting loc')
64
+ // console.log("Setting loc")
61
65
  const update = () => {
62
- console.log('Updating location', getLocation())
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(location, shouldReplace = false) {
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 (!location || location.length < 1 || !/^(\/|#\/)/.test(location)) {
134
- throw Error('Invalid parameter location')
137
+ if (!location_ || location_.length < 1 || !/^(\/|#\/)/.test(location_)) {
138
+ throw Error("Invalid parameter location")
135
139
  }
136
140
  }
137
141
  else {
138
- if (!location || location.length < 1 || !/^\//.test(location)) {
139
- throw Error('Invalid parameter location')
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 = (location.charAt(0) === '#' ? '' : '#') + location
165
+ window.location.hash = (location_.charAt(0) === "#" ? "" : "#") + location_
162
166
  }
163
167
  else {
164
- window.dispatchEvent(new Event('hashchange'))
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() != 'a') {
213
- throw Error('Action "link" can only be used with <a> tags')
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('click', ev => {
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 !== 'string' && opts.shouldReplace
234
+ const shouldReplace = typeof opts !== "string" && opts.shouldReplace
224
235
 
225
- jediForcePush(node.getAttribute('href'), shouldReplace)
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('href')
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 = '#' + href
274
+ href = "#" + href
264
275
  }
265
- else if (!href || href.length < 2 || href.slice(0, 2) != '#/') {
266
- throw Error('Invalid value for "href" attribute: ' + href)
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('href', href)
276
- node.addEventListener('click', (event) => {
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('href'))
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 == 'string') {
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 'svelte'
332
- import {parse} from 'regexparam'
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 != 'function' && (typeof component != 'object' || component._sveltesparouter !== true))) {
374
- throw Error('Invalid component object')
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 == 'string' && (path.length < 1 || (path.charAt(0) != '/' && path.charAt(0) != '*'))) ||
380
- (typeof path == 'object' && !(path instanceof RegExp))
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('Invalid value for "path" argument - strings must start with / or *')
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 == 'object' && component._sveltesparouter === true) {
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 == 'string') {
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] || '') || null
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 ? 'manual' : 'auto'
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('popstate', popStateChanged)
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 == 'object' && Object.keys(match).length) ? match : null
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('conditionsFailed', detail)
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('routeLoading', Object.assign({}, detail))
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('routeLoaded', Object.assign({}, detail, {
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 == 'object' && Object.keys(match).length) {
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('routeLoaded', Object.assign({}, detail, {
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('popstate', popStateChanged)
670
+ popStateChanged && window.removeEventListener("popstate", popStateChanged)
660
671
  })
661
672
  </script>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@keenmate/svelte-spa-router",
3
3
  "private": false,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "description": "Router for SPAs using Svelte 4",
6
6
  "main": "Router.svelte",
7
7
  "svelte": "Router.svelte",