@aegisjsproject/atlas 1.0.0 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [v1.0.2] - 2026-04-30
11
+
12
+ ### Added
13
+ - Implement View Transitions (resolves #6)
14
+ - Support router handling on initial load (resolves #5)
15
+ - Handle page metadata via functions (resolves #4)
16
+
17
+ ## [v1.0.1] - 22026-04-29
18
+
19
+ ### Fixed
20
+ - Fix resolving module specifiers
21
+
10
22
  ## [v1.0.0] - 2026-04-29
11
23
 
12
24
  Initial Release
package/atlas.cjs CHANGED
@@ -5,6 +5,12 @@
5
5
  */
6
6
  const reg = new Map();
7
7
 
8
+ const cache = new Map();
9
+
10
+ const PATH_EXP = /^(?:\.*\/)+/;
11
+
12
+ const isBareSpecifier = specifier => ! PATH_EXP.test(specifier);
13
+
8
14
  /**
9
15
  * @typedef RouteMatch
10
16
  * @property {URLPatternResult|null} result The results of `pattern.exec(url)`
@@ -26,6 +32,13 @@ const invalidMatchResult = Object.freeze({ result: null, specifier: null, hasReg
26
32
  */
27
33
  const getRegistryKey = url => reg.keys().find(pattern => pattern.test(url));
28
34
 
35
+ /**
36
+ * Checks if a route for the given URL is registered
37
+ * @param {string} [url=location.href]
38
+ * @return {boolean}
39
+ */
40
+ const hasRegistryKey = (url = location.href) => reg.keys().some(pattern => pattern.test(url));
41
+
29
42
  /**
30
43
  *
31
44
  * @param {URLPattern} key
@@ -39,16 +52,37 @@ const getRegistrySpecifier = key => reg.get(key);
39
52
  * @returns {RouteMatch}
40
53
  */
41
54
  function lookupRoute(url) {
42
- const key = getRegistryKey(url);
55
+ if (cache.has(url)) {
56
+ return cache.get(url);
57
+ } else {
58
+ const key = getRegistryKey(url);
43
59
 
44
- if (key instanceof URLPattern) {
45
- return Object.freeze({
46
- result: key.exec(url),
47
- specifier: reg.get(key),
48
- hasRegExpGroups: key.hasRegExpGroups,
49
- });
60
+ if (key instanceof URLPattern) {
61
+ const match = Object.freeze({
62
+ result: key.exec(url),
63
+ specifier: reg.get(key),
64
+ hasRegExpGroups: key.hasRegExpGroups,
65
+ });
66
+
67
+ cache.set(url, match);
68
+ return match;
69
+ } else {
70
+ cache.set(url, invalidMatchResult);
71
+ return invalidMatchResult;
72
+ }
73
+
74
+ }
75
+ }
76
+
77
+ function resolveSpecifier(specifier) {
78
+ if (isBareSpecifier(specifier)) {
79
+ return undefined(specifier);
80
+ } else if (URL.canParse(specifier)) {
81
+ return specifier;
82
+ } else if (specifier instanceof URL) {
83
+ return specifier.href;
50
84
  } else {
51
- return invalidMatchResult;
85
+ return new URL (specifier, document.baseURI).href;
52
86
  }
53
87
  }
54
88
 
@@ -64,14 +98,12 @@ function registerModule(pattern, specifier) {
64
98
  } else if (typeof pattern === 'string') {
65
99
  reg.set(
66
100
  URL.canParse(pattern) ? new URLPattern(pattern) : new URLPattern({ pathname: pattern }),
67
- specifier.toString()
101
+ resolveSpecifier(specifier)
68
102
  );
69
103
  } else if (! (pattern instanceof URLPattern)) {
70
104
  throw new TypeError(`Invalid pattner "${pattern}".`);
71
- } else if (specifier instanceof URL) {
72
- reg.set(pattern, specifier.href);
73
- } else {
74
- reg.set(pattern, specifier);
105
+ } else {
106
+ reg.set(pattern, resolveSpecifier(specifier));
75
107
  }
76
108
  }
77
109
 
@@ -267,10 +299,7 @@ async function preloadOnHover(target, {
267
299
  const pattern = getRegistryKey(currentTarget.href);
268
300
 
269
301
  if (pattern instanceof URLPattern) {
270
- const specifier = getRegistrySpecifier(pattern);
271
- const resolved = undefined(specifier);
272
-
273
- await preloadModule(resolved, {
302
+ await preloadModule(getRegistrySpecifier(pattern), {
274
303
  fetchPriority,
275
304
  referrerPolicy,
276
305
  crossOrigin,
@@ -343,7 +372,8 @@ const DESC_SELECTOR = 'meta[name="description"], meta[itemprop="description"], m
343
372
  * @property {AbortController} controller
344
373
  * @property {AbortSignal} signal
345
374
  * @property {NavigationType} type
346
- * @property {URL} url
375
+ * @property {URL} newURL
376
+ * @property {URL} oldURL
347
377
  * @property {any} state
348
378
  * @property {any} info
349
379
  * @property {number} timestamp
@@ -400,8 +430,9 @@ function getRequestMethod(source) {
400
430
  /**
401
431
  *
402
432
  * @param {NavigationEvent} event
433
+ * @param {URL} oldURL
403
434
  */
404
- async function handleNavigation(event) {
435
+ async function handleNavigation(event, oldURL = new URL(location.href)) {
405
436
  if (! (event instanceof NavigateEvent)) {
406
437
  throw new TypeError('Not a navigation event.');
407
438
  } else if (event.signal.aborted) {
@@ -411,15 +442,18 @@ async function handleNavigation(event) {
411
442
  const request = new Request(event.destination.url, {
412
443
  // `sourceElement` could be a form, a `<button type="submit">`, or an `<a>
413
444
  method: method,
414
- body: method === 'GET' ? undefined : event.formData,// ?? new FormData(event.sourceElement?.form ?? event.sourceElement),
445
+ body: method === 'GET' ? undefined : event.formData,
415
446
  signal: event.signal,
447
+ mode: 'same-origin',
448
+ credentials: 'include',
449
+ priority: 'high',
416
450
  });
417
451
 
418
452
  const { result, specifier, hasRegExpGroups } = lookupRoute(event.destination.url);
419
453
 
420
454
  if (typeof specifier !== 'string' || result === null) {
421
455
  const resp = await fetch(request);
422
- await updateContent(resp);
456
+ await updateContent(resp, {});
423
457
  } else {
424
458
  const params = hasRegExpGroups ? {
425
459
  ...result.protocol.groups, ...result.username.groups, ...result.password.groups, ...result.hostname.groups,
@@ -437,6 +471,12 @@ async function handleNavigation(event) {
437
471
  const timestamp = performance.now();
438
472
  const signal = AbortSignal.any([controller.signal, request.signal]);
439
473
 
474
+ /**
475
+ * Dispose of stack on next navigation or on error, triggering clean-up
476
+ */
477
+ navigation.addEventListener('navigate', () => stack.dispose(), { once: true, signal });
478
+ navigation.addEventListener('navigateerror', () => stack.dispose(), { once: true, signal });
479
+
440
480
  /**
441
481
  * @type {RouteContextObject}
442
482
  */
@@ -447,7 +487,8 @@ async function handleNavigation(event) {
447
487
  type: event.navigationType,
448
488
  state: event.destination.getState(),
449
489
  info: event.info,
450
- url: new URL(event.destination.url),
490
+ newURL: new URL(event.destination.url),
491
+ oldURL,
451
492
  signal,
452
493
  result,
453
494
  params,
@@ -457,8 +498,7 @@ async function handleNavigation(event) {
457
498
  return await handleRequestModule(request, context, module);
458
499
  } catch(err) {
459
500
  reportError(err);
460
- } finally {
461
- stack.dispose();
501
+ stack.dispose(); // Ensure clean-up happens even in an error
462
502
  }
463
503
  }
464
504
  }
@@ -478,9 +518,9 @@ function init(routes, {
478
518
  signal,
479
519
  } = {}) {
480
520
  if (typeof routes === 'string') {
481
- init(JSON.parse(document.scripts.namedItem(routes).textContent), { root, preload, signal });
521
+ init(JSON.parse(document.scripts.namedItem(routes).innerHTML), { root, preload, signal });
482
522
  } else if (typeof routes === 'number') {
483
- init(JSON.parse(document.scripts.item(routes).textContent), { root, preload, signal });
523
+ init(JSON.parse(document.scripts.item(routes).innerHTML), { root, preload, signal });
484
524
  } else if (routes instanceof HTMLScriptElement) {
485
525
  init(JSON.parse(routes.textContent), { root, preload, signal });
486
526
  } else if (typeof routes === 'object') {
@@ -491,14 +531,19 @@ function init(routes, {
491
531
  }
492
532
 
493
533
  navigation.addEventListener('navigate', event => {
534
+ const oldURL = new URL(location.href);
494
535
  if (event.canIntercept && event.destination.url.startsWith(location.origin) && ! event.sourceElement?.classList?.contains?.('no-router')) {
495
- event.intercept({ handler: () => handleNavigation(event) });
536
+ event.intercept({ handler: () => handleNavigation(event, oldURL) });
496
537
  }
497
538
  }, { signal });
498
539
 
499
540
  if (preload) {
500
541
  observePreloadsOn(document.body);
501
542
  }
543
+
544
+ if (hasRegistryKey(location.href)) {
545
+ navigation.reload({ info: 'Initial Load', state: navigation.currentEntry.getState() });
546
+ }
502
547
  } else {
503
548
  throw new TypeError(`Routes must be an object, \`<script>\`, or name/index of \`document.scripts\`. Got a ${typeof routes}.`);
504
549
  }
@@ -582,28 +627,42 @@ async function handleRequestModule(request, context, module) {
582
627
  if (typeof module.default === 'undefined') {
583
628
  throw new TypeError(`No default export in module for <${request.url}>.`);
584
629
  } else if (typeof module.default === 'function') {
585
- const result = await module.default(request, context);
586
- await updateContent(result);
587
- updateMeta(module);
630
+ const result = module.default.prototype instanceof HTMLElement
631
+ ? new module.default(request, context)
632
+ : await module.default(request, context);
633
+
634
+ await updateContent(result, module);
635
+ updateMeta(module, context);
588
636
  } else {
589
637
  await updateContent(module.default);
590
- updateMeta(module);
638
+ updateMeta(module, context);
591
639
  }
592
640
  }
593
641
 
594
- function updateMeta({ title, description, styles }) {
642
+ async function updateMeta({ title, description, styles }, context) {
595
643
  if (typeof title === 'string') {
596
644
  document.title = title;
645
+ } else if (typeof title === 'function') {
646
+ document.title = await title(context);
597
647
  }
598
648
 
599
649
  if (typeof description === 'string') {
600
650
  setDescription(description);
651
+ } else if (typeof description === 'function') {
652
+ setDescription(await description(context));
601
653
  }
602
654
 
603
655
  if (styles instanceof CSSStyleSheet) {
604
656
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
605
657
  } else if (Array.isArray(styles) && styles.length !== 0) {
606
658
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...styles];
659
+ } else if (typeof styles === 'function') {
660
+ const newStyles = await styles(context);
661
+ if (newStyles instanceof CSSStyleSheet) {
662
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, newStyles];
663
+ } else if (Array.isArray(newStyles) && newStyles.length !== 0) {
664
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...newStyles];
665
+ }
607
666
  }
608
667
  }
609
668
 
@@ -611,7 +670,7 @@ function updateMeta({ title, description, styles }) {
611
670
  *
612
671
  * @param {HandlerResult} content
613
672
  */
614
- async function updateContent(content) {
673
+ async function updateContent(content, { viewTransitionTypes: types = [] } = {}) {
615
674
  if (content instanceof URL) {
616
675
  navigate(content);
617
676
  } else if (content instanceof Response) {
@@ -623,18 +682,33 @@ async function updateContent(content) {
623
682
  const html = await content.text();
624
683
  /** @type HTMLDocument */
625
684
  const doc = Document.parseHTMLUnsafe(policy.createHTML(html)); // Unsafe, but necessary... Same-origin at least
626
- await updateContent(doc);
685
+ await updateContent(doc, { viewTransitionTypes: types });
627
686
  }
628
687
  } else if (content instanceof Element || content instanceof DocumentFragment) {
629
- root.replaceChildren(content);
688
+ await document.startViewTransition({
689
+ types,
690
+ update() {
691
+ root.replaceChildren(content);
692
+ },
693
+ });
630
694
  } else if (content instanceof HTMLDocument) {
631
695
  document.title = content.title;
632
696
  setDescription(content.head.querySelector(DESC_SELECTOR)?.content);
633
697
 
634
698
  if (root instanceof HTMLBodyElement) {
635
- root.replaceChildren(...content.body.childNodes);
699
+ await document.startViewTransition({
700
+ types,
701
+ update() {
702
+ root.replaceChildren(...content.body.childNodes);
703
+ }
704
+ });
636
705
  } else if (root instanceof HTMLElement && typeof root.id === 'string') {
637
- root.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);
706
+ await document.startViewTransition({
707
+ types,
708
+ update() {
709
+ root.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);
710
+ }
711
+ });
638
712
  } else {
639
713
  throw new TypeError('Root must be `<body>` or an element with an `id`.');
640
714
  }
package/atlas.min.js CHANGED
@@ -1,2 +1,2 @@
1
- const e=new Map,t=Object.freeze({result:null,specifier:null,hasRegExpGroups:!1}),n=t=>e.keys().find((e=>e.test(t))),r=t=>e.get(t);function o(r){const o=n(r);return o instanceof URLPattern?Object.freeze({result:o.exec(r),specifier:e.get(o),hasRegExpGroups:o.hasRegExpGroups}):t}function i(t,n){if(!("string"==typeof n||n instanceof URL))throw new TypeError(`Invalid specifier type ${typeof n}.`);if("string"==typeof t)e.set(URL.canParse(t)?new URLPattern(t):new URLPattern({pathname:t}),n.toString());else{if(!(t instanceof URLPattern))throw new TypeError(`Invalid pattner "${t}".`);n instanceof URL?e.set(t,n.href):e.set(t,n)}}function a(e,{relList:t=[],crossOrigin:n="anonymous",referrerPolicy:r="no-referrer",fetchPriority:o="auto",signal:i,as:a,integrity:s,media:c,type:l}={}){const{promise:f,resolve:d,reject:p}=Promise.withResolvers(),g=document.createElement("link");if(i instanceof AbortSignal&&i.aborted)p(i.reason);else{if("string"==typeof e||e instanceof URL){if(g.relList.add(...t),"string"==typeof o&&(g.fetchPriority=o),"string"==typeof n&&(g.crossOrigin=n),"string"==typeof l&&(g.type=l),"string"==typeof c?g.media=c:c instanceof MediaQueryList&&(g.media=c.media),"string"==typeof a&&(g.as=a),"string"==typeof s&&(g.integrity=s),g.relList.contains("preload")||g.relList.contains("modulepreload")){const t=new AbortController,n=i instanceof AbortSignal?AbortSignal.any([t.signal,i]):t.signal;return i instanceof AbortSignal&&i.addEventListener("abort",(({target:e})=>{p(e.reason)}),{signal:t.signal,once:!0}),g.referrerPolicy=r,g.addEventListener("load",(()=>{d(),t.abort()}),{signal:n}),g.addEventListener("error",(()=>{p(new DOMException(`Error loading ${e}`,"NotFoundError")),t.abort()}),{signal:n}),g.href=e,document.head.append(g),f.catch(reportError).finally((()=>g.isConnected&&g.remove()))}return g.href=e,document.head.append(g),d(),f}p(new TypeError(`Invalid href to preload: "${e}.`))}}function s(e){e instanceof MutationRecord?s(e.target):"A"!==e.tagName||e.classList.contains("no-router")?e.querySelectorAll("a:not(.no-router)").forEach((e=>d(e,e.dataset))):d(e,e.dataset)}const c=new MutationObserver((e=>e.forEach(s)));async function l(e,{crossOrigin:t="anonymous",referrerPolicy:n="no-referrer",fetchPriority:r="low",as:o="script",signal:i,integrity:s}={}){await a(e,{relList:["modulepreload"],crossOrigin:t,referrerPolicy:n,fetchPriority:r,as:o,signal:i,integrity:s})}async function f(e,{crossOrigin:t="anonymous",referrerPolicy:n="no-referrer",fetchPriority:r="auto",signal:o,as:i,integrity:s,media:c,type:l}={}){await a(e,{relList:["preload"],crossOrigin:t,referrerPolicy:n,fetchPriority:r,as:i,signal:o,type:l,media:c,integrity:s})}async function d(e,{crossOrigin:t="anonymous",referrerPolicy:o="no-referrer",fetchPriority:i="high",signal:a}={}){const{resolve:s,reject:c,promise:p}=Promise.withResolvers();"string"==typeof e?await Promise.all(Array.from(document.querySelectorAll(e),(e=>d(e)))).then(s,c):e instanceof HTMLElement&&!e.classList.contains("no-router")&&"string"==typeof e.href&&e.origin===location.origin&&0===e.download.length&&URL.canParse(e.href)?e.addEventListener("mouseover",(async({currentTarget:e})=>{const c=n(e.href);if(c instanceof URLPattern){const n=r(c),f=import.meta.resolve(n);await l(f,{fetchPriority:i,referrerPolicy:o,crossOrigin:t,integrity:e.dataset.integrity,signal:a}),s()}else await f(e.href,{fetchPriority:i,crossOrigin:t,referrerPolicy:o,as:e.dataset.preloadAs??"fetch",type:e.dataset.preloadType??"text/html",integrity:e.dataset.integrity,signal:a}),s()}),{once:!0,passive:!0,signal:a}):s(),await p}function p(e,t=document.documentElement){if("string"==typeof e)p(t.querySelector(e));else{if(!(e instanceof HTMLElement||e instanceof ShadowRoot))throw new TypeError("`observePreloadsOn` requires a selector or HTMLElement or ShadowRoot.");c.observe(e,{childList:!0,subtree:!0}),s(e)}}const g="trustedTypes"in globalThis?trustedTypes.createPolicy("aegis-atlas#html",{createHTML:e=>e}):Object.freeze({createHTML:e=>e}),u='meta[name="description"], meta[itemprop="description"], meta[property="og:description"], meta[name="twitter:description"]';let y=document.body;function m(e,t=document){if("string"==typeof e)m(t.getElementById(e));else{if(!(e instanceof HTMLElement))throw new TypeError("New root must be an `Element` or `id` of an element.");y=e}}async function h(e){if(!(e instanceof NavigateEvent))throw new TypeError("Not a navigation event.");if(e.signal.aborted)throw e.signal.reason;{const n=!((t=e.sourceElement)instanceof HTMLElement)||t instanceof HTMLAnchorElement?"GET":t instanceof HTMLFormElement?t.method.toUpperCase():t instanceof HTMLButtonElement?t.hasAttribute("formmethod")&&0!==t.formMethod.length?t.formMethod.toUpperCase():t.form instanceof HTMLFormElement?t.form.method.toUpperCase():(console.warn("Not sure this should be possible..."),"GET"):"GET",r=new Request(e.destination.url,{method:n,body:"GET"===n?void 0:e.formData,signal:e.signal}),{result:i,specifier:a,hasRegExpGroups:s}=o(e.destination.url);if("string"!=typeof a||null===i){const e=await fetch(r);await P(e)}else{const t=s?{...i.protocol.groups,...i.username.groups,...i.password.groups,...i.hostname.groups,...i.port.groups,...i.pathname.groups,...i.search.groups,...i.hash.groups}:{};delete t[0];const n=await import(a),o=new DisposableStack,c=o.adopt(new AbortController,(e=>e.abort(new DOMException("Stack was disposed.","AbortError")))),l=performance.now(),f=AbortSignal.any([c.signal,r.signal]),d=Object.freeze({timestamp:l,stack:o,controller:c,type:e.navigationType,state:e.destination.getState(),info:e.info,url:new URL(e.destination.url),signal:f,result:i,params:t});try{return await async function(e,t,n){if(void 0===n.default)throw new TypeError(`No default export in module for <${e.url}>.`);if("function"==typeof n.default){const r=await n.default(e,t);await P(r),S(n)}else await P(n.default),S(n)}(r,d,n)}catch(e){reportError(e)}finally{o.dispose()}}}var t}function w(e,{root:t,preload:n=!1,signal:r}={}){if("string"==typeof e)w(JSON.parse(document.scripts.namedItem(e).textContent),{root:t,preload:n,signal:r});else if("number"==typeof e)w(JSON.parse(document.scripts.item(e).textContent),{root:t,preload:n,signal:r});else if(e instanceof HTMLScriptElement)w(JSON.parse(e.textContent),{root:t,preload:n,signal:r});else{if("object"!=typeof e)throw new TypeError(`Routes must be an object, \`<script>\`, or name/index of \`document.scripts\`. Got a ${typeof e}.`);Object.entries(e).forEach((([e,t])=>i(e,t))),("string"==typeof t||t instanceof HTMLElement)&&m(t),navigation.addEventListener("navigate",(e=>{e.canIntercept&&e.destination.url.startsWith(location.origin)&&!e.sourceElement?.classList?.contains?.("no-router")&&e.intercept({handler:()=>h(e)})}),{signal:r}),n&&p(document.body)}}async function E({signal:e}={}){const{resolve:t,reject:n,promise:r}=Promise.withResolvers();if(e?.aborted)n(e.reason);else{const r=new AbortController,o={once:!0,signal:e instanceof AbortSignal?AbortSignal.any([e,r.signal]):r.signal};navigation.addEventListener("navigatesuccess",(()=>{t(navigation.currentEntry),r.abort()}),o),navigation.addEventListener("navigateerror",(e=>{n(e.error),r.abort()}),o),e instanceof AbortSignal&&e.addEventListener("abort",(({target:e})=>{n(e.reason),r.abort(e.reason)}),{once:!0,signal:r.signal})}return r}const b=(e,t)=>navigation.navigate(e,t),L=e=>navigation.back(e),v=e=>navigation.forward(e),T=e=>navigation.reload(e);function S({title:e,description:t,styles:n}){"string"==typeof e&&(document.title=e),"string"==typeof t&&M(t),n instanceof CSSStyleSheet?document.adoptedStyleSheets=[...document.adoptedStyleSheets,n]:Array.isArray(n)&&0!==n.length&&(document.adoptedStyleSheets=[...document.adoptedStyleSheets,...n])}async function P(e){if(e instanceof URL)b(e);else if(e instanceof Response){if(!e.ok)throw new DOMException(`${e.url} [${e.status}]`,"NetworkError");if(!e.headers.get("Content-Type")?.startsWith?.("text/html"))throw new TypeError(`Unsupported Content-Type for <${e.url}> - "${e.headers.get("Content-Type")??"Unset"}".`);{const t=await e.text(),n=Document.parseHTMLUnsafe(g.createHTML(t));await P(n)}}else if(e instanceof Element||e instanceof DocumentFragment)y.replaceChildren(e);else{if(!(e instanceof HTMLDocument))throw new TypeError("Content must be an `Element`, `DocumentFragment`, `HTMLDocument`, or `Response`.");if(document.title=e.title,M(e.head.querySelector(u)?.content),y instanceof HTMLBodyElement)y.replaceChildren(...e.body.childNodes);else{if(!(y instanceof HTMLElement&&"string"==typeof y.id))throw new TypeError("Root must be `<body>` or an element with an `id`.");y.replaceChildren(...e.getElementById(y.id)?.childNodes??[])}}}function M(e=""){document.head.querySelectorAll(u).forEach((t=>t.content=e))}export{L as back,v as forward,n as getRegistryKey,r as getRegistrySpecifier,h as handleNavigation,w as init,o as lookupRoute,b as navigate,p as observePreloadsOn,f as preload,l as preloadModule,d as preloadOnHover,i as registerModule,T as reload,m as setRoot,E as whenLoaded};
1
+ const e=new Map,t=new Map,n=/^(?:\.*\/)+/,r=Object.freeze({result:null,specifier:null,hasRegExpGroups:!1}),o=t=>e.keys().find((e=>e.test(t))),i=t=>e.get(t);function a(n){if(t.has(n))return t.get(n);{const i=o(n);if(i instanceof URLPattern){const r=Object.freeze({result:i.exec(n),specifier:e.get(i),hasRegExpGroups:i.hasRegExpGroups});return t.set(n,r),r}return t.set(n,r),r}}function s(e){return(e=>!n.test(e))(e)?import.meta.resolve(e):URL.canParse(e)?e:e instanceof URL?e.href:new URL(e,document.baseURI).href}function c(t,n){if(!("string"==typeof n||n instanceof URL))throw new TypeError(`Invalid specifier type ${typeof n}.`);if("string"==typeof t)e.set(URL.canParse(t)?new URLPattern(t):new URLPattern({pathname:t}),s(n));else{if(!(t instanceof URLPattern))throw new TypeError(`Invalid pattner "${t}".`);e.set(t,s(n))}}function l(e,{relList:t=[],crossOrigin:n="anonymous",referrerPolicy:r="no-referrer",fetchPriority:o="auto",signal:i,as:a,integrity:s,media:c,type:l}={}){const{promise:f,resolve:d,reject:p}=Promise.withResolvers(),u=document.createElement("link");if(i instanceof AbortSignal&&i.aborted)p(i.reason);else{if("string"==typeof e||e instanceof URL){if(u.relList.add(...t),"string"==typeof o&&(u.fetchPriority=o),"string"==typeof n&&(u.crossOrigin=n),"string"==typeof l&&(u.type=l),"string"==typeof c?u.media=c:c instanceof MediaQueryList&&(u.media=c.media),"string"==typeof a&&(u.as=a),"string"==typeof s&&(u.integrity=s),u.relList.contains("preload")||u.relList.contains("modulepreload")){const t=new AbortController,n=i instanceof AbortSignal?AbortSignal.any([t.signal,i]):t.signal;return i instanceof AbortSignal&&i.addEventListener("abort",(({target:e})=>{p(e.reason)}),{signal:t.signal,once:!0}),u.referrerPolicy=r,u.addEventListener("load",(()=>{d(),t.abort()}),{signal:n}),u.addEventListener("error",(()=>{p(new DOMException(`Error loading ${e}`,"NotFoundError")),t.abort()}),{signal:n}),u.href=e,document.head.append(u),f.catch(reportError).finally((()=>u.isConnected&&u.remove()))}return u.href=e,document.head.append(u),d(),f}p(new TypeError(`Invalid href to preload: "${e}.`))}}function f(e){e instanceof MutationRecord?f(e.target):"A"!==e.tagName||e.classList.contains("no-router")?e.querySelectorAll("a:not(.no-router)").forEach((e=>g(e,e.dataset))):g(e,e.dataset)}const d=new MutationObserver((e=>e.forEach(f)));async function p(e,{crossOrigin:t="anonymous",referrerPolicy:n="no-referrer",fetchPriority:r="low",as:o="script",signal:i,integrity:a}={}){await l(e,{relList:["modulepreload"],crossOrigin:t,referrerPolicy:n,fetchPriority:r,as:o,signal:i,integrity:a})}async function u(e,{crossOrigin:t="anonymous",referrerPolicy:n="no-referrer",fetchPriority:r="auto",signal:o,as:i,integrity:a,media:s,type:c}={}){await l(e,{relList:["preload"],crossOrigin:t,referrerPolicy:n,fetchPriority:r,as:i,signal:o,type:c,media:s,integrity:a})}async function g(e,{crossOrigin:t="anonymous",referrerPolicy:n="no-referrer",fetchPriority:r="high",signal:a}={}){const{resolve:s,reject:c,promise:l}=Promise.withResolvers();"string"==typeof e?await Promise.all(Array.from(document.querySelectorAll(e),(e=>g(e)))).then(s,c):e instanceof HTMLElement&&!e.classList.contains("no-router")&&"string"==typeof e.href&&e.origin===location.origin&&0===e.download.length&&URL.canParse(e.href)?e.addEventListener("mouseover",(async({currentTarget:e})=>{const c=o(e.href);c instanceof URLPattern?(await p(i(c),{fetchPriority:r,referrerPolicy:n,crossOrigin:t,integrity:e.dataset.integrity,signal:a}),s()):(await u(e.href,{fetchPriority:r,crossOrigin:t,referrerPolicy:n,as:e.dataset.preloadAs??"fetch",type:e.dataset.preloadType??"text/html",integrity:e.dataset.integrity,signal:a}),s())}),{once:!0,passive:!0,signal:a}):s(),await l}function y(e,t=document.documentElement){if("string"==typeof e)y(t.querySelector(e));else{if(!(e instanceof HTMLElement||e instanceof ShadowRoot))throw new TypeError("`observePreloadsOn` requires a selector or HTMLElement or ShadowRoot.");d.observe(e,{childList:!0,subtree:!0}),f(e)}}const m="trustedTypes"in globalThis?trustedTypes.createPolicy("aegis-atlas#html",{createHTML:e=>e}):Object.freeze({createHTML:e=>e}),h='meta[name="description"], meta[itemprop="description"], meta[property="og:description"], meta[name="twitter:description"]';let w=document.body;function E(e,t=document){if("string"==typeof e)E(t.getElementById(e));else{if(!(e instanceof HTMLElement))throw new TypeError("New root must be an `Element` or `id` of an element.");w=e}}async function L(e,t=new URL(location.href)){if(!(e instanceof NavigateEvent))throw new TypeError("Not a navigation event.");if(e.signal.aborted)throw e.signal.reason;{const r=!((n=e.sourceElement)instanceof HTMLElement)||n instanceof HTMLAnchorElement?"GET":n instanceof HTMLFormElement?n.method.toUpperCase():n instanceof HTMLButtonElement?n.hasAttribute("formmethod")&&0!==n.formMethod.length?n.formMethod.toUpperCase():n.form instanceof HTMLFormElement?n.form.method.toUpperCase():(console.warn("Not sure this should be possible..."),"GET"):"GET",o=new Request(e.destination.url,{method:r,body:"GET"===r?void 0:e.formData,signal:e.signal,mode:"same-origin",credentials:"include",priority:"high"}),{result:i,specifier:s,hasRegExpGroups:c}=a(e.destination.url);if("string"!=typeof s||null===i){const e=await fetch(o);await U(e,{})}else{const n=c?{...i.protocol.groups,...i.username.groups,...i.password.groups,...i.hostname.groups,...i.port.groups,...i.pathname.groups,...i.search.groups,...i.hash.groups}:{};delete n[0];const r=await import(s),a=new DisposableStack,l=a.adopt(new AbortController,(e=>e.abort(new DOMException("Stack was disposed.","AbortError")))),f=performance.now(),d=AbortSignal.any([l.signal,o.signal]);navigation.addEventListener("navigate",(()=>a.dispose()),{once:!0,signal:d}),navigation.addEventListener("navigateerror",(()=>a.dispose()),{once:!0,signal:d});const p=Object.freeze({timestamp:f,stack:a,controller:l,type:e.navigationType,state:e.destination.getState(),info:e.info,newURL:new URL(e.destination.url),oldURL:t,signal:d,result:i,params:n});try{return await async function(e,t,n){if(void 0===n.default)throw new TypeError(`No default export in module for <${e.url}>.`);if("function"==typeof n.default){const r=n.default.prototype instanceof HTMLElement?new n.default(e,t):await n.default(e,t);await U(r,n),P(n,t)}else await U(n.default),P(n,t)}(o,p,r)}catch(e){reportError(e),a.dispose()}}}var n}function v(t,{root:n,preload:r=!1,signal:o}={}){if("string"==typeof t)v(JSON.parse(document.scripts.namedItem(t).innerHTML),{root:n,preload:r,signal:o});else if("number"==typeof t)v(JSON.parse(document.scripts.item(t).innerHTML),{root:n,preload:r,signal:o});else if(t instanceof HTMLScriptElement)v(JSON.parse(t.textContent),{root:n,preload:r,signal:o});else{if("object"!=typeof t)throw new TypeError(`Routes must be an object, \`<script>\`, or name/index of \`document.scripts\`. Got a ${typeof t}.`);Object.entries(t).forEach((([e,t])=>c(e,t))),("string"==typeof n||n instanceof HTMLElement)&&E(n),navigation.addEventListener("navigate",(e=>{const t=new URL(location.href);e.canIntercept&&e.destination.url.startsWith(location.origin)&&!e.sourceElement?.classList?.contains?.("no-router")&&e.intercept({handler:()=>L(e,t)})}),{signal:o}),r&&y(document.body),((t=location.href)=>e.keys().some((e=>e.test(t))))(location.href)&&navigation.reload({info:"Initial Load",state:navigation.currentEntry.getState()})}}async function T({signal:e}={}){const{resolve:t,reject:n,promise:r}=Promise.withResolvers();if(e?.aborted)n(e.reason);else{const r=new AbortController,o={once:!0,signal:e instanceof AbortSignal?AbortSignal.any([e,r.signal]):r.signal};navigation.addEventListener("navigatesuccess",(()=>{t(navigation.currentEntry),r.abort()}),o),navigation.addEventListener("navigateerror",(e=>{n(e.error),r.abort()}),o),e instanceof AbortSignal&&e.addEventListener("abort",(({target:e})=>{n(e.reason),r.abort(e.reason)}),{once:!0,signal:r.signal})}return r}const b=(e,t)=>navigation.navigate(e,t),S=e=>navigation.back(e),R=e=>navigation.forward(e),M=e=>navigation.reload(e);async function P({title:e,description:t,styles:n},r){if("string"==typeof e?document.title=e:"function"==typeof e&&(document.title=await e(r)),"string"==typeof t?A(t):"function"==typeof t&&A(await t(r)),n instanceof CSSStyleSheet)document.adoptedStyleSheets=[...document.adoptedStyleSheets,n];else if(Array.isArray(n)&&0!==n.length)document.adoptedStyleSheets=[...document.adoptedStyleSheets,...n];else if("function"==typeof n){const e=await n(r);e instanceof CSSStyleSheet?document.adoptedStyleSheets=[...document.adoptedStyleSheets,e]:Array.isArray(e)&&0!==e.length&&(document.adoptedStyleSheets=[...document.adoptedStyleSheets,...e])}}async function U(e,{viewTransitionTypes:t=[]}={}){if(e instanceof URL)b(e);else if(e instanceof Response){if(!e.ok)throw new DOMException(`${e.url} [${e.status}]`,"NetworkError");if(!e.headers.get("Content-Type")?.startsWith?.("text/html"))throw new TypeError(`Unsupported Content-Type for <${e.url}> - "${e.headers.get("Content-Type")??"Unset"}".`);{const n=await e.text(),r=Document.parseHTMLUnsafe(m.createHTML(n));await U(r,{viewTransitionTypes:t})}}else if(e instanceof Element||e instanceof DocumentFragment)await document.startViewTransition({types:t,update(){w.replaceChildren(e)}});else{if(!(e instanceof HTMLDocument))throw new TypeError("Content must be an `Element`, `DocumentFragment`, `HTMLDocument`, or `Response`.");if(document.title=e.title,A(e.head.querySelector(h)?.content),w instanceof HTMLBodyElement)await document.startViewTransition({types:t,update(){w.replaceChildren(...e.body.childNodes)}});else{if(!(w instanceof HTMLElement&&"string"==typeof w.id))throw new TypeError("Root must be `<body>` or an element with an `id`.");await document.startViewTransition({types:t,update(){w.replaceChildren(...e.getElementById(w.id)?.childNodes??[])}})}}}function A(e=""){document.head.querySelectorAll(h).forEach((t=>t.content=e))}export{S as back,R as forward,o as getRegistryKey,i as getRegistrySpecifier,L as handleNavigation,v as init,a as lookupRoute,b as navigate,y as observePreloadsOn,u as preload,p as preloadModule,g as preloadOnHover,c as registerModule,M as reload,E as setRoot,T as whenLoaded};
2
2
  //# sourceMappingURL=atlas.min.js.map
package/atlas.min.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"atlas.min.js","sources":["routes.js","preload.js","router.js"],"sourcesContent":["/**\n * @type {Map<URLPattern, string>}\n */\nconst reg = new Map();\n\n/**\n * @typedef RouteMatch\n * @property {URLPatternResult|null} result The results of `pattern.exec(url)`\n * @property {string|null} specifier The module specifier mapped to the URL\n * @property {boolean} hasRegExpGroups\n * @readonly\n */\n\n/**\n * @type RouteMatch\n */\nconst invalidMatchResult = Object.freeze({ result: null, specifier: null, hasRegExpGroups: false });\n\n/**\n * Finds the URLPattern that corresponds to the given URL\n *\n * @param {string} url\n * @returns {URLPattern|undefined}\n */\nexport const getRegistryKey = url => reg.keys().find(pattern => pattern.test(url));\n\n/**\n *\n * @param {URLPattern} key\n * @returns {string|null} The module specifier\n */\nexport const getRegistrySpecifier = key => reg.get(key);\n\n/**\n *\n * @param {string} url\n * @returns {RouteMatch}\n */\nexport function lookupRoute(url) {\n\tconst key = getRegistryKey(url);\n\n\tif (key instanceof URLPattern) {\n\t\treturn Object.freeze({\n\t\t\tresult: key.exec(url),\n\t\t\tspecifier: reg.get(key),\n\t\t\thasRegExpGroups: key.hasRegExpGroups,\n\t\t});\n\t} else {\n\t\treturn invalidMatchResult;\n\t}\n}\n\n/**\n * Registers module `specifier` to handle routes matching `pattern`\n *\n * @param {string|URLPattern} pattern The pattern to handle\n * @param {string|URL} specifier The module to register to the pattern\n */\nexport function registerModule(pattern, specifier) {\n\tif (typeof specifier !== 'string' && ! (specifier instanceof URL)) {\n\t\tthrow new TypeError(`Invalid specifier type ${typeof specifier}.`);\n\t} else if (typeof pattern === 'string') {\n\t\treg.set(\n\t\t\tURL.canParse(pattern) ? new URLPattern(pattern) : new URLPattern({ pathname: pattern }),\n\t\t\tspecifier.toString()\n\t\t);\n\t} else if (! (pattern instanceof URLPattern)) {\n\t\tthrow new TypeError(`Invalid pattner \"${pattern}\".`);\n\t} else if (specifier instanceof URL) {\n\t\treg.set(pattern, specifier.href);\n\t} else {\n\t\treg.set(pattern, specifier);\n\t}\n}\n","import { getRegistryKey, getRegistrySpecifier } from './routes.js';\n\nfunction _loadLink(href, {\n\trelList = [],\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'auto',\n\tsignal: passedSignal,\n\tas,\n\tintegrity,\n\tmedia,\n\ttype,\n} = {}) {\n\tconst { promise, resolve, reject } = Promise.withResolvers();\n\tconst link = document.createElement('link');\n\n\tif (passedSignal instanceof AbortSignal && passedSignal.aborted) {\n\t\treject(passedSignal.reason);\n\t} else if (typeof href !== 'string' && ! (href instanceof URL)) {\n\t\treject(new TypeError(`Invalid href to preload: \"${href}.`));\n\t} else {\n\t\tlink.relList.add(...relList);\n\n\t\tif (typeof fetchPriority === 'string') {\n\t\t\tlink.fetchPriority = fetchPriority;\n\t\t}\n\n\t\tif (typeof crossOrigin === 'string') {\n\t\t\tlink.crossOrigin = crossOrigin;\n\t\t}\n\n\t\tif (typeof type === 'string') {\n\t\t\tlink.type = type;\n\t\t}\n\n\t\tif (typeof media === 'string') {\n\t\t\tlink.media = media;\n\t\t} else if (media instanceof MediaQueryList) {\n\t\t\tlink.media = media.media;\n\t\t}\n\n\t\tif (typeof as === 'string') {\n\t\t\tlink.as = as;\n\t\t}\n\n\t\tif (typeof integrity === 'string') {\n\t\t\tlink.integrity = integrity;\n\t\t}\n\n\t\tif (link.relList.contains('preload') || link.relList.contains('modulepreload')) {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst signal = passedSignal instanceof AbortSignal ? AbortSignal.any([controller.signal, passedSignal]) : controller.signal;\n\n\t\t\tif (passedSignal instanceof AbortSignal) {\n\t\t\t\tpassedSignal.addEventListener('abort', ({ target }) => {\n\t\t\t\t\treject(target.reason);\n\t\t\t\t}, { signal: controller.signal, once: true });\n\t\t\t}\n\n\t\t\tlink.referrerPolicy = referrerPolicy;\n\n\t\t\tlink.addEventListener('load', () => {\n\t\t\t\tresolve();\n\t\t\t\tcontroller.abort();\n\t\t\t}, { signal });\n\n\t\t\tlink.addEventListener('error', () => {\n\t\t\t\treject(new DOMException(`Error loading ${href}`, 'NotFoundError'));\n\t\t\t\tcontroller.abort();\n\t\t\t}, { signal });\n\n\t\t\tlink.href = href;\n\n\t\t\tdocument.head.append(link);\n\n\t\t\treturn promise.catch(reportError).finally(() => link.isConnected && link.remove());\n\t\t} else {\n\t\t\tlink.href = href;\n\t\t\tdocument.head.append(link);\n\t\t\tresolve();\n\t\t\treturn promise;\n\t\t}\n\t}\n}\n\nfunction _handlePreloadMutations(target) {\n\tif (target instanceof MutationRecord) {\n\t\t_handlePreloadMutations(target.target);\n\t} else if (target.tagName === 'A' && ! target.classList.contains('no-router')) {\n\t\tpreloadOnHover(target, target.dataset);\n\t} else {\n\t\ttarget.querySelectorAll('a:not(.no-router)').forEach(a => preloadOnHover(a, a.dataset));\n\t}\n}\n\nconst preloadObserver = new MutationObserver(entries => entries.forEach(_handlePreloadMutations));\n\n/**\n * Preloads a module asynchronously.\n *\n * @param {string} src - The URL or specifier to the module to preload.\n * @param {object} [options] - Optional options for the preload element.\n * @param {string} [options.crossOrigin=\"anonymous\"] - The CORS mode to use when fetching the module. Defaults to 'anonymous'.\n * @param {string} [options.referrerPolicy=\"no-referrer\"] - The referrer policy to use when fetching the module. Defaults to 'no-referrer'.\n * @param {string} [options.fetchPriority=\"low\"] - The fetch priority for the preload request. Defaults to 'auto'.\n * @param {string} [options.as=\"script\"] - The type of resource to preload. Defaults to 'script'.\n * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.\n * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource\n * @returns {Promise<void>} A promise that resolves when the module is preloaded or rejects on error or signal is aborted.\n * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.\n */\nexport async function preloadModule(src, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'low',\n\tas = 'script',\n\tsignal,\n\tintegrity,\n} = {}) {\n\tawait _loadLink(src, {\n\t\trelList: ['modulepreload'],\n\t\tcrossOrigin, referrerPolicy, fetchPriority, as, signal, integrity,\n\t});\n}\n\n/**\n * Preloads a resource asynchronously.\n\n * @param {string|URL} href - The URL or specifier to the resource to preload.\n * @param {Object} [options] - Optional options for the preload element.\n * @param {string} [options.crossOrigin=\"anonymous\"] - The CORS mode to use when fetching the resource. Defaults to 'anonymous'.\n * @param {string} [options.referrerPolicy=\"no-referrer\"] - The referrer policy to use when fetching the resource. Defaults to 'no-referrer'.\n * @param {string} [options.fetchPriority=\"auto\"] - The fetch priority for the preload request. Defaults to 'auto'.\n * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.\n * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource\n * @param {string} [options.as] - The type of resource to preload.\n * @param {string} [options.type] - The MIME type of the resource to preload.\n * @param {(string|MediaQueryList)} [options.media] - A media query string or a MediaQueryList object.\n * @returns {Promise<void>} A promise that resolves when the resource is preloaded or rejects on error or signal is aborted.\n * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.\n */\nexport async function preload(href, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'auto',\n\tsignal,\n\tas,\n\tintegrity,\n\tmedia,\n\ttype,\n} = {}) {\n\n\tawait _loadLink(href, {\n\t\trelList: ['preload'],\n\t\tcrossOrigin, referrerPolicy, fetchPriority, as, signal, type, media, integrity,\n\t});\n}\n/**\n * Preloads resources associated with an element or selector when hovered over, with optional configuration.\n *\n * @param {string|HTMLElement} target - A CSS selector string or an HTMLElement that triggers preloading.\n * @param {object} [options={}] - Configuration options for preloading.\n * @param {string} [options.crossOrigin='anonymous'] - The cross-origin attribute for the request, useful for fetching from other origins.\n * @param {string} [options.referrerPolicy='no-referrer'] - The referrer policy to apply to the request.\n * @param {string} [options.fetchPriority='high'] - The priority level of the fetch operation.\n * @param {AbortSignal} [options.signal] - Optional signal to abort the preload operation if needed.\n * @returns {Promise<void>} A promise that resolves once preloading completes.\n * @throws {TypeError} Throws if the target is not a valid selector or an HTMLElement with a valid `href` attribute.\n */\nexport async function preloadOnHover(target, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'high',\n\tsignal,\n} = {}) {\n\tconst { resolve, reject, promise } = Promise.withResolvers();\n\n\tif (typeof target === 'string') {\n\t\tawait Promise.all(Array.from(\n\t\t\tdocument.querySelectorAll(target),\n\t\t\tlink => preloadOnHover(link)\n\t\t)).then(resolve, reject);\n\t} else if (\n\t\ttarget instanceof HTMLElement\n\t\t&& ! target.classList.contains('no-router')\n\t\t&& typeof target.href === 'string'\n\t\t&& target.origin === location.origin\n\t\t&& target.download.length === 0\n\t\t&& URL.canParse(target.href)\n\t) {\n\t\ttarget.addEventListener('mouseover', async ({ currentTarget }) => {\n\t\t\tconst pattern = getRegistryKey(currentTarget.href);\n\n\t\t\tif (pattern instanceof URLPattern) {\n\t\t\t\tconst specifier = getRegistrySpecifier(pattern);\n\t\t\t\tconst resolved = import.meta.resolve(specifier);\n\n\t\t\t\tawait preloadModule(resolved, {\n\t\t\t\t\tfetchPriority,\n\t\t\t\t\treferrerPolicy,\n\t\t\t\t\tcrossOrigin,\n\t\t\t\t\tintegrity: currentTarget.dataset.integrity,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\tawait preload(currentTarget.href, {\n\t\t\t\t\tfetchPriority,\n\t\t\t\t\tcrossOrigin,\n\t\t\t\t\treferrerPolicy,\n\t\t\t\t\tas: currentTarget.dataset.preloadAs ?? 'fetch',\n\t\t\t\t\ttype: currentTarget.dataset.preloadType ?? 'text/html',\n\t\t\t\t\tintegrity: currentTarget.dataset.integrity,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, { once: true, passive: true, signal });\n\t} else {\n\t\tresolve();\n\t}\n\n\tawait promise;\n}\n\n/**\n * Adds `mouseenter` listeners to preload links/handlers via a `MutationObserver`\n *\n * @param {HTMLElement|ShadowRoot|string} target Target for the mutation observer or its selector\n * @param {HTMLElement|ShadowRoot} [base=document] The element to query from if `target` is a selector\n */\nexport function observePreloadsOn(target, base = document.documentElement) {\n\tif (typeof target === 'string') {\n\t\tobservePreloadsOn(base.querySelector(target));\n\t} else if (target instanceof HTMLElement || target instanceof ShadowRoot) {\n\t\tpreloadObserver.observe(target, { childList : true, subtree: true });\n\t\t_handlePreloadMutations(target);\n\t} else {\n\t\tthrow new TypeError('`observePreloadsOn` requires a selector or HTMLElement or ShadowRoot.');\n\t}\n}\n","import { registerModule, lookupRoute } from './routes.js';\nimport { observePreloadsOn } from './preload.js';\n\n/**\n * This is necessary since an HTML response from a same-origin\n * request should result in the same document state as if\n * it were initial load. CSP/Trusted Types requires `TrustedHTML`\n * for `Document.parseHTMLUnsage` (or `innerHTML`), and `setHTML()`\n * would filter out any `<iframe>` or `onclick` or `<form action>`.\n */\nconst policy = 'trustedTypes' in globalThis\n\t? trustedTypes.createPolicy('aegis-atlas#html', {\n\t\tcreateHTML(input) {\n\t\t\treturn input;\n\t\t}\n\t}) : Object.freeze({\n\t\tcreateHTML(input) {\n\t\t\treturn input;\n\t\t}\n\t});\n\nconst DESC_SELECTOR = 'meta[name=\"description\"], meta[itemprop=\"description\"], meta[property=\"og:description\"], meta[name=\"twitter:description\"]';\n\n/**\n * @typedef RouteContextObject\n * @property {URLPatternResult} result\n * @property {Record<string, string>} params\n * @property {DisposableStack} stack\n * @property {AbortController} controller\n * @property {AbortSignal} signal\n * @property {NavigationType} type\n * @property {URL} url\n * @property {any} state\n * @property {any} info\n * @property {number} timestamp\n * @readonly\n */\n\n/** @typedef {Response|DocumentFragment|Element|HTMLDocument|URL} HandlerResult */\n/** @typedef {(request: Request, context: RouteContextObject) => Promise<HandlerResult>} RouteHandler */\n\n/** @typedef {Readonly<Record<string, unknown>> & {default?: RouteHandler|HandlerResult, title?: string, description?: string, styles?: CSSStyleSheet|CSSStyleSheet[]}} Module */\n\n/**\n * @type HTMLElement\n */\nlet root = document.body;\n\n/**\n *\n * @param {string|HTMLElement} newRoot\n * @param {DocumentOrShadowRoot} base\n */\nexport function setRoot(newRoot, base = document) {\n\tif (typeof newRoot === 'string') {\n\t\tsetRoot(base.getElementById(newRoot));\n\t} else if (newRoot instanceof HTMLElement) {\n\t\troot = newRoot;\n\t} else {\n\t\tthrow new TypeError('New root must be an `Element` or `id` of an element.');\n\t}\n}\n\n/**\n *\n * @param {HTMLFormElement|HTMLButtonElement|HTMLAnchorElement} source\n * @returns {\"GET\"|\"POST\"}\n */\nfunction getRequestMethod(source) {\n\tif (! (source instanceof HTMLElement) || source instanceof HTMLAnchorElement) {\n\t\treturn 'GET';\n\t} else if (source instanceof HTMLFormElement) {\n\t\treturn source.method.toUpperCase();\n\t} else if (! (source instanceof HTMLButtonElement)) {\n\t\treturn 'GET';\n\t} else if (source.hasAttribute('formmethod') && source.formMethod.length !== 0) {\n\t\treturn source.formMethod.toUpperCase();\n\t} else if (source.form instanceof HTMLFormElement) {\n\t\treturn source.form.method.toUpperCase();\n\t} else {\n\t\tconsole.warn('Not sure this should be possible...');\n\t\treturn 'GET';\n\t}\n}\n\n/**\n *\n * @param {NavigationEvent} event\n */\nexport async function handleNavigation(event) {\n\tif (! (event instanceof NavigateEvent)) {\n\t\tthrow new TypeError('Not a navigation event.');\n\t} else if (event.signal.aborted) {\n\t\tthrow event.signal.reason;\n\t} else {\n\t\tconst method = getRequestMethod(event.sourceElement);\n\t\tconst request = new Request(event.destination.url, {\n\t\t\t// `sourceElement` could be a form, a `<button type=\"submit\">`, or an `<a>\n\t\t\tmethod: method,\n\t\t\tbody: method === 'GET' ? undefined : event.formData,// ?? new FormData(event.sourceElement?.form ?? event.sourceElement),\n\t\t\tsignal: event.signal,\n\t\t});\n\n\t\tconst { result, specifier, hasRegExpGroups } = lookupRoute(event.destination.url);\n\n\t\tif (typeof specifier !== 'string' || result === null) {\n\t\t\tconst resp = await fetch(request);\n\t\t\tawait updateContent(resp);\n\t\t} else {\n\t\t\tconst params = hasRegExpGroups ? {\n\t\t\t\t...result.protocol.groups, ...result.username.groups, ...result.password.groups, ...result.hostname.groups,\n\t\t\t\t...result.port.groups, ...result.pathname.groups, ...result.search.groups, ...result.hash.groups,\n\t\t\t}: {};\n\n\t\t\tdelete params['0'];\n\t\t\tconst module = await import(specifier);\n\t\t\tconst stack = new DisposableStack();\n\t\t\tconst controller = stack.adopt(\n\t\t\t\tnew AbortController(),\n\t\t\t\tcontroller => controller.abort(new DOMException('Stack was disposed.', 'AbortError')),\n\t\t\t);\n\n\t\t\tconst timestamp = performance.now();\n\t\t\tconst signal = AbortSignal.any([controller.signal, request.signal]);\n\n\t\t\t/**\n\t\t\t * @type {RouteContextObject}\n\t\t\t */\n\t\t\tconst context = Object.freeze({\n\t\t\t\ttimestamp,\n\t\t\t\tstack,\n\t\t\t\tcontroller,\n\t\t\t\ttype: event.navigationType,\n\t\t\t\tstate: event.destination.getState(),\n\t\t\t\tinfo: event.info,\n\t\t\t\turl: new URL(event.destination.url),\n\t\t\t\tsignal,\n\t\t\t\tresult,\n\t\t\t\tparams,\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\treturn await handleRequestModule(request, context, module);\n\t\t\t} catch(err) {\n\t\t\t\treportError(err);\n\t\t\t} finally {\n\t\t\t\tstack.dispose();\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n *\n * @param {unknown} routes\n * @param {object} config\n * @param {HTMLElement|string} [config.root]\n * @param {boolean} [config.preload=false]\n * @param {AbortSignal} [config.signal]\n */\nexport function init(routes, {\n\troot,\n\tpreload = false,\n\tsignal,\n} = {}) {\n\tif (typeof routes === 'string') {\n\t\tinit(JSON.parse(document.scripts.namedItem(routes).textContent), { root, preload, signal });\n\t} else if (typeof routes === 'number') {\n\t\tinit(JSON.parse(document.scripts.item(routes).textContent), { root, preload, signal });\n\t} else if (routes instanceof HTMLScriptElement) {\n\t\tinit(JSON.parse(routes.textContent), { root, preload, signal });\n\t} else if (typeof routes === 'object') {\n\t\tObject.entries(routes).forEach(([key, val]) => registerModule(key, val));\n\n\t\tif (typeof root === 'string' || root instanceof HTMLElement) {\n\t\t\tsetRoot(root);\n\t\t}\n\n\t\tnavigation.addEventListener('navigate', event => {\n\t\t\tif (event.canIntercept && event.destination.url.startsWith(location.origin) && ! event.sourceElement?.classList?.contains?.('no-router')) {\n\t\t\t\tevent.intercept({ handler: () => handleNavigation(event) });\n\t\t\t}\n\t\t}, { signal });\n\n\t\tif (preload) {\n\t\t\tobservePreloadsOn(document.body);\n\t\t}\n\t} else {\n\t\tthrow new TypeError(`Routes must be an object, \\`<script>\\`, or name/index of \\`document.scripts\\`. Got a ${typeof routes}.`);\n\t}\n}\n\n/**\n *\n * @param {object} options\n * @param {AbortSignal} [options.signal]\n * @returns {Promise<NavigationHistoryEntry>}\n */\nexport async function whenLoaded({ signal } = {}) {\n\tconst { resolve, reject, promise } = Promise.withResolvers();\n\n\tif (signal?.aborted) {\n\t\treject(signal.reason);\n\t} else {\n\t\tconst controller = new AbortController();\n\t\tconst opts = {\n\t\t\tonce: true,\n\t\t\tsignal: signal instanceof AbortSignal ? AbortSignal.any([signal, controller.signal]) : controller.signal,\n\t\t};\n\n\t\tnavigation.addEventListener('navigatesuccess', () => {\n\t\t\tresolve(navigation.currentEntry);\n\t\t\tcontroller.abort();\n\t\t}, opts);\n\n\t\tnavigation.addEventListener('navigateerror', event => {\n\t\t\treject(event.error);\n\t\t\tcontroller.abort();\n\t\t}, opts);\n\n\t\tif (signal instanceof AbortSignal) {\n\t\t\tsignal.addEventListener('abort', ({ target }) => {\n\t\t\t\treject(target.reason);\n\t\t\t\tcontroller.abort(target.reason);\n\t\t\t}, { once: true, signal: controller.signal });\n\t\t}\n\t}\n\n\treturn promise;\n}\n\n/**\n *\n * @param {string|URL} newURL\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const navigate = (newURL, options) => navigation.navigate(newURL, options);\n\n/**\n *\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const back = (options) => navigation.back(options);\n\n/**\n *\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const forward = (options) => navigation.forward(options);\n\n/**\n *\n * @param {NavigationReloadOptions} options\n * @returns {NavigationResult}\n */\nexport const reload = (options) => navigation.reload(options);\n\n/**\n *\n * @param {Request} request\n * @param {RouteContextObject} context\n * @param {Module} module\n */\nasync function handleRequestModule(request, context, module) {\n\tif (typeof module.default === 'undefined') {\n\t\tthrow new TypeError(`No default export in module for <${request.url}>.`);\n\t} else if (typeof module.default === 'function') {\n\t\tconst result = await module.default(request, context);\n\t\tawait updateContent(result);\n\t\tupdateMeta(module);\n\t} else {\n\t\tawait updateContent(module.default);\n\t\tupdateMeta(module);\n\t}\n}\n\nfunction updateMeta({ title, description, styles }) {\n\tif (typeof title === 'string') {\n\t\tdocument.title = title;\n\t}\n\n\tif (typeof description === 'string') {\n\t\tsetDescription(description);\n\t}\n\n\tif (styles instanceof CSSStyleSheet) {\n\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];\n\t} else if (Array.isArray(styles) && styles.length !== 0) {\n\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, ...styles];\n\t}\n}\n\n/**\n *\n * @param {HandlerResult} content\n */\nasync function updateContent(content) {\n\tif (content instanceof URL) {\n\t\tnavigate(content);\n\t} else if (content instanceof Response) {\n\t\tif (! content.ok) {\n\t\t\tthrow new DOMException(`${content.url} [${content.status}]`, 'NetworkError');\n\t\t} else if (! content.headers.get('Content-Type')?.startsWith?.('text/html')) {\n\t\t\tthrow new TypeError(`Unsupported Content-Type for <${content.url}> - \"${content.headers.get('Content-Type') ?? 'Unset'}\".`);\n\t\t} else {\n\t\t\tconst html = await content.text();\n\t\t\t/** @type HTMLDocument */\n\t\t\tconst doc = Document.parseHTMLUnsafe(policy.createHTML(html)); // Unsafe, but necessary... Same-origin at least\n\t\t\tawait updateContent(doc);\n\t\t}\n\t} else if (content instanceof Element || content instanceof DocumentFragment) {\n\t\troot.replaceChildren(content);\n\t} else if (content instanceof HTMLDocument) {\n\t\tdocument.title = content.title;\n\t\tsetDescription(content.head.querySelector(DESC_SELECTOR)?.content);\n\n\t\tif (root instanceof HTMLBodyElement) {\n\t\t\troot.replaceChildren(...content.body.childNodes);\n\t\t} else if (root instanceof HTMLElement && typeof root.id === 'string') {\n\t\t\troot.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);\n\t\t} else {\n\t\t\tthrow new TypeError('Root must be `<body>` or an element with an `id`.');\n\t\t}\n\t} else {\n\t\tthrow new TypeError('Content must be an `Element`, `DocumentFragment`, `HTMLDocument`, or `Response`.');\n\t}\n}\n\n/**\n *\n * @param {string} description\n */\nfunction setDescription(description = '') {\n\tdocument.head.querySelectorAll(DESC_SELECTOR).forEach(el => el.content = description);\n}\n"],"names":["reg","Map","invalidMatchResult","Object","freeze","result","specifier","hasRegExpGroups","getRegistryKey","url","keys","find","pattern","test","getRegistrySpecifier","key","get","lookupRoute","URLPattern","exec","registerModule","URL","TypeError","set","canParse","pathname","toString","href","_loadLink","relList","crossOrigin","referrerPolicy","fetchPriority","signal","passedSignal","as","integrity","media","type","promise","resolve","reject","Promise","withResolvers","link","document","createElement","AbortSignal","aborted","reason","add","MediaQueryList","contains","controller","AbortController","any","addEventListener","target","once","abort","DOMException","head","append","catch","reportError","finally","isConnected","remove","_handlePreloadMutations","MutationRecord","tagName","classList","querySelectorAll","forEach","a","preloadOnHover","dataset","preloadObserver","MutationObserver","entries","async","preloadModule","src","preload","all","Array","from","then","HTMLElement","origin","location","download","length","currentTarget","resolved","preloadAs","preloadType","passive","observePreloadsOn","base","documentElement","querySelector","ShadowRoot","observe","childList","subtree","policy","globalThis","trustedTypes","createPolicy","createHTML","input","DESC_SELECTOR","root","body","setRoot","newRoot","getElementById","handleNavigation","event","NavigateEvent","method","source","sourceElement","HTMLAnchorElement","HTMLFormElement","toUpperCase","HTMLButtonElement","hasAttribute","formMethod","form","console","warn","request","Request","destination","undefined","formData","resp","fetch","updateContent","params","protocol","groups","username","password","hostname","port","search","hash","module","import","stack","DisposableStack","adopt","timestamp","performance","now","context","navigationType","state","getState","info","default","updateMeta","handleRequestModule","err","dispose","init","routes","JSON","parse","scripts","namedItem","textContent","item","HTMLScriptElement","val","navigation","canIntercept","startsWith","intercept","handler","whenLoaded","opts","currentEntry","error","navigate","newURL","options","back","forward","reload","title","description","styles","setDescription","CSSStyleSheet","adoptedStyleSheets","isArray","content","Response","ok","status","headers","html","text","doc","Document","parseHTMLUnsafe","Element","DocumentFragment","replaceChildren","HTMLDocument","HTMLBodyElement","childNodes","id","el"],"mappings":"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"}
1
+ {"version":3,"file":"atlas.min.js","sources":["routes.js","preload.js","router.js"],"sourcesContent":["/**\n * @type {Map<URLPattern, string>}\n */\nconst reg = new Map();\n\nconst cache = new Map();\n\nconst PATH_EXP = /^(?:\\.*\\/)+/;\n\nexport const isBareSpecifier = specifier => ! PATH_EXP.test(specifier);\n\n/**\n * @typedef RouteMatch\n * @property {URLPatternResult|null} result The results of `pattern.exec(url)`\n * @property {string|null} specifier The module specifier mapped to the URL\n * @property {boolean} hasRegExpGroups\n * @readonly\n */\n\n/**\n * @type RouteMatch\n */\nconst invalidMatchResult = Object.freeze({ result: null, specifier: null, hasRegExpGroups: false });\n\n/**\n * Finds the URLPattern that corresponds to the given URL\n *\n * @param {string} url\n * @returns {URLPattern|undefined}\n */\nexport const getRegistryKey = url => reg.keys().find(pattern => pattern.test(url));\n\n/**\n * Checks if a route for the given URL is registered\n * @param {string} [url=location.href]\n * @return {boolean}\n */\nexport const hasRegistryKey = (url = location.href) => reg.keys().some(pattern => pattern.test(url));\n\n/**\n *\n * @param {URLPattern} key\n * @returns {string|null} The module specifier\n */\nexport const getRegistrySpecifier = key => reg.get(key);\n\n/**\n *\n * @param {string} url\n * @returns {RouteMatch}\n */\nexport function lookupRoute(url) {\n\tif (cache.has(url)) {\n\t\treturn cache.get(url);\n\t} else {\n\t\tconst key = getRegistryKey(url);\n\n\t\tif (key instanceof URLPattern) {\n\t\t\tconst match = Object.freeze({\n\t\t\t\tresult: key.exec(url),\n\t\t\t\tspecifier: reg.get(key),\n\t\t\t\thasRegExpGroups: key.hasRegExpGroups,\n\t\t\t});\n\n\t\t\tcache.set(url, match);\n\t\t\treturn match;\n\t\t} else {\n\t\t\tcache.set(url, invalidMatchResult);\n\t\t\treturn invalidMatchResult;\n\t\t}\n\n\t}\n}\n\nfunction resolveSpecifier(specifier) {\n\tif (isBareSpecifier(specifier)) {\n\t\treturn import.meta.resolve(specifier);\n\t} else if (URL.canParse(specifier)) {\n\t\treturn specifier;\n\t} else if (specifier instanceof URL) {\n\t\treturn specifier.href;\n\t} else {\n\t\treturn new URL (specifier, document.baseURI).href;\n\t}\n}\n\n/**\n * Registers module `specifier` to handle routes matching `pattern`\n *\n * @param {string|URLPattern} pattern The pattern to handle\n * @param {string|URL} specifier The module to register to the pattern\n */\nexport function registerModule(pattern, specifier) {\n\tif (typeof specifier !== 'string' && ! (specifier instanceof URL)) {\n\t\tthrow new TypeError(`Invalid specifier type ${typeof specifier}.`);\n\t} else if (typeof pattern === 'string') {\n\t\treg.set(\n\t\t\tURL.canParse(pattern) ? new URLPattern(pattern) : new URLPattern({ pathname: pattern }),\n\t\t\tresolveSpecifier(specifier)\n\t\t);\n\t} else if (! (pattern instanceof URLPattern)) {\n\t\tthrow new TypeError(`Invalid pattner \"${pattern}\".`);\n\t} else {\n\t\treg.set(pattern, resolveSpecifier(specifier));\n\t}\n}\n","import { getRegistryKey, getRegistrySpecifier } from './routes.js';\n\nfunction _loadLink(href, {\n\trelList = [],\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'auto',\n\tsignal: passedSignal,\n\tas,\n\tintegrity,\n\tmedia,\n\ttype,\n} = {}) {\n\tconst { promise, resolve, reject } = Promise.withResolvers();\n\tconst link = document.createElement('link');\n\n\tif (passedSignal instanceof AbortSignal && passedSignal.aborted) {\n\t\treject(passedSignal.reason);\n\t} else if (typeof href !== 'string' && ! (href instanceof URL)) {\n\t\treject(new TypeError(`Invalid href to preload: \"${href}.`));\n\t} else {\n\t\tlink.relList.add(...relList);\n\n\t\tif (typeof fetchPriority === 'string') {\n\t\t\tlink.fetchPriority = fetchPriority;\n\t\t}\n\n\t\tif (typeof crossOrigin === 'string') {\n\t\t\tlink.crossOrigin = crossOrigin;\n\t\t}\n\n\t\tif (typeof type === 'string') {\n\t\t\tlink.type = type;\n\t\t}\n\n\t\tif (typeof media === 'string') {\n\t\t\tlink.media = media;\n\t\t} else if (media instanceof MediaQueryList) {\n\t\t\tlink.media = media.media;\n\t\t}\n\n\t\tif (typeof as === 'string') {\n\t\t\tlink.as = as;\n\t\t}\n\n\t\tif (typeof integrity === 'string') {\n\t\t\tlink.integrity = integrity;\n\t\t}\n\n\t\tif (link.relList.contains('preload') || link.relList.contains('modulepreload')) {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst signal = passedSignal instanceof AbortSignal ? AbortSignal.any([controller.signal, passedSignal]) : controller.signal;\n\n\t\t\tif (passedSignal instanceof AbortSignal) {\n\t\t\t\tpassedSignal.addEventListener('abort', ({ target }) => {\n\t\t\t\t\treject(target.reason);\n\t\t\t\t}, { signal: controller.signal, once: true });\n\t\t\t}\n\n\t\t\tlink.referrerPolicy = referrerPolicy;\n\n\t\t\tlink.addEventListener('load', () => {\n\t\t\t\tresolve();\n\t\t\t\tcontroller.abort();\n\t\t\t}, { signal });\n\n\t\t\tlink.addEventListener('error', () => {\n\t\t\t\treject(new DOMException(`Error loading ${href}`, 'NotFoundError'));\n\t\t\t\tcontroller.abort();\n\t\t\t}, { signal });\n\n\t\t\tlink.href = href;\n\n\t\t\tdocument.head.append(link);\n\n\t\t\treturn promise.catch(reportError).finally(() => link.isConnected && link.remove());\n\t\t} else {\n\t\t\tlink.href = href;\n\t\t\tdocument.head.append(link);\n\t\t\tresolve();\n\t\t\treturn promise;\n\t\t}\n\t}\n}\n\nfunction _handlePreloadMutations(target) {\n\tif (target instanceof MutationRecord) {\n\t\t_handlePreloadMutations(target.target);\n\t} else if (target.tagName === 'A' && ! target.classList.contains('no-router')) {\n\t\tpreloadOnHover(target, target.dataset);\n\t} else {\n\t\ttarget.querySelectorAll('a:not(.no-router)').forEach(a => preloadOnHover(a, a.dataset));\n\t}\n}\n\nconst preloadObserver = new MutationObserver(entries => entries.forEach(_handlePreloadMutations));\n\n/**\n * Preloads a module asynchronously.\n *\n * @param {string} src - The URL or specifier to the module to preload.\n * @param {object} [options] - Optional options for the preload element.\n * @param {string} [options.crossOrigin=\"anonymous\"] - The CORS mode to use when fetching the module. Defaults to 'anonymous'.\n * @param {string} [options.referrerPolicy=\"no-referrer\"] - The referrer policy to use when fetching the module. Defaults to 'no-referrer'.\n * @param {string} [options.fetchPriority=\"low\"] - The fetch priority for the preload request. Defaults to 'auto'.\n * @param {string} [options.as=\"script\"] - The type of resource to preload. Defaults to 'script'.\n * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.\n * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource\n * @returns {Promise<void>} A promise that resolves when the module is preloaded or rejects on error or signal is aborted.\n * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.\n */\nexport async function preloadModule(src, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'low',\n\tas = 'script',\n\tsignal,\n\tintegrity,\n} = {}) {\n\tawait _loadLink(src, {\n\t\trelList: ['modulepreload'],\n\t\tcrossOrigin, referrerPolicy, fetchPriority, as, signal, integrity,\n\t});\n}\n\n/**\n * Preloads a resource asynchronously.\n\n * @param {string|URL} href - The URL or specifier to the resource to preload.\n * @param {Object} [options] - Optional options for the preload element.\n * @param {string} [options.crossOrigin=\"anonymous\"] - The CORS mode to use when fetching the resource. Defaults to 'anonymous'.\n * @param {string} [options.referrerPolicy=\"no-referrer\"] - The referrer policy to use when fetching the resource. Defaults to 'no-referrer'.\n * @param {string} [options.fetchPriority=\"auto\"] - The fetch priority for the preload request. Defaults to 'auto'.\n * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.\n * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource\n * @param {string} [options.as] - The type of resource to preload.\n * @param {string} [options.type] - The MIME type of the resource to preload.\n * @param {(string|MediaQueryList)} [options.media] - A media query string or a MediaQueryList object.\n * @returns {Promise<void>} A promise that resolves when the resource is preloaded or rejects on error or signal is aborted.\n * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.\n */\nexport async function preload(href, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'auto',\n\tsignal,\n\tas,\n\tintegrity,\n\tmedia,\n\ttype,\n} = {}) {\n\n\tawait _loadLink(href, {\n\t\trelList: ['preload'],\n\t\tcrossOrigin, referrerPolicy, fetchPriority, as, signal, type, media, integrity,\n\t});\n}\n/**\n * Preloads resources associated with an element or selector when hovered over, with optional configuration.\n *\n * @param {string|HTMLElement} target - A CSS selector string or an HTMLElement that triggers preloading.\n * @param {object} [options={}] - Configuration options for preloading.\n * @param {string} [options.crossOrigin='anonymous'] - The cross-origin attribute for the request, useful for fetching from other origins.\n * @param {string} [options.referrerPolicy='no-referrer'] - The referrer policy to apply to the request.\n * @param {string} [options.fetchPriority='high'] - The priority level of the fetch operation.\n * @param {AbortSignal} [options.signal] - Optional signal to abort the preload operation if needed.\n * @returns {Promise<void>} A promise that resolves once preloading completes.\n * @throws {TypeError} Throws if the target is not a valid selector or an HTMLElement with a valid `href` attribute.\n */\nexport async function preloadOnHover(target, {\n\tcrossOrigin = 'anonymous',\n\treferrerPolicy = 'no-referrer',\n\tfetchPriority = 'high',\n\tsignal,\n} = {}) {\n\tconst { resolve, reject, promise } = Promise.withResolvers();\n\n\tif (typeof target === 'string') {\n\t\tawait Promise.all(Array.from(\n\t\t\tdocument.querySelectorAll(target),\n\t\t\tlink => preloadOnHover(link)\n\t\t)).then(resolve, reject);\n\t} else if (\n\t\ttarget instanceof HTMLElement\n\t\t&& ! target.classList.contains('no-router')\n\t\t&& typeof target.href === 'string'\n\t\t&& target.origin === location.origin\n\t\t&& target.download.length === 0\n\t\t&& URL.canParse(target.href)\n\t) {\n\t\ttarget.addEventListener('mouseover', async ({ currentTarget }) => {\n\t\t\tconst pattern = getRegistryKey(currentTarget.href);\n\n\t\t\tif (pattern instanceof URLPattern) {\n\t\t\t\tawait preloadModule(getRegistrySpecifier(pattern), {\n\t\t\t\t\tfetchPriority,\n\t\t\t\t\treferrerPolicy,\n\t\t\t\t\tcrossOrigin,\n\t\t\t\t\tintegrity: currentTarget.dataset.integrity,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\tawait preload(currentTarget.href, {\n\t\t\t\t\tfetchPriority,\n\t\t\t\t\tcrossOrigin,\n\t\t\t\t\treferrerPolicy,\n\t\t\t\t\tas: currentTarget.dataset.preloadAs ?? 'fetch',\n\t\t\t\t\ttype: currentTarget.dataset.preloadType ?? 'text/html',\n\t\t\t\t\tintegrity: currentTarget.dataset.integrity,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, { once: true, passive: true, signal });\n\t} else {\n\t\tresolve();\n\t}\n\n\tawait promise;\n}\n\n/**\n * Adds `mouseenter` listeners to preload links/handlers via a `MutationObserver`\n *\n * @param {HTMLElement|ShadowRoot|string} target Target for the mutation observer or its selector\n * @param {HTMLElement|ShadowRoot} [base=document] The element to query from if `target` is a selector\n */\nexport function observePreloadsOn(target, base = document.documentElement) {\n\tif (typeof target === 'string') {\n\t\tobservePreloadsOn(base.querySelector(target));\n\t} else if (target instanceof HTMLElement || target instanceof ShadowRoot) {\n\t\tpreloadObserver.observe(target, { childList : true, subtree: true });\n\t\t_handlePreloadMutations(target);\n\t} else {\n\t\tthrow new TypeError('`observePreloadsOn` requires a selector or HTMLElement or ShadowRoot.');\n\t}\n}\n","import { registerModule, lookupRoute, hasRegistryKey } from './routes.js';\nimport { observePreloadsOn } from './preload.js';\n\n/**\n * This is necessary since an HTML response from a same-origin\n * request should result in the same document state as if\n * it were initial load. CSP/Trusted Types requires `TrustedHTML`\n * for `Document.parseHTMLUnsage` (or `innerHTML`), and `setHTML()`\n * would filter out any `<iframe>` or `onclick` or `<form action>`.\n */\nconst policy = 'trustedTypes' in globalThis\n\t? trustedTypes.createPolicy('aegis-atlas#html', {\n\t\tcreateHTML(input) {\n\t\t\treturn input;\n\t\t}\n\t}) : Object.freeze({\n\t\tcreateHTML(input) {\n\t\t\treturn input;\n\t\t}\n\t});\n\nconst DESC_SELECTOR = 'meta[name=\"description\"], meta[itemprop=\"description\"], meta[property=\"og:description\"], meta[name=\"twitter:description\"]';\n\n/**\n * @typedef RouteContextObject\n * @property {URLPatternResult} result\n * @property {Record<string, string>} params\n * @property {DisposableStack} stack\n * @property {AbortController} controller\n * @property {AbortSignal} signal\n * @property {NavigationType} type\n * @property {URL} newURL\n * @property {URL} oldURL\n * @property {any} state\n * @property {any} info\n * @property {number} timestamp\n * @readonly\n */\n\n/** @typedef {Response|DocumentFragment|Element|HTMLDocument|URL} HandlerResult */\n/** @typedef {(request: Request, context: RouteContextObject) => Promise<HandlerResult>} RouteHandler */\n\n/** @typedef {Readonly<Record<string, unknown>> & {default?: RouteHandler|HandlerResult, title?: string, description?: string, styles?: CSSStyleSheet|CSSStyleSheet[]}} Module */\n\n/**\n * @type HTMLElement\n */\nlet root = document.body;\n\n/**\n *\n * @param {string|HTMLElement} newRoot\n * @param {DocumentOrShadowRoot} base\n */\nexport function setRoot(newRoot, base = document) {\n\tif (typeof newRoot === 'string') {\n\t\tsetRoot(base.getElementById(newRoot));\n\t} else if (newRoot instanceof HTMLElement) {\n\t\troot = newRoot;\n\t} else {\n\t\tthrow new TypeError('New root must be an `Element` or `id` of an element.');\n\t}\n}\n\n/**\n *\n * @param {HTMLFormElement|HTMLButtonElement|HTMLAnchorElement} source\n * @returns {\"GET\"|\"POST\"}\n */\nfunction getRequestMethod(source) {\n\tif (! (source instanceof HTMLElement) || source instanceof HTMLAnchorElement) {\n\t\treturn 'GET';\n\t} else if (source instanceof HTMLFormElement) {\n\t\treturn source.method.toUpperCase();\n\t} else if (! (source instanceof HTMLButtonElement)) {\n\t\treturn 'GET';\n\t} else if (source.hasAttribute('formmethod') && source.formMethod.length !== 0) {\n\t\treturn source.formMethod.toUpperCase();\n\t} else if (source.form instanceof HTMLFormElement) {\n\t\treturn source.form.method.toUpperCase();\n\t} else {\n\t\tconsole.warn('Not sure this should be possible...');\n\t\treturn 'GET';\n\t}\n}\n\n/**\n *\n * @param {NavigationEvent} event\n * @param {URL} oldURL\n */\nexport async function handleNavigation(event, oldURL = new URL(location.href)) {\n\tif (! (event instanceof NavigateEvent)) {\n\t\tthrow new TypeError('Not a navigation event.');\n\t} else if (event.signal.aborted) {\n\t\tthrow event.signal.reason;\n\t} else {\n\t\tconst method = getRequestMethod(event.sourceElement);\n\t\tconst request = new Request(event.destination.url, {\n\t\t\t// `sourceElement` could be a form, a `<button type=\"submit\">`, or an `<a>\n\t\t\tmethod: method,\n\t\t\tbody: method === 'GET' ? undefined : event.formData,\n\t\t\tsignal: event.signal,\n\t\t\tmode: 'same-origin',\n\t\t\tcredentials: 'include',\n\t\t\tpriority: 'high',\n\t\t});\n\n\t\tconst { result, specifier, hasRegExpGroups } = lookupRoute(event.destination.url);\n\n\t\tif (typeof specifier !== 'string' || result === null) {\n\t\t\tconst resp = await fetch(request);\n\t\t\tawait updateContent(resp, {});\n\t\t} else {\n\t\t\tconst params = hasRegExpGroups ? {\n\t\t\t\t...result.protocol.groups, ...result.username.groups, ...result.password.groups, ...result.hostname.groups,\n\t\t\t\t...result.port.groups, ...result.pathname.groups, ...result.search.groups, ...result.hash.groups,\n\t\t\t}: {};\n\n\t\t\tdelete params['0'];\n\t\t\tconst module = await import(specifier);\n\t\t\tconst stack = new DisposableStack();\n\t\t\tconst controller = stack.adopt(\n\t\t\t\tnew AbortController(),\n\t\t\t\tcontroller => controller.abort(new DOMException('Stack was disposed.', 'AbortError')),\n\t\t\t);\n\n\t\t\tconst timestamp = performance.now();\n\t\t\tconst signal = AbortSignal.any([controller.signal, request.signal]);\n\n\t\t\t/**\n\t\t\t * Dispose of stack on next navigation or on error, triggering clean-up\n\t\t\t */\n\t\t\tnavigation.addEventListener('navigate', () => stack.dispose(), { once: true, signal });\n\t\t\tnavigation.addEventListener('navigateerror', () => stack.dispose(), { once: true, signal });\n\n\t\t\t/**\n\t\t\t * @type {RouteContextObject}\n\t\t\t */\n\t\t\tconst context = Object.freeze({\n\t\t\t\ttimestamp,\n\t\t\t\tstack,\n\t\t\t\tcontroller,\n\t\t\t\ttype: event.navigationType,\n\t\t\t\tstate: event.destination.getState(),\n\t\t\t\tinfo: event.info,\n\t\t\t\tnewURL: new URL(event.destination.url),\n\t\t\t\toldURL,\n\t\t\t\tsignal,\n\t\t\t\tresult,\n\t\t\t\tparams,\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\treturn await handleRequestModule(request, context, module);\n\t\t\t} catch(err) {\n\t\t\t\treportError(err);\n\t\t\t\tstack.dispose(); // Ensure clean-up happens even in an error\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n *\n * @param {unknown} routes\n * @param {object} config\n * @param {HTMLElement|string} [config.root]\n * @param {boolean} [config.preload=false]\n * @param {AbortSignal} [config.signal]\n */\nexport function init(routes, {\n\troot,\n\tpreload = false,\n\tsignal,\n} = {}) {\n\tif (typeof routes === 'string') {\n\t\tinit(JSON.parse(document.scripts.namedItem(routes).innerHTML), { root, preload, signal });\n\t} else if (typeof routes === 'number') {\n\t\tinit(JSON.parse(document.scripts.item(routes).innerHTML), { root, preload, signal });\n\t} else if (routes instanceof HTMLScriptElement) {\n\t\tinit(JSON.parse(routes.textContent), { root, preload, signal });\n\t} else if (typeof routes === 'object') {\n\t\tObject.entries(routes).forEach(([key, val]) => registerModule(key, val));\n\n\t\tif (typeof root === 'string' || root instanceof HTMLElement) {\n\t\t\tsetRoot(root);\n\t\t}\n\n\t\tnavigation.addEventListener('navigate', event => {\n\t\t\tconst oldURL = new URL(location.href);\n\t\t\tif (event.canIntercept && event.destination.url.startsWith(location.origin) && ! event.sourceElement?.classList?.contains?.('no-router')) {\n\t\t\t\tevent.intercept({ handler: () => handleNavigation(event, oldURL) });\n\t\t\t}\n\t\t}, { signal });\n\n\t\tif (preload) {\n\t\t\tobservePreloadsOn(document.body);\n\t\t}\n\n\t\tif (hasRegistryKey(location.href)) {\n\t\t\tnavigation.reload({ info: 'Initial Load', state: navigation.currentEntry.getState() });\n\t\t}\n\t} else {\n\t\tthrow new TypeError(`Routes must be an object, \\`<script>\\`, or name/index of \\`document.scripts\\`. Got a ${typeof routes}.`);\n\t}\n}\n\n/**\n *\n * @param {object} options\n * @param {AbortSignal} [options.signal]\n * @returns {Promise<NavigationHistoryEntry>}\n */\nexport async function whenLoaded({ signal } = {}) {\n\tconst { resolve, reject, promise } = Promise.withResolvers();\n\n\tif (signal?.aborted) {\n\t\treject(signal.reason);\n\t} else {\n\t\tconst controller = new AbortController();\n\t\tconst opts = {\n\t\t\tonce: true,\n\t\t\tsignal: signal instanceof AbortSignal ? AbortSignal.any([signal, controller.signal]) : controller.signal,\n\t\t};\n\n\t\tnavigation.addEventListener('navigatesuccess', () => {\n\t\t\tresolve(navigation.currentEntry);\n\t\t\tcontroller.abort();\n\t\t}, opts);\n\n\t\tnavigation.addEventListener('navigateerror', event => {\n\t\t\treject(event.error);\n\t\t\tcontroller.abort();\n\t\t}, opts);\n\n\t\tif (signal instanceof AbortSignal) {\n\t\t\tsignal.addEventListener('abort', ({ target }) => {\n\t\t\t\treject(target.reason);\n\t\t\t\tcontroller.abort(target.reason);\n\t\t\t}, { once: true, signal: controller.signal });\n\t\t}\n\t}\n\n\treturn promise;\n}\n\n/**\n *\n * @param {string|URL} newURL\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const navigate = (newURL, options) => navigation.navigate(newURL, options);\n\n/**\n *\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const back = (options) => navigation.back(options);\n\n/**\n *\n * @param {NavigationOptions} options\n * @returns {NavigationResult}\n */\nexport const forward = (options) => navigation.forward(options);\n\n/**\n *\n * @param {NavigationReloadOptions} options\n * @returns {NavigationResult}\n */\nexport const reload = (options) => navigation.reload(options);\n\n/**\n *\n * @param {Request} request\n * @param {RouteContextObject} context\n * @param {Module} module\n */\nasync function handleRequestModule(request, context, module) {\n\tif (typeof module.default === 'undefined') {\n\t\tthrow new TypeError(`No default export in module for <${request.url}>.`);\n\t} else if (typeof module.default === 'function') {\n\t\tconst result = module.default.prototype instanceof HTMLElement\n\t\t\t? new module.default(request, context)\n\t\t\t: await module.default(request, context);\n\n\t\tawait updateContent(result, module);\n\t\tupdateMeta(module, context);\n\t} else {\n\t\tawait updateContent(module.default);\n\t\tupdateMeta(module, context);\n\t}\n}\n\nasync function updateMeta({ title, description, styles }, context) {\n\tif (typeof title === 'string') {\n\t\tdocument.title = title;\n\t} else if (typeof title === 'function') {\n\t\tdocument.title = await title(context);\n\t}\n\n\tif (typeof description === 'string') {\n\t\tsetDescription(description);\n\t} else if (typeof description === 'function') {\n\t\tsetDescription(await description(context));\n\t}\n\n\tif (styles instanceof CSSStyleSheet) {\n\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];\n\t} else if (Array.isArray(styles) && styles.length !== 0) {\n\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, ...styles];\n\t} else if (typeof styles === 'function') {\n\t\tconst newStyles = await styles(context);\n\t\tif (newStyles instanceof CSSStyleSheet) {\n\t\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, newStyles];\n\t\t} else if (Array.isArray(newStyles) && newStyles.length !== 0) {\n\t\t\tdocument.adoptedStyleSheets = [...document.adoptedStyleSheets, ...newStyles];\n\t\t}\n\t}\n}\n\n/**\n *\n * @param {HandlerResult} content\n */\nasync function updateContent(content, { viewTransitionTypes: types = [] } = {}) {\n\tif (content instanceof URL) {\n\t\tnavigate(content);\n\t} else if (content instanceof Response) {\n\t\tif (! content.ok) {\n\t\t\tthrow new DOMException(`${content.url} [${content.status}]`, 'NetworkError');\n\t\t} else if (! content.headers.get('Content-Type')?.startsWith?.('text/html')) {\n\t\t\tthrow new TypeError(`Unsupported Content-Type for <${content.url}> - \"${content.headers.get('Content-Type') ?? 'Unset'}\".`);\n\t\t} else {\n\t\t\tconst html = await content.text();\n\t\t\t/** @type HTMLDocument */\n\t\t\tconst doc = Document.parseHTMLUnsafe(policy.createHTML(html)); // Unsafe, but necessary... Same-origin at least\n\t\t\tawait updateContent(doc, { viewTransitionTypes: types });\n\t\t}\n\t} else if (content instanceof Element || content instanceof DocumentFragment) {\n\t\tawait document.startViewTransition({\n\t\t\ttypes,\n\t\t\tupdate() {\n\t\t\t\troot.replaceChildren(content);\n\t\t\t},\n\t\t});\n\t} else if (content instanceof HTMLDocument) {\n\t\tdocument.title = content.title;\n\t\tsetDescription(content.head.querySelector(DESC_SELECTOR)?.content);\n\n\t\tif (root instanceof HTMLBodyElement) {\n\t\t\tawait document.startViewTransition({\n\t\t\t\ttypes,\n\t\t\t\tupdate() {\n\t\t\t\t\troot.replaceChildren(...content.body.childNodes);\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (root instanceof HTMLElement && typeof root.id === 'string') {\n\t\t\tawait document.startViewTransition({\n\t\t\t\ttypes,\n\t\t\t\tupdate() {\n\t\t\t\t\troot.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new TypeError('Root must be `<body>` or an element with an `id`.');\n\t\t}\n\t} else {\n\t\tthrow new TypeError('Content must be an `Element`, `DocumentFragment`, `HTMLDocument`, or `Response`.');\n\t}\n}\n\n/**\n *\n * @param {string} description\n */\nfunction setDescription(description = '') {\n\tdocument.head.querySelectorAll(DESC_SELECTOR).forEach(el => el.content = description);\n}\n"],"names":["reg","Map","cache","PATH_EXP","invalidMatchResult","Object","freeze","result","specifier","hasRegExpGroups","getRegistryKey","url","keys","find","pattern","test","getRegistrySpecifier","key","get","lookupRoute","has","URLPattern","match","exec","set","resolveSpecifier","isBareSpecifier","resolve","URL","canParse","href","document","baseURI","registerModule","TypeError","pathname","_loadLink","relList","crossOrigin","referrerPolicy","fetchPriority","signal","passedSignal","as","integrity","media","type","promise","reject","Promise","withResolvers","link","createElement","AbortSignal","aborted","reason","add","MediaQueryList","contains","controller","AbortController","any","addEventListener","target","once","abort","DOMException","head","append","catch","reportError","finally","isConnected","remove","_handlePreloadMutations","MutationRecord","tagName","classList","querySelectorAll","forEach","a","preloadOnHover","dataset","preloadObserver","MutationObserver","entries","async","preloadModule","src","preload","all","Array","from","then","HTMLElement","origin","location","download","length","currentTarget","preloadAs","preloadType","passive","observePreloadsOn","base","documentElement","querySelector","ShadowRoot","observe","childList","subtree","policy","globalThis","trustedTypes","createPolicy","createHTML","input","DESC_SELECTOR","root","body","setRoot","newRoot","getElementById","handleNavigation","event","oldURL","NavigateEvent","method","source","sourceElement","HTMLAnchorElement","HTMLFormElement","toUpperCase","HTMLButtonElement","hasAttribute","formMethod","form","console","warn","request","Request","destination","undefined","formData","mode","credentials","priority","resp","fetch","updateContent","params","protocol","groups","username","password","hostname","port","search","hash","module","import","stack","DisposableStack","adopt","timestamp","performance","now","navigation","dispose","context","navigationType","state","getState","info","newURL","default","prototype","updateMeta","handleRequestModule","err","init","routes","JSON","parse","scripts","namedItem","innerHTML","item","HTMLScriptElement","textContent","val","canIntercept","startsWith","intercept","handler","some","hasRegistryKey","reload","currentEntry","whenLoaded","opts","error","navigate","options","back","forward","title","description","styles","setDescription","CSSStyleSheet","adoptedStyleSheets","isArray","newStyles","content","viewTransitionTypes","types","Response","ok","status","headers","html","text","doc","Document","parseHTMLUnsafe","Element","DocumentFragment","startViewTransition","update","replaceChildren","HTMLDocument","HTMLBodyElement","childNodes","id","el"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aegisjsproject/atlas",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A client-side router library using `Navigation` & `URLPattern`",
5
5
  "keywords": [
6
6
  "router",
package/preload.js CHANGED
@@ -192,10 +192,7 @@ export async function preloadOnHover(target, {
192
192
  const pattern = getRegistryKey(currentTarget.href);
193
193
 
194
194
  if (pattern instanceof URLPattern) {
195
- const specifier = getRegistrySpecifier(pattern);
196
- const resolved = import.meta.resolve(specifier);
197
-
198
- await preloadModule(resolved, {
195
+ await preloadModule(getRegistrySpecifier(pattern), {
199
196
  fetchPriority,
200
197
  referrerPolicy,
201
198
  crossOrigin,
package/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { registerModule, lookupRoute } from './routes.js';
1
+ import { registerModule, lookupRoute, hasRegistryKey } from './routes.js';
2
2
  import { observePreloadsOn } from './preload.js';
3
3
 
4
4
  /**
@@ -29,7 +29,8 @@ const DESC_SELECTOR = 'meta[name="description"], meta[itemprop="description"], m
29
29
  * @property {AbortController} controller
30
30
  * @property {AbortSignal} signal
31
31
  * @property {NavigationType} type
32
- * @property {URL} url
32
+ * @property {URL} newURL
33
+ * @property {URL} oldURL
33
34
  * @property {any} state
34
35
  * @property {any} info
35
36
  * @property {number} timestamp
@@ -86,8 +87,9 @@ function getRequestMethod(source) {
86
87
  /**
87
88
  *
88
89
  * @param {NavigationEvent} event
90
+ * @param {URL} oldURL
89
91
  */
90
- export async function handleNavigation(event) {
92
+ export async function handleNavigation(event, oldURL = new URL(location.href)) {
91
93
  if (! (event instanceof NavigateEvent)) {
92
94
  throw new TypeError('Not a navigation event.');
93
95
  } else if (event.signal.aborted) {
@@ -97,15 +99,18 @@ export async function handleNavigation(event) {
97
99
  const request = new Request(event.destination.url, {
98
100
  // `sourceElement` could be a form, a `<button type="submit">`, or an `<a>
99
101
  method: method,
100
- body: method === 'GET' ? undefined : event.formData,// ?? new FormData(event.sourceElement?.form ?? event.sourceElement),
102
+ body: method === 'GET' ? undefined : event.formData,
101
103
  signal: event.signal,
104
+ mode: 'same-origin',
105
+ credentials: 'include',
106
+ priority: 'high',
102
107
  });
103
108
 
104
109
  const { result, specifier, hasRegExpGroups } = lookupRoute(event.destination.url);
105
110
 
106
111
  if (typeof specifier !== 'string' || result === null) {
107
112
  const resp = await fetch(request);
108
- await updateContent(resp);
113
+ await updateContent(resp, {});
109
114
  } else {
110
115
  const params = hasRegExpGroups ? {
111
116
  ...result.protocol.groups, ...result.username.groups, ...result.password.groups, ...result.hostname.groups,
@@ -123,6 +128,12 @@ export async function handleNavigation(event) {
123
128
  const timestamp = performance.now();
124
129
  const signal = AbortSignal.any([controller.signal, request.signal]);
125
130
 
131
+ /**
132
+ * Dispose of stack on next navigation or on error, triggering clean-up
133
+ */
134
+ navigation.addEventListener('navigate', () => stack.dispose(), { once: true, signal });
135
+ navigation.addEventListener('navigateerror', () => stack.dispose(), { once: true, signal });
136
+
126
137
  /**
127
138
  * @type {RouteContextObject}
128
139
  */
@@ -133,7 +144,8 @@ export async function handleNavigation(event) {
133
144
  type: event.navigationType,
134
145
  state: event.destination.getState(),
135
146
  info: event.info,
136
- url: new URL(event.destination.url),
147
+ newURL: new URL(event.destination.url),
148
+ oldURL,
137
149
  signal,
138
150
  result,
139
151
  params,
@@ -143,8 +155,7 @@ export async function handleNavigation(event) {
143
155
  return await handleRequestModule(request, context, module);
144
156
  } catch(err) {
145
157
  reportError(err);
146
- } finally {
147
- stack.dispose();
158
+ stack.dispose(); // Ensure clean-up happens even in an error
148
159
  }
149
160
  }
150
161
  }
@@ -164,9 +175,9 @@ export function init(routes, {
164
175
  signal,
165
176
  } = {}) {
166
177
  if (typeof routes === 'string') {
167
- init(JSON.parse(document.scripts.namedItem(routes).textContent), { root, preload, signal });
178
+ init(JSON.parse(document.scripts.namedItem(routes).innerHTML), { root, preload, signal });
168
179
  } else if (typeof routes === 'number') {
169
- init(JSON.parse(document.scripts.item(routes).textContent), { root, preload, signal });
180
+ init(JSON.parse(document.scripts.item(routes).innerHTML), { root, preload, signal });
170
181
  } else if (routes instanceof HTMLScriptElement) {
171
182
  init(JSON.parse(routes.textContent), { root, preload, signal });
172
183
  } else if (typeof routes === 'object') {
@@ -177,14 +188,19 @@ export function init(routes, {
177
188
  }
178
189
 
179
190
  navigation.addEventListener('navigate', event => {
191
+ const oldURL = new URL(location.href);
180
192
  if (event.canIntercept && event.destination.url.startsWith(location.origin) && ! event.sourceElement?.classList?.contains?.('no-router')) {
181
- event.intercept({ handler: () => handleNavigation(event) });
193
+ event.intercept({ handler: () => handleNavigation(event, oldURL) });
182
194
  }
183
195
  }, { signal });
184
196
 
185
197
  if (preload) {
186
198
  observePreloadsOn(document.body);
187
199
  }
200
+
201
+ if (hasRegistryKey(location.href)) {
202
+ navigation.reload({ info: 'Initial Load', state: navigation.currentEntry.getState() });
203
+ }
188
204
  } else {
189
205
  throw new TypeError(`Routes must be an object, \`<script>\`, or name/index of \`document.scripts\`. Got a ${typeof routes}.`);
190
206
  }
@@ -268,28 +284,42 @@ async function handleRequestModule(request, context, module) {
268
284
  if (typeof module.default === 'undefined') {
269
285
  throw new TypeError(`No default export in module for <${request.url}>.`);
270
286
  } else if (typeof module.default === 'function') {
271
- const result = await module.default(request, context);
272
- await updateContent(result);
273
- updateMeta(module);
287
+ const result = module.default.prototype instanceof HTMLElement
288
+ ? new module.default(request, context)
289
+ : await module.default(request, context);
290
+
291
+ await updateContent(result, module);
292
+ updateMeta(module, context);
274
293
  } else {
275
294
  await updateContent(module.default);
276
- updateMeta(module);
295
+ updateMeta(module, context);
277
296
  }
278
297
  }
279
298
 
280
- function updateMeta({ title, description, styles }) {
299
+ async function updateMeta({ title, description, styles }, context) {
281
300
  if (typeof title === 'string') {
282
301
  document.title = title;
302
+ } else if (typeof title === 'function') {
303
+ document.title = await title(context);
283
304
  }
284
305
 
285
306
  if (typeof description === 'string') {
286
307
  setDescription(description);
308
+ } else if (typeof description === 'function') {
309
+ setDescription(await description(context));
287
310
  }
288
311
 
289
312
  if (styles instanceof CSSStyleSheet) {
290
313
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
291
314
  } else if (Array.isArray(styles) && styles.length !== 0) {
292
315
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...styles];
316
+ } else if (typeof styles === 'function') {
317
+ const newStyles = await styles(context);
318
+ if (newStyles instanceof CSSStyleSheet) {
319
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, newStyles];
320
+ } else if (Array.isArray(newStyles) && newStyles.length !== 0) {
321
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...newStyles];
322
+ }
293
323
  }
294
324
  }
295
325
 
@@ -297,7 +327,7 @@ function updateMeta({ title, description, styles }) {
297
327
  *
298
328
  * @param {HandlerResult} content
299
329
  */
300
- async function updateContent(content) {
330
+ async function updateContent(content, { viewTransitionTypes: types = [] } = {}) {
301
331
  if (content instanceof URL) {
302
332
  navigate(content);
303
333
  } else if (content instanceof Response) {
@@ -309,18 +339,33 @@ async function updateContent(content) {
309
339
  const html = await content.text();
310
340
  /** @type HTMLDocument */
311
341
  const doc = Document.parseHTMLUnsafe(policy.createHTML(html)); // Unsafe, but necessary... Same-origin at least
312
- await updateContent(doc);
342
+ await updateContent(doc, { viewTransitionTypes: types });
313
343
  }
314
344
  } else if (content instanceof Element || content instanceof DocumentFragment) {
315
- root.replaceChildren(content);
345
+ await document.startViewTransition({
346
+ types,
347
+ update() {
348
+ root.replaceChildren(content);
349
+ },
350
+ });
316
351
  } else if (content instanceof HTMLDocument) {
317
352
  document.title = content.title;
318
353
  setDescription(content.head.querySelector(DESC_SELECTOR)?.content);
319
354
 
320
355
  if (root instanceof HTMLBodyElement) {
321
- root.replaceChildren(...content.body.childNodes);
356
+ await document.startViewTransition({
357
+ types,
358
+ update() {
359
+ root.replaceChildren(...content.body.childNodes);
360
+ }
361
+ });
322
362
  } else if (root instanceof HTMLElement && typeof root.id === 'string') {
323
- root.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);
363
+ await document.startViewTransition({
364
+ types,
365
+ update() {
366
+ root.replaceChildren(...content.getElementById(root.id)?.childNodes ?? []);
367
+ }
368
+ });
324
369
  } else {
325
370
  throw new TypeError('Root must be `<body>` or an element with an `id`.');
326
371
  }
package/routes.js CHANGED
@@ -3,6 +3,12 @@
3
3
  */
4
4
  const reg = new Map();
5
5
 
6
+ const cache = new Map();
7
+
8
+ const PATH_EXP = /^(?:\.*\/)+/;
9
+
10
+ export const isBareSpecifier = specifier => ! PATH_EXP.test(specifier);
11
+
6
12
  /**
7
13
  * @typedef RouteMatch
8
14
  * @property {URLPatternResult|null} result The results of `pattern.exec(url)`
@@ -24,6 +30,13 @@ const invalidMatchResult = Object.freeze({ result: null, specifier: null, hasReg
24
30
  */
25
31
  export const getRegistryKey = url => reg.keys().find(pattern => pattern.test(url));
26
32
 
33
+ /**
34
+ * Checks if a route for the given URL is registered
35
+ * @param {string} [url=location.href]
36
+ * @return {boolean}
37
+ */
38
+ export const hasRegistryKey = (url = location.href) => reg.keys().some(pattern => pattern.test(url));
39
+
27
40
  /**
28
41
  *
29
42
  * @param {URLPattern} key
@@ -37,16 +50,37 @@ export const getRegistrySpecifier = key => reg.get(key);
37
50
  * @returns {RouteMatch}
38
51
  */
39
52
  export function lookupRoute(url) {
40
- const key = getRegistryKey(url);
53
+ if (cache.has(url)) {
54
+ return cache.get(url);
55
+ } else {
56
+ const key = getRegistryKey(url);
57
+
58
+ if (key instanceof URLPattern) {
59
+ const match = Object.freeze({
60
+ result: key.exec(url),
61
+ specifier: reg.get(key),
62
+ hasRegExpGroups: key.hasRegExpGroups,
63
+ });
41
64
 
42
- if (key instanceof URLPattern) {
43
- return Object.freeze({
44
- result: key.exec(url),
45
- specifier: reg.get(key),
46
- hasRegExpGroups: key.hasRegExpGroups,
47
- });
65
+ cache.set(url, match);
66
+ return match;
67
+ } else {
68
+ cache.set(url, invalidMatchResult);
69
+ return invalidMatchResult;
70
+ }
71
+
72
+ }
73
+ }
74
+
75
+ function resolveSpecifier(specifier) {
76
+ if (isBareSpecifier(specifier)) {
77
+ return import.meta.resolve(specifier);
78
+ } else if (URL.canParse(specifier)) {
79
+ return specifier;
80
+ } else if (specifier instanceof URL) {
81
+ return specifier.href;
48
82
  } else {
49
- return invalidMatchResult;
83
+ return new URL (specifier, document.baseURI).href;
50
84
  }
51
85
  }
52
86
 
@@ -62,13 +96,11 @@ export function registerModule(pattern, specifier) {
62
96
  } else if (typeof pattern === 'string') {
63
97
  reg.set(
64
98
  URL.canParse(pattern) ? new URLPattern(pattern) : new URLPattern({ pathname: pattern }),
65
- specifier.toString()
99
+ resolveSpecifier(specifier)
66
100
  );
67
101
  } else if (! (pattern instanceof URLPattern)) {
68
102
  throw new TypeError(`Invalid pattner "${pattern}".`);
69
- } else if (specifier instanceof URL) {
70
- reg.set(pattern, specifier.href);
71
- } else {
72
- reg.set(pattern, specifier);
103
+ } else {
104
+ reg.set(pattern, resolveSpecifier(specifier));
73
105
  }
74
106
  }