@aegisjsproject/atlas 0.0.0

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.
@@ -0,0 +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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"}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@aegisjsproject/atlas",
3
+ "version": "0.0.0",
4
+ "description": "A template repo for npm packages",
5
+ "keywords": [],
6
+ "type": "module",
7
+ "main": "./atlas.cjs",
8
+ "module": "./atlas.js",
9
+ "unpkg": "./atlas.min.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./atlas.js",
13
+ "require": "./atlas.cjs"
14
+ },
15
+ "./*.js": {
16
+ "import": "./*.js",
17
+ "require": "./*.cjs"
18
+ },
19
+ "./*.mjs": {
20
+ "import": "./*.js",
21
+ "require": "./*.cjs"
22
+ },
23
+ "./*.cjs": {
24
+ "import": "./*.js",
25
+ "require": "./*.cjs"
26
+ },
27
+ "./*": {
28
+ "import": "./*.js",
29
+ "require": "./*.cjs"
30
+ }
31
+ },
32
+ "engines": {
33
+ "node": ">=24.10.0"
34
+ },
35
+ "private": false,
36
+ "scripts": {
37
+ "test": "npm run lint:js && npm run run:tests",
38
+ "start": "http-server -c ./http.config.js",
39
+ "preversion": "npm test && npm run build --if-present",
40
+ "prepare": "npm test && npm run build --if-present",
41
+ "lint:js": "eslint .",
42
+ "fix:js": "eslint . --fix",
43
+ "build": "npm run build:js",
44
+ "run:tests": "node --test",
45
+ "clean": "rm -f ./*.cjs",
46
+ "build:js": "npm run clean && rollup -c rollup.config.js",
47
+ "create:lock": "npm i --package-lock-only --ignore-scripts --no-audit --no-fund",
48
+ "version:bump": "npm run version:bump:patch",
49
+ "version:bump:patch": "npm version --no-git-tag-version patch && npm run create:lock",
50
+ "version:bump:minor": "npm version --no-git-tag-version minor && npm run create:lock",
51
+ "version:bump:major": "npm version --no-git-tag-version major && npm run create:lock"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/AegisJSProject/atlas.git"
56
+ },
57
+ "author": "Chris Zuber <admin@kernvalley.us>",
58
+ "license": "MIT",
59
+ "funding": [
60
+ {
61
+ "type": "librepay",
62
+ "url": "https://liberapay.com/shgysk8zer0"
63
+ },
64
+ {
65
+ "type": "github",
66
+ "url": "https://github.com/sponsors/shgysk8zer0"
67
+ }
68
+ ],
69
+ "bugs": {
70
+ "url": "https://github.com/AegisJSProject/atlas/issues"
71
+ },
72
+ "homepage": "https://github.com/AegisJSProject/atlas#readme",
73
+ "devDependencies": {
74
+ "@aegisjsproject/dev-server": "^1.0.6",
75
+ "@aegisjsproject/http-utils": "^1.0.4",
76
+ "@rollup/plugin-terser": "^1.0.0",
77
+ "@shgysk8zer0/eslint-config": "^1.0.9",
78
+ "@shgysk8zer0/http-server": "^1.1.2",
79
+ "@shgysk8zer0/importmap": "^1.9.10",
80
+ "eslint": "^10.2.1",
81
+ "rollup": "^4.60.2"
82
+ }
83
+ }
package/preload.js ADDED
@@ -0,0 +1,241 @@
1
+ import { getRegistryKey, getRegistrySpecifier } from './routes.js';
2
+
3
+ function _loadLink(href, {
4
+ relList = [],
5
+ crossOrigin = 'anonymous',
6
+ referrerPolicy = 'no-referrer',
7
+ fetchPriority = 'auto',
8
+ signal: passedSignal,
9
+ as,
10
+ integrity,
11
+ media,
12
+ type,
13
+ } = {}) {
14
+ const { promise, resolve, reject } = Promise.withResolvers();
15
+ const link = document.createElement('link');
16
+
17
+ if (passedSignal instanceof AbortSignal && passedSignal.aborted) {
18
+ reject(passedSignal.reason);
19
+ } else if (typeof href !== 'string' && ! (href instanceof URL)) {
20
+ reject(new TypeError(`Invalid href to preload: "${href}.`));
21
+ } else {
22
+ link.relList.add(...relList);
23
+
24
+ if (typeof fetchPriority === 'string') {
25
+ link.fetchPriority = fetchPriority;
26
+ }
27
+
28
+ if (typeof crossOrigin === 'string') {
29
+ link.crossOrigin = crossOrigin;
30
+ }
31
+
32
+ if (typeof type === 'string') {
33
+ link.type = type;
34
+ }
35
+
36
+ if (typeof media === 'string') {
37
+ link.media = media;
38
+ } else if (media instanceof MediaQueryList) {
39
+ link.media = media.media;
40
+ }
41
+
42
+ if (typeof as === 'string') {
43
+ link.as = as;
44
+ }
45
+
46
+ if (typeof integrity === 'string') {
47
+ link.integrity = integrity;
48
+ }
49
+
50
+ if (link.relList.contains('preload') || link.relList.contains('modulepreload')) {
51
+ const controller = new AbortController();
52
+ const signal = passedSignal instanceof AbortSignal ? AbortSignal.any([controller.signal, passedSignal]) : controller.signal;
53
+
54
+ if (passedSignal instanceof AbortSignal) {
55
+ passedSignal.addEventListener('abort', ({ target }) => {
56
+ reject(target.reason);
57
+ }, { signal: controller.signal, once: true });
58
+ }
59
+
60
+ link.referrerPolicy = referrerPolicy;
61
+
62
+ link.addEventListener('load', () => {
63
+ resolve();
64
+ controller.abort();
65
+ }, { signal });
66
+
67
+ link.addEventListener('error', () => {
68
+ reject(new DOMException(`Error loading ${href}`, 'NotFoundError'));
69
+ controller.abort();
70
+ }, { signal });
71
+
72
+ link.href = href;
73
+
74
+ document.head.append(link);
75
+
76
+ return promise.catch(reportError).finally(() => link.isConnected && link.remove());
77
+ } else {
78
+ link.href = href;
79
+ document.head.append(link);
80
+ resolve();
81
+ return promise;
82
+ }
83
+ }
84
+ }
85
+
86
+ function _handlePreloadMutations(target) {
87
+ if (target instanceof MutationRecord) {
88
+ _handlePreloadMutations(target.target);
89
+ } else if (target.tagName === 'A' && ! target.classList.contains('no-router')) {
90
+ preloadOnHover(target, target.dataset);
91
+ } else {
92
+ target.querySelectorAll('a:not(.no-router)').forEach(a => preloadOnHover(a, a.dataset));
93
+ }
94
+ }
95
+
96
+ const preloadObserver = new MutationObserver(entries => entries.forEach(_handlePreloadMutations));
97
+
98
+ /**
99
+ * Preloads a module asynchronously.
100
+ *
101
+ * @param {string} src - The URL or specifier to the module to preload.
102
+ * @param {object} [options] - Optional options for the preload element.
103
+ * @param {string} [options.crossOrigin="anonymous"] - The CORS mode to use when fetching the module. Defaults to 'anonymous'.
104
+ * @param {string} [options.referrerPolicy="no-referrer"] - The referrer policy to use when fetching the module. Defaults to 'no-referrer'.
105
+ * @param {string} [options.fetchPriority="low"] - The fetch priority for the preload request. Defaults to 'auto'.
106
+ * @param {string} [options.as="script"] - The type of resource to preload. Defaults to 'script'.
107
+ * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.
108
+ * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource
109
+ * @returns {Promise<void>} A promise that resolves when the module is preloaded or rejects on error or signal is aborted.
110
+ * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.
111
+ */
112
+ export async function preloadModule(src, {
113
+ crossOrigin = 'anonymous',
114
+ referrerPolicy = 'no-referrer',
115
+ fetchPriority = 'low',
116
+ as = 'script',
117
+ signal,
118
+ integrity,
119
+ } = {}) {
120
+ await _loadLink(src, {
121
+ relList: ['modulepreload'],
122
+ crossOrigin, referrerPolicy, fetchPriority, as, signal, integrity,
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Preloads a resource asynchronously.
128
+
129
+ * @param {string|URL} href - The URL or specifier to the resource to preload.
130
+ * @param {Object} [options] - Optional options for the preload element.
131
+ * @param {string} [options.crossOrigin="anonymous"] - The CORS mode to use when fetching the resource. Defaults to 'anonymous'.
132
+ * @param {string} [options.referrerPolicy="no-referrer"] - The referrer policy to use when fetching the resource. Defaults to 'no-referrer'.
133
+ * @param {string} [options.fetchPriority="auto"] - The fetch priority for the preload request. Defaults to 'auto'.
134
+ * @param {AbortSignal} [options.signal] - An AbortSignal to abort the preload request. Defaults to a 5-second timeout.
135
+ * @param {string} [options.integrity] - A base64-encoded cryptographic hash of the resource
136
+ * @param {string} [options.as] - The type of resource to preload.
137
+ * @param {string} [options.type] - The MIME type of the resource to preload.
138
+ * @param {(string|MediaQueryList)} [options.media] - A media query string or a MediaQueryList object.
139
+ * @returns {Promise<void>} A promise that resolves when the resource is preloaded or rejects on error or signal is aborted.
140
+ * @throws {Error} Throws if the signal is aborted or if an `error` event is fired on the preload.
141
+ */
142
+ export async function preload(href, {
143
+ crossOrigin = 'anonymous',
144
+ referrerPolicy = 'no-referrer',
145
+ fetchPriority = 'auto',
146
+ signal,
147
+ as,
148
+ integrity,
149
+ media,
150
+ type,
151
+ } = {}) {
152
+
153
+ await _loadLink(href, {
154
+ relList: ['preload'],
155
+ crossOrigin, referrerPolicy, fetchPriority, as, signal, type, media, integrity,
156
+ });
157
+ }
158
+ /**
159
+ * Preloads resources associated with an element or selector when hovered over, with optional configuration.
160
+ *
161
+ * @param {string|HTMLElement} target - A CSS selector string or an HTMLElement that triggers preloading.
162
+ * @param {object} [options={}] - Configuration options for preloading.
163
+ * @param {string} [options.crossOrigin='anonymous'] - The cross-origin attribute for the request, useful for fetching from other origins.
164
+ * @param {string} [options.referrerPolicy='no-referrer'] - The referrer policy to apply to the request.
165
+ * @param {string} [options.fetchPriority='high'] - The priority level of the fetch operation.
166
+ * @param {AbortSignal} [options.signal] - Optional signal to abort the preload operation if needed.
167
+ * @returns {Promise<void>} A promise that resolves once preloading completes.
168
+ * @throws {TypeError} Throws if the target is not a valid selector or an HTMLElement with a valid `href` attribute.
169
+ */
170
+ export async function preloadOnHover(target, {
171
+ crossOrigin = 'anonymous',
172
+ referrerPolicy = 'no-referrer',
173
+ fetchPriority = 'high',
174
+ signal,
175
+ } = {}) {
176
+ const { resolve, reject, promise } = Promise.withResolvers();
177
+
178
+ if (typeof target === 'string') {
179
+ await Promise.all(Array.from(
180
+ document.querySelectorAll(target),
181
+ link => preloadOnHover(link)
182
+ )).then(resolve, reject);
183
+ } else if (
184
+ target instanceof HTMLElement
185
+ && ! target.classList.contains('no-router')
186
+ && typeof target.href === 'string'
187
+ && target.origin === location.origin
188
+ && target.download.length === 0
189
+ && URL.canParse(target.href)
190
+ ) {
191
+ target.addEventListener('mouseover', async ({ currentTarget }) => {
192
+ const pattern = getRegistryKey(currentTarget.href);
193
+
194
+ if (pattern instanceof URLPattern) {
195
+ const specifier = getRegistrySpecifier(pattern);
196
+ const resolved = import.meta.resolve(specifier);
197
+
198
+ await preloadModule(resolved, {
199
+ fetchPriority,
200
+ referrerPolicy,
201
+ crossOrigin,
202
+ integrity: currentTarget.dataset.integrity,
203
+ signal,
204
+ });
205
+ resolve();
206
+ } else {
207
+ await preload(currentTarget.href, {
208
+ fetchPriority,
209
+ crossOrigin,
210
+ referrerPolicy,
211
+ as: currentTarget.dataset.preloadAs ?? 'fetch',
212
+ type: currentTarget.dataset.preloadType ?? 'text/html',
213
+ integrity: currentTarget.dataset.integrity,
214
+ signal,
215
+ });
216
+ resolve();
217
+ }
218
+ }, { once: true, passive: true, signal });
219
+ } else {
220
+ resolve();
221
+ }
222
+
223
+ await promise;
224
+ }
225
+
226
+ /**
227
+ * Adds `mouseenter` listeners to preload links/handlers via a `MutationObserver`
228
+ *
229
+ * @param {HTMLElement|ShadowRoot|string} target Target for the mutation observer or its selector
230
+ * @param {HTMLElement|ShadowRoot} [base=document] The element to query from if `target` is a selector
231
+ */
232
+ export function observePreloadsOn(target, base = document.documentElement) {
233
+ if (typeof target === 'string') {
234
+ observePreloadsOn(base.querySelector(target));
235
+ } else if (target instanceof HTMLElement || target instanceof ShadowRoot) {
236
+ preloadObserver.observe(target, { childList : true, subtree: true });
237
+ _handlePreloadMutations(target);
238
+ } else {
239
+ throw new TypeError('`observePreloadsOn` requires a selector or HTMLElement or ShadowRoot.');
240
+ }
241
+ }