navgo 3.0.6 → 3.0.8

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.
Files changed (4) hide show
  1. package/index.d.ts +10 -0
  2. package/index.js +20 -6
  3. package/package.json +5 -2
  4. package/readme.md +42 -20
package/index.d.ts CHANGED
@@ -75,6 +75,8 @@ export interface Options {
75
75
  preload_delay?: number
76
76
  /** Disable hover/touch preloading when `false`. Default true. */
77
77
  preload_on_hover?: boolean
78
+ /** Attach instance to window as `window.navgo`. Default true. */
79
+ attach_to_window?: boolean
78
80
  /** Global hook fired after per-route `before_route_leave`, before loaders/history change. Can cancel. */
79
81
  before_navigate?(_nav: Navigation): void
80
82
  /** Global hook fired after routing completes (data loaded, URL updated, handlers run). */
@@ -110,6 +112,14 @@ export default class Navgo<T = unknown> {
110
112
  init(): Promise<void>
111
113
  /** Remove listeners installed by `init()`. */
112
114
  destroy(): void
115
+ /** Writable store with current { url, route, params }. */
116
+ readonly route: import('svelte/store').Writable<{
117
+ url: URL
118
+ route: RouteTuple<T> | null
119
+ params: Params
120
+ }>
121
+ /** Writable store indicating active navigation. */
122
+ readonly is_navigating: import('svelte/store').Writable<boolean>
113
123
  /** Built-in validator helpers (namespaced). */
114
124
  static validators: ValidatorHelpers
115
125
  }
package/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { parse } from 'regexparam'
2
+ import { tick } from 'svelte'
3
+ import { writable } from 'svelte/store'
2
4
 
3
5
  const ℹ = (...args) => {}
4
6
 
@@ -11,7 +13,8 @@ export default class Navgo {
11
13
  before_navigate: undefined,
12
14
  after_navigate: undefined,
13
15
  url_changed: undefined,
14
- tick: undefined,
16
+ tick,
17
+ attach_to_window: true,
15
18
  }
16
19
  /** @type {Array<{ pattern: RegExp, keys: string[]|null, data: RouteTuple }>} */
17
20
  #routes = []
@@ -36,6 +39,8 @@ export default class Navgo {
36
39
  #nav_active = 0
37
40
  /** @type {(e: Event) => void | null} */
38
41
  #scroll_handler = null
42
+ route = writable({ url: new URL(location.href), route: null, params: {} })
43
+ is_navigating = writable(false)
39
44
 
40
45
  //
41
46
  // Event listeners
@@ -99,7 +104,7 @@ export default class Navgo {
99
104
  this.#current.url = target
100
105
  ℹ(' - [🧭 event:popstate]', 'same path+search; skip loaders')
101
106
  this.#apply_scroll(ev)
102
- this.#opts.url_changed?.(this.#current)
107
+ this.route.set(this.#current)
103
108
  return
104
109
  }
105
110
  // Explicit shallow entries (pushState/replaceState) regardless of path
@@ -107,7 +112,7 @@ export default class Navgo {
107
112
  this.#current.url = target
108
113
  ℹ(' - [🧭 event:popstate]', 'shallow entry; skip loaders')
109
114
  this.#apply_scroll(ev)
110
- this.#opts.url_changed?.(this.#current)
115
+ this.route.set(this.#current)
111
116
  return
112
117
  }
113
118
 
@@ -142,7 +147,7 @@ export default class Navgo {
142
147
  }
143
148
  // update current URL snapshot and notify
144
149
  this.#current.url = new URL(location.href)
145
- this.#opts.url_changed?.(this.#current)
150
+ this.route.set(this.#current)
146
151
  }
147
152
 
148
153
  /** @type {any} */
@@ -296,6 +301,7 @@ export default class Navgo {
296
301
  ℹ('[🧭 goto]', 'invalid url', { url: url_raw })
297
302
  return
298
303
  }
304
+ this.is_navigating.set(true)
299
305
  const { url, path } = info
300
306
 
301
307
  const is_popstate = nav_type === 'popstate'
@@ -325,6 +331,7 @@ export default class Navgo {
325
331
  }
326
332
  }
327
333
  }
334
+ if (nav_id === this.#nav_active) this.is_navigating.set(false)
328
335
  ℹ('[🧭 goto]', 'cancelled by before_route_leave')
329
336
  return
330
337
  }
@@ -417,7 +424,8 @@ export default class Navgo {
417
424
  await this.#opts.tick?.()
418
425
 
419
426
  this.#apply_scroll(nav)
420
- this.#opts.url_changed?.(this.#current)
427
+ this.route.set(this.#current)
428
+ if (nav_id === this.#nav_active) this.is_navigating.set(false)
421
429
  }
422
430
 
423
431
  /**
@@ -455,7 +463,7 @@ export default class Navgo {
455
463
  if (!replace) this.#clear_onward_history()
456
464
  // update current URL snapshot and notify
457
465
  this.#current.url = u
458
- this.#opts.url_changed?.(this.#current)
466
+ this.route.set(this.#current)
459
467
  }
460
468
 
461
469
  /** @param {string|URL} [url] @param {any} [state] */
@@ -563,6 +571,11 @@ export default class Navgo {
563
571
  return pat
564
572
  })
565
573
 
574
+ // TODO: deprecated, remove later
575
+ this.route.subscribe(() => {
576
+ this.#opts.url_changed?.(this.#current)
577
+ })
578
+
566
579
  ℹ('[🧭 init]', {
567
580
  base: this.#base,
568
581
  routes: this.#routes.length,
@@ -608,6 +621,7 @@ export default class Navgo {
608
621
  }
609
622
 
610
623
  ℹ('[🧭 init]', 'initial goto')
624
+ if (this.#opts.attach_to_window) window.navgo = this
611
625
  return this.goto()
612
626
  }
613
627
  destroy() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navgo",
3
- "version": "3.0.6",
3
+ "version": "3.0.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mustafa0x/navgo.git"
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "perl -0777 -i -pe 's/console.debug\\(...args\\)/{}/g' index.js",
19
- "prepublishOnly": "pnpm test && pnpm run build",
19
+ "prepublishOnly": "pnpm run build",
20
20
  "test": "vitest run index.test.js",
21
21
  "test:e2e": "playwright test",
22
22
  "start:testsite": "pnpm vite dev test/site --port 5714",
@@ -34,6 +34,9 @@
34
34
  "dependencies": {
35
35
  "regexparam": "^3.0.0"
36
36
  },
37
+ "peerDependencies": {
38
+ "svelte": "5"
39
+ },
37
40
  "devDependencies": {
38
41
  "@eslint/js": "^9.37.0",
39
42
  "@playwright/test": "^1.56.0",
package/readme.md CHANGED
@@ -103,16 +103,38 @@ Notes:
103
103
  - `tick`: `() => void | Promise<void>`
104
104
  - Awaited after `after_navigate` and before scroll handling; useful for frameworks to flush DOM so anchor/top scrolling lands correctly.
105
105
  - `url_changed`: `(snapshot: any) => void`
106
- - Fires on every URL change shallow `push_state`/`replace_state`, hash changes, `popstate` shallow entries, 404s, and full navigations.
106
+ - Fires on every URL change -- shallow `push_state`/`replace_state`, hash changes, `popstate` shallow entries, 404s, and full navigations. (deprecated; subscribe to `.route` instead.)
107
107
  - Receives the router's current snapshot: an object like `{ url: URL, route: RouteTuple|null, params: Params }`.
108
108
  - The snapshot type is intentionally `any` and may evolve without a breaking change.
109
109
  - `preload_delay`: `number` (default `20`)
110
110
  - Delay in ms before hover preloading triggers.
111
111
  - `preload_on_hover`: `boolean` (default `true`)
112
112
  - When `false`, disables hover/touch preloading.
113
+ - `attach_to_window`: `boolean` (default `true`)
114
+ - When `true`, `init()` attaches the instance to `window.navgo` for convenience.
113
115
 
114
116
  Important: Navgo only processes routes that match your `base` path.
115
117
 
118
+ ### Instance stores
119
+
120
+ - `router.route` -- `Writable<{ url: URL; route: RouteTuple|null; params: Params }>`
121
+ - Readonly property that holds the current snapshot.
122
+ - Subscribe to react to changes; Navgo updates it on every URL change.
123
+ - `router.is_navigating` -- `Writable<boolean>`
124
+ - `true` while a navigation is in flight (between start and completion/cancel).
125
+
126
+ Example:
127
+
128
+ ```svelte
129
+ Current path: {$route.path}
130
+ <div class="request-indicator" class:active={$is_navigating}></div>
131
+
132
+ <script>
133
+ const router = new Navgo(...)
134
+ const {route, is_navigating} = router
135
+ </script>
136
+ ```
137
+
116
138
  ### Route Hooks
117
139
 
118
140
  - param_validators?: `Record<string, (value: string|null|undefined) => boolean>`
@@ -225,8 +247,8 @@ While listening, link clicks are intercepted and translated into `goto()` naviga
225
247
 
226
248
  In addition, `init()` wires preloading listeners (enabled by default) so route data can be fetched early:
227
249
 
228
- - `mousemove` (hover) after a short delay, hovering an in-app link triggers `preload(href)`.
229
- - `touchstart` and `mousedown` (tap) tapping or pressing on an in-app link also triggers `preload(href)`.
250
+ - `mousemove` (hover) -- after a short delay, hovering an in-app link triggers `preload(href)`.
251
+ - `touchstart` and `mousedown` (tap) -- tapping or pressing on an in-app link also triggers `preload(href)`.
230
252
 
231
253
  Preloading applies only to in-app anchors that match the configured [`base`](#base). You can tweak this behavior with the `preload_delay` and `preload_on_hover` options.
232
254
 
@@ -287,10 +309,10 @@ This section explains, in detail, how navigation is processed: matching, hooks,
287
309
 
288
310
  ### Navigation Types
289
311
 
290
- - `link` user clicked an in-app `<a>` that matches `base`.
291
- - `goto` programmatic navigation via `router.goto(...)`.
292
- - `popstate` browser back/forward.
293
- - `leave` page is unloading (refresh, external navigation, tab close) via `beforeunload`.
312
+ - `link` -- user clicked an in-app `<a>` that matches `base`.
313
+ - `goto` -- programmatic navigation via `router.goto(...)`.
314
+ - `popstate` -- browser back/forward.
315
+ - `leave` -- page is unloading (refresh, external navigation, tab close) via `beforeunload`.
294
316
 
295
317
  The router passes the type to your route-level `before_route_leave(nav)` hook.
296
318
 
@@ -360,19 +382,19 @@ scroll flow
360
382
 
361
383
  ### Method-by-Method Semantics
362
384
 
363
- - `format(uri)` normalizes a path relative to `base`. Returns `false` when `uri` is outside of `base`.
364
- - `match(uri)` returns a Promise of `{ route, params } | null` using string/RegExp patterns and validators. Awaits an async `validate(params)` if provided.
365
- - `goto(uri, { replace? })` fires route-level `before_route_leave('goto')`, calls global `before_navigate`, saves scroll, runs loaders, pushes/replaces, and completes via `after_navigate`.
366
- - `init()` wires global listeners (`popstate`, `pushstate`, `replacestate`, click) and optional hover/tap preloading; immediately processes the current location.
367
- - `destroy()` removes listeners added by `init()`.
368
- - `preload(uri)` pre-executes a route's `loaders` for a path and caches the result; concurrent calls are deduped.
369
- - `push_state(url?, state?)` shallow push that updates the URL and `history.state` without route processing.
370
- - `replace_state(url?, state?)` shallow replace that updates the URL and `history.state` without route processing.
385
+ - `format(uri)` -- normalizes a path relative to `base`. Returns `false` when `uri` is outside of `base`.
386
+ - `match(uri)` -- returns a Promise of `{ route, params } | null` using string/RegExp patterns and validators. Awaits an async `validate(params)` if provided.
387
+ - `goto(uri, { replace? })` -- fires route-level `before_route_leave('goto')`, calls global `before_navigate`, saves scroll, runs loaders, pushes/replaces, and completes via `after_navigate`.
388
+ - `init()` -- wires global listeners (`popstate`, `pushstate`, `replacestate`, click) and optional hover/tap preloading; immediately processes the current location.
389
+ - `destroy()` -- removes listeners added by `init()`.
390
+ - `preload(uri)` -- pre-executes a route's `loaders` for a path and caches the result; concurrent calls are deduped.
391
+ - `push_state(url?, state?)` -- shallow push that updates the URL and `history.state` without route processing.
392
+ - `replace_state(url?, state?)` -- shallow replace that updates the URL and `history.state` without route processing.
371
393
 
372
394
  ### Built-in Validators
373
395
 
374
- - `Navgo.validators.int({ min?, max? })` `true` iff the value is an integer within optional bounds.
375
- - `Navgo.validators.one_of(iterable)` `true` iff the value is in the provided set.
396
+ - `Navgo.validators.int({ min?, max? })` -- `true` iff the value is an integer within optional bounds.
397
+ - `Navgo.validators.one_of(iterable)` -- `true` iff the value is in the provided set.
376
398
 
377
399
  Attach validators via a route tuple's `data.param_validators` to constrain matches.
378
400
 
@@ -380,6 +402,6 @@ Attach validators via a route tuple's `data.param_validators` to constrain match
380
402
 
381
403
  This router integrates ideas and small portions of code from these fantastic projects:
382
404
 
383
- - SvelteKit https://github.com/sveltejs/kit
384
- - navaid https://github.com/lukeed/navaid
385
- - TanStack Router https://github.com/TanStack/router
405
+ - SvelteKit -- https://github.com/sveltejs/kit
406
+ - navaid -- https://github.com/lukeed/navaid
407
+ - TanStack Router -- https://github.com/TanStack/router