@keenmate/svelte-spa-router 1.0.2 → 1.0.4

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,29 @@
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 => {
220
225
  ev.stopImmediatePropagation()
221
226
  ev.preventDefault()
222
227
 
223
- const shouldReplace = typeof opts !== 'string' && opts.shouldReplace
228
+ const linkTarget = ev.target.getAttribute("target")
229
+ if (linkTarget && linkTarget !== "_self") {
230
+ // prevent pushState when link is perhaps going outside of this window
231
+ window.open(node.getAttribute("href"), linkTarget)
232
+
233
+ return
234
+ }
235
+
236
+ const shouldReplace = typeof opts !== "string" && opts.shouldReplace
224
237
 
225
- jediForcePush(node.getAttribute('href'), shouldReplace)
226
- window.dispatchEvent(new Event(SvelteSPARouterNavigationEvent))
238
+ jediForcePush(node.getAttribute("href"), shouldReplace)
239
+ // window.dispatchEvent(new Event(SvelteSPARouterNavigationEvent))
227
240
  }, {capture: true})
228
241
  }
229
242
 
@@ -255,15 +268,15 @@
255
268
  function updateLink(node, opts) {
256
269
  const basePath = get(BasePath)
257
270
 
258
- let href = opts.href || node.getAttribute('href')
271
+ let href = opts.href || node.getAttribute("href")
259
272
 
260
273
  if (get(HashRoutingEnabled)) {
261
- if (href && href.charAt(0) == '/') {
274
+ if (href && href.charAt(0) == "/") {
262
275
  // Add # to the href attribute
263
- href = '#' + href
276
+ href = "#" + href
264
277
  }
265
- else if (!href || href.length < 2 || href.slice(0, 2) != '#/') {
266
- throw Error('Invalid value for "href" attribute: ' + href)
278
+ else if (!href || href.length < 2 || href.slice(0, 2) != "#/") {
279
+ throw Error("Invalid value for \"href\" attribute: " + href)
267
280
  }
268
281
  }
269
282
  else {
@@ -272,19 +285,19 @@
272
285
  }
273
286
  }
274
287
 
275
- node.setAttribute('href', href)
276
- node.addEventListener('click', (event) => {
288
+ node.setAttribute("href", href)
289
+ node.addEventListener("click", (event) => {
277
290
  // Prevent default anchor onclick behaviour
278
291
  event.preventDefault()
279
292
  if (!opts.disabled) {
280
- scrollstateHistoryHandler(event.currentTarget.getAttribute('href'))
293
+ scrollstateHistoryHandler(event.currentTarget.getAttribute("href"))
281
294
  }
282
295
  })
283
296
  }
284
297
 
285
298
  // Internal function that ensures the argument of the link action is always an object
286
299
  function linkOpts(val) {
287
- if (val && typeof val == 'string') {
300
+ if (val && typeof val == "string") {
288
301
  return {
289
302
  href: val
290
303
  }
@@ -328,8 +341,8 @@
328
341
  {/if}
329
342
 
330
343
  <script>
331
- import {onDestroy, createEventDispatcher, afterUpdate} from 'svelte'
332
- import {parse} from 'regexparam'
344
+ import {onDestroy, createEventDispatcher, afterUpdate} from "svelte"
345
+ import {parse} from "regexparam"
333
346
 
334
347
  /**
335
348
  * Dictionary of all routes, in the format `'/path': component`.
@@ -351,7 +364,7 @@
351
364
  /**
352
365
  * Optional prefix for the routes in this router. This is useful for example in the case of nested routers.
353
366
  */
354
- export let prefix = ''
367
+ export let prefix = ""
355
368
 
356
369
  /**
357
370
  * If set to true, the router will restore scroll positions on back navigation
@@ -370,16 +383,16 @@
370
383
  * @param {SvelteComponent|WrappedComponent} component - Svelte component for the route, optionally wrapped
371
384
  */
372
385
  constructor(path, component) {
373
- if (!component || (typeof component != 'function' && (typeof component != 'object' || component._sveltesparouter !== true))) {
374
- throw Error('Invalid component object')
386
+ if (!component || (typeof component != "function" && (typeof component != "object" || component._sveltesparouter !== true))) {
387
+ throw Error("Invalid component object")
375
388
  }
376
389
 
377
390
  // Path must be a regular or expression, or a string starting with '/' or '*'
378
391
  if (!path ||
379
- (typeof path == 'string' && (path.length < 1 || (path.charAt(0) != '/' && path.charAt(0) != '*'))) ||
380
- (typeof path == 'object' && !(path instanceof RegExp))
392
+ (typeof path == "string" && (path.length < 1 || (path.charAt(0) != "/" && path.charAt(0) != "*"))) ||
393
+ (typeof path == "object" && !(path instanceof RegExp))
381
394
  ) {
382
- throw Error('Invalid value for "path" argument - strings must start with / or *')
395
+ throw Error("Invalid value for \"path\" argument - strings must start with / or *")
383
396
  }
384
397
 
385
398
  const {pattern, keys} = parse(path)
@@ -387,7 +400,7 @@
387
400
  this.path = path
388
401
 
389
402
  // Check if the component is wrapped and we have conditions
390
- if (typeof component == 'object' && component._sveltesparouter === true) {
403
+ if (typeof component == "object" && component._sveltesparouter === true) {
391
404
  this.component = component.component
392
405
  this.conditions = component.conditions || []
393
406
  this.userData = component.userData
@@ -416,9 +429,9 @@
416
429
  // If there's a prefix, check if it matches the start of the path.
417
430
  // If not, bail early, else remove it before we run the matching.
418
431
  if (prefix) {
419
- if (typeof prefix == 'string') {
432
+ if (typeof prefix == "string") {
420
433
  if (path.startsWith(prefix)) {
421
- path = path.substr(prefix.length) || '/'
434
+ path = path.substr(prefix.length) || "/"
422
435
  }
423
436
  else {
424
437
  return null
@@ -427,7 +440,7 @@
427
440
  else if (prefix instanceof RegExp) {
428
441
  const match = path.match(prefix)
429
442
  if (match && match[0]) {
430
- path = path.substr(match[0].length) || '/'
443
+ path = path.substr(match[0].length) || "/"
431
444
  }
432
445
  else {
433
446
  return null
@@ -451,7 +464,7 @@
451
464
  while (i < this._keys.length) {
452
465
  // In the match parameters, URL-decode all values
453
466
  try {
454
- out[this._keys[i]] = decodeURIComponent(matches[i + 1] || '') || null
467
+ out[this._keys[i]] = decodeURIComponent(matches[i + 1] || "") || null
455
468
  } catch (e) {
456
469
  out[this._keys[i]] = null
457
470
  }
@@ -522,7 +535,7 @@
522
535
  let previousScrollState = null
523
536
 
524
537
  // Update history.scrollRestoration depending on restoreScrollState
525
- $: history.scrollRestoration = restoreScrollState ? 'manual' : 'auto'
538
+ $: history.scrollRestoration = restoreScrollState ? "manual" : "auto"
526
539
  let popStateChanged = null
527
540
  if (restoreScrollState) {
528
541
  popStateChanged = (event) => {
@@ -537,7 +550,7 @@
537
550
  }
538
551
  }
539
552
  // This is removed in the destroy() invocation below
540
- window.addEventListener('popstate', popStateChanged)
553
+ window.addEventListener("popstate", popStateChanged)
541
554
 
542
555
  afterUpdate(() => {
543
556
  restoreScroll(previousScrollState)
@@ -570,7 +583,7 @@
570
583
  location: newLoc.location,
571
584
  querystring: newLoc.querystring,
572
585
  userData: routesList[i].userData,
573
- params: (match && typeof match == 'object' && Object.keys(match).length) ? match : null
586
+ params: (match && typeof match == "object" && Object.keys(match).length) ? match : null
574
587
  }
575
588
 
576
589
  // Check if the route can be loaded - if all conditions succeed
@@ -579,13 +592,13 @@
579
592
  component = null
580
593
  componentObj = null
581
594
  // Trigger an event to notify the user, then exit
582
- dispatchNextTick('conditionsFailed', detail)
595
+ dispatchNextTick("conditionsFailed", detail)
583
596
  return
584
597
  }
585
598
 
586
599
  // Trigger an event to alert that we're loading the route
587
600
  // 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))
601
+ dispatchNextTick("routeLoading", Object.assign({}, detail))
589
602
 
590
603
  // If there's a component to show while we're loading the route, display it
591
604
  const obj = routesList[i].component
@@ -599,7 +612,7 @@
599
612
 
600
613
  // Trigger the routeLoaded event for the loading component
601
614
  // 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, {
615
+ dispatchNextTick("routeLoaded", Object.assign({}, detail, {
603
616
  component: component,
604
617
  name: component.name,
605
618
  params: componentParams
@@ -626,7 +639,7 @@
626
639
 
627
640
  // Set componentParams only if we have a match, to avoid a warning similar to `<Component> was created with unknown prop 'params'`
628
641
  // 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) {
642
+ if (match && typeof match == "object" && Object.keys(match).length) {
630
643
  componentParams = match
631
644
  }
632
645
  else {
@@ -638,7 +651,7 @@
638
651
 
639
652
  // Dispatch the routeLoaded event then exit
640
653
  // 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, {
654
+ dispatchNextTick("routeLoaded", Object.assign({}, detail, {
642
655
  component: component,
643
656
  name: component.name,
644
657
  params: componentParams
@@ -656,6 +669,6 @@
656
669
 
657
670
  onDestroy(() => {
658
671
  unsubscribeLoc()
659
- popStateChanged && window.removeEventListener('popstate', popStateChanged)
672
+ popStateChanged && window.removeEventListener("popstate", popStateChanged)
660
673
  })
661
674
  </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.4",
5
5
  "description": "Router for SPAs using Svelte 4",
6
6
  "main": "Router.svelte",
7
7
  "svelte": "Router.svelte",