UNPKG

@aegisjsproject/router

Version:
1 lines 124 kB
{"version":3,"file":"router.mjs","sources":["node_modules/@aegisjsproject/url/utils.js","node_modules/@aegisjsproject/url/parser.js","node_modules/@aegisjsproject/callback-registry/callbacks.js","node_modules/@aegisjsproject/callback-registry/events.js","router.js"],"sourcesContent":["export function stringify(thing) {\n\tswitch(typeof thing) {\n\t\tcase 'string':\n\t\t\treturn thing;\n\n\t\tcase 'function':\n\t\t\tthrow new TypeError('Functions are not supported.');\n\n\t\tcase 'undefined':\n\t\t\treturn '';\n\n\t\tcase 'object':\n\t\t\tif (thing === null) {\n\t\t\t\treturn '';\n\t\t\t} else if (thing instanceof Date) {\n\t\t\t\treturn thing.toISOString();\n\t\t\t} else if (Array.isArray(thing)) {\n\t\t\t\treturn thing.map(stringify).join(',');\n\t\t\t} else if (thing instanceof ArrayBuffer && Uint8Array.prototype.toBase64 instanceof Function) {\n\t\t\t\treturn new Uint8Array(thing).toBase64();\n\t\t\t} else if (ArrayBuffer.isView(thing) && thing.toBase64 instanceof Function) {\n\t\t\t\treturn thing.toBase64();\n\t\t\t} else if (thing instanceof Blob) {\n\t\t\t\treturn URL.createObjectURL(thing);\n\t\t\t} else {\n\t\t\t\treturn thing.toString();\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn thing.toString();\n\t}\n}\n","import { stringify } from './utils.js';\n\n/**\n * Escapes a component of a URL, also protecting against path traversal\n *\n * @param {string} str\n * @returns {string} The URL-safe string\n */\nexport function escape(str) {\n\treturn encodeURIComponent(stringify(str).trim()).replaceAll('..%2F', '%2E%2E%2F').replaceAll('.%2F', '%2E%2E%2F');\n}\n\n/**\n * Creates a URL parser tagged template with a custom base\n *\n * @param {string} [base=document.baseURI] Base to parse relative URLs from\n * @returns {Function} A URL parsing tagged template\n */\nexport function createURLParser(base = globalThis?.document?.baseURI) {\n\treturn function url(strings, value, ...values) {\n\t\tif (value instanceof Blob && strings.length === 2 && strings[0] === '' && strings[1] === '') {\n\t\t\treturn new URL(URL.createObjectURL(value));\n\t\t} else if (URL.canParse(value)) {\n\t\t\treturn URL.parse(String.raw(strings, '', ...values.map(escape)), value);\n\t\t} else if (strings[0].startsWith('/')) {\n\t\t\treturn URL.parse(String.raw(strings, escape(value), ...values.map(escape)), base);\n\t\t} else if (strings[0].startsWith('./') || strings[0].startsWith('../')) {\n\t\t\treturn URL.parse(String.raw(strings, escape(value), ...values.map(escape)), base);\n\t\t} else {\n\t\t\treturn URL.parse(String.raw(strings, escape(value), ...values.map(escape)));\n\t\t}\n\t};\n}\n\n/**\n * A function for creating URL objects from tagged template literals.\n *\n * @param {TemplateStringsArray} strings - The template string parts.\n * @param {...any} args - The template string substitutions.\n * @returns {URL | null} - A URL object if the URL is valid, otherwise null.\n */\nexport const url = createURLParser();\n","import {\n\teventToProp, capture as captureAttr, once as onceAttr, passive as passiveAttr, signal as signalAttr,\n\tregisterEventAttribute, hasEventAttribute, registerSignal, abortController,\n} from './events.js';\n\nlet _isRegistrationOpen = true;\n\nconst $$ = (selector, base = document) => base.querySelectorAll(selector);\n\nconst $ = (selector, base = document) => base.querySelector(selector);\n\nexport const FUNCS = {\n\tdebug: {\n\t\tlog: 'aegis:debug:log',\n\t\tinfo: 'aegis:debug:info',\n\t\twarn: 'aegis:debug:warn',\n\t\terror: 'aegis:debug:error',\n\t},\n\tnavigate: {\n\t\tback: 'aegis:navigate:back',\n\t\tforward: 'aegis:navigate:forward',\n\t\treload: 'aegis:navigate:reload',\n\t\tclose: 'aegis:navigate:close',\n\t\tlink: 'aegis:navigate:go',\n\t\tpopup: 'aegis:navigate:popup',\n\t},\n\tui: {\n\t\tprint: 'aegis:ui:print',\n\t\tremove: 'aegis:ui:remove',\n\t\thide: 'aegis:ui:hide',\n\t\tunhide: 'aegis:ui:unhide',\n\t\tshowModal: 'aegis:ui:showModal',\n\t\tcloseModal: 'aegis:ui:closeModal',\n\t\tshowPopover: 'aegis:ui:showPopover',\n\t\thidePopover: 'aegis:ui:hidePopover',\n\t\ttogglePopover: 'aegis:ui:togglePopover',\n\t\tenable: 'aegis:ui:enable',\n\t\tdisable: 'aegis:ui:disable',\n\t\tscrollTo: 'aegis:ui:scrollTo',\n\t\tprevent: 'aegis:ui:prevent',\n\t\trevokeObjectURL: 'aegis:ui:revokeObjectURL',\n\t\tcancelAnimationFrame: 'aegis:ui:cancelAnimationFrame',\n\t\tabortController: 'aegis:ui:controller:abort',\n\t},\n};\n\nconst registry = new Map([\n\t[FUNCS.debug.log, console.log],\n\t[FUNCS.debug.warn, console.warn],\n\t[FUNCS.debug.error, console.error],\n\t[FUNCS.debug.info, console.info],\n\t[FUNCS.navigate.back, () => history.back()],\n\t[FUNCS.navigate.forward, () => history.forward()],\n\t[FUNCS.navigate.reload, () => history.go(0)],\n\t[FUNCS.navigate.close, () => globalThis.close()],\n\t[FUNCS.navigate.link, event => {\n\t\tif (event.isTrusted) {\n\t\t\tevent.preventDefault();\n\t\t\tlocation.href = event.currentTarget.dataset.url;\n\t\t}\n\t}],\n\t[FUNCS.navigate.popup, event => {\n\t\tif (event.isTrusted) {\n\t\t\tevent.preventDefault();\n\t\t\tglobalThis.open(event.currentTarget.dataset.url);\n\t\t}\n\t}],\n\t[FUNCS.ui.hide, ({ currentTarget }) => {\n\t\t$$(currentTarget.dataset.hideSelector).forEach(el => el.hidden = true);\n\t}],\n\t[FUNCS.ui.unhide, ({ currentTarget }) => {\n\t\t$$(currentTarget.dataset.unhideSelector).forEach(el => el.hidden = false);\n\t}],\n\t[FUNCS.ui.disable, ({ currentTarget }) => {\n\t\t$$(currentTarget.dataset.disableSelector).forEach(el => el.disabled = true);\n\t}],\n\t[FUNCS.ui.enable, ({ currentTarget }) => {\n\t\t$$(currentTarget.dataset.enableSelector).forEach(el => el.disabled = false);\n\t}],\n\t[FUNCS.ui.remove, ({ currentTarget }) => {\n\t\t$$(currentTarget.dataset.removeSelector).forEach(el => el.remove());\n\t}],\n\t[FUNCS.ui.scrollTo, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.scrollToSelector);\n\n\t\tif (target instanceof Element) {\n\t\t\ttarget.scrollIntoView({\n\t\t\t\tbehavior: matchMedia('(prefers-reduced-motion: reduce)').matches\n\t\t\t\t\t? 'instant'\n\t\t\t\t\t: 'smooth',\n\t\t\t});\n\t\t}\n\t}],\n\t[FUNCS.ui.revokeObjectURL, ({ currentTarget }) => URL.revokeObjectURL(currentTarget.src)],\n\t[FUNCS.ui.cancelAnimationFrame, ({ currentTarget }) => cancelAnimationFrame(parseInt(currentTarget.dataset.animationFrame))],\n\t[FUNCS.ui.clearInterval, ({ currentTarget }) => clearInterval(parseInt(currentTarget.dataset.clearInterval))],\n\t[FUNCS.ui.clearTimeout, ({ currentTarget }) => clearTimeout(parseInt(currentTarget.dataset.timeout))],\n\t[FUNCS.ui.abortController, ({ currentTarget }) => abortController(currentTarget.dataset.aegisEventController, currentTarget.dataset.aegisControllerReason)],\n\t[FUNCS.ui.showModal, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.showModalSelector);\n\n\t\tif (target instanceof HTMLDialogElement) {\n\t\t\ttarget.showModal();\n\t\t}\n\t}],\n\t[FUNCS.ui.closeModal, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.closeModalSelector);\n\n\t\tif (target instanceof HTMLDialogElement) {\n\t\t\ttarget.close();\n\t\t}\n\t}],\n\t[FUNCS.ui.showPopover, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.showPopoverSelector);\n\n\t\tif (target instanceof HTMLElement) {\n\t\t\ttarget.showPopover();\n\t\t}\n\t}],\n\t[FUNCS.ui.hidePopover, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.hidePopoverSelector);\n\n\t\tif (target instanceof HTMLElement) {\n\t\t\ttarget.hidePopover();\n\t\t}\n\t}],\n\t[FUNCS.ui.togglePopover, ({ currentTarget }) => {\n\t\tconst target = $(currentTarget.dataset.togglePopoverSelector);\n\n\t\tif (target instanceof HTMLElement) {\n\t\t\ttarget.togglePopover();\n\t\t}\n\t}],\n\t[FUNCS.ui.print, () => globalThis.print()],\n\t[FUNCS.ui.prevent, event => event.preventDefault()],\n]);\n\n/**\n * Check if callback registry is open\n *\n * @returns {boolean} Whether or not callback registry is open\n */\nexport const isRegistrationOpen = () => _isRegistrationOpen;\n\n/**\n * Close callback registry\n *\n * @returns {boolean} Whether or not the callback was succesfully removed\n */\nexport const closeRegistration = () => _isRegistrationOpen = false;\n\n/**\n * Get an array of registered callbacks\n *\n * @returns {Array} A frozen array listing keys to all registered callbacks\n */\nexport const listCallbacks = () => Object.freeze(Array.from(registry.keys()));\n\n/**\n * Check if a callback is registered\n *\n * @param {string} name The name/key to check for in callback registry\n * @returns {boolean} Whether or not a callback is registered\n */\nexport const hasCallback = name => registry.has(name);\n\n/**\n * Get a callback from the registry by name/key\n *\n * @param {string} name The name/key of the callback to get\n * @returns {Function|undefined} The corresponding function registered under that name/key\n */\nexport const getCallback = name => registry.get(name);\n\n/**\n *\t Remove a callback from the registry\n *\n * @param {string} name The name/key of the callback to get\n * @returns {boolean} Whether or not the callback was successfully unregisterd\n */\nexport const unregisterCallback = name => _isRegistrationOpen && registry.delete(name);\n\n/**\n * Remove all callbacks from the registry\n *\n * @returns {void}\n */\nexport const clearRegistry = () => registry.clear();\n\n/**\n * Create a registered callback with a randomly generated name\n *\n * @param {Function} callback Callback function to register\n * @returns {string} The automatically generated key/name of the registered callback\n */\nexport const createCallback = (callback) => registerCallback('aegis:callback:' + crypto.randomUUID(), callback);\n\n/**\n * Call a callback fromt the registry by name/key\n *\n * @param {string} name The name/key of the registered function\n * @param {...any} args Any arguments to pass along to the function\n * @returns {any} Whatever the return value of the function is\n * @throws {Error} Throws if callback is not found or any error resulting from calling the function\n */\nexport function callCallback(name, ...args) {\n\tif (registry.has(name)) {\n\t\treturn registry.get(name).apply(this || globalThis, args);\n\t} else {\n\t\tthrow new Error(`No ${name} function registered.`);\n\t}\n}\n\n/**\n * Register a named callback in registry\n *\n * @param {string} name The name/key to register the callback under\n * @param {Function} callback The callback value to register\n * @returns {string} The registered name/key\n */\nexport function registerCallback(name, callback) {\n\tif (typeof name !== 'string' || name.length === 0) {\n\t\tthrow new TypeError('Callback name must be a string.');\n\t} if (! (callback instanceof Function)) {\n\t\tthrow new TypeError('Callback must be a function.');\n\t} else if (! _isRegistrationOpen) {\n\t\tthrow new TypeError('Cannot register new callbacks because registry is closed.');\n\t} else if (registry.has(name)) {\n\t\tthrow new Error(`Handler \"${name}\" is already registered.`);\n\t} else {\n\t\tregistry.set(name, callback);\n\t\treturn name;\n\t}\n}\n\n/**\n * Get the host/root node of a given thing.\n *\n * @param {Event|Document|Element|ShadowRoot} target Source thing to search for host of\n * @returns {Document|Element|null} The host/root node, or null\n */\nexport function getHost(target) {\n\tif (target instanceof Event) {\n\t\treturn getHost(target.currentTarget);\n\t} else if (target instanceof Document) {\n\t\treturn target;\n\t} else if (target instanceof Element) {\n\t\treturn getHost(target.getRootNode());\n\t} else if (target instanceof ShadowRoot) {\n\t\treturn target.host;\n\t} else {\n\t\treturn null;\n\t}\n}\n\nexport function on(event, callback, { capture = false, passive = false, once = false, signal } = {}) {\n\tif (callback instanceof Function) {\n\t\treturn on(event, createCallback(callback), { capture, passive, once, signal });\n\t} else if (typeof callback !== 'string' || callback.length === 0) {\n\t\tthrow new TypeError('Callback must be a function or a registered callback string.');\n\t} else if (typeof event !== 'string' || event.length === 0) {\n\t\tthrow new TypeError('Event must be a non-empty string.');\n\t} else {\n\t\tif (! hasEventAttribute(event)) {\n\t\t\tregisterEventAttribute(event);\n\t\t}\n\n\t\tconst parts = [[eventToProp(event), callback]];\n\n\t\tif (capture) {\n\t\t\tparts.push([captureAttr, '']);\n\t\t}\n\n\t\tif (passive) {\n\t\t\tparts.push([passiveAttr, '']);\n\t\t}\n\n\t\tif (once) {\n\t\t\tparts.push([onceAttr, '']);\n\t\t}\n\n\t\tif (signal instanceof AbortSignal) {\n\t\t\tparts.push([signalAttr, registerSignal(signal)]);\n\t\t} else if (typeof signal === 'string') {\n\t\t\tparts.push([signalAttr, signal]);\n\t\t}\n\n\t\treturn parts.map(([prop, val]) => `${prop}=\"${val}\"`).join(' ');\n\t}\n}\n","import { hasCallback, getCallback } from './callbacks.js';\n\nconst PREFIX = 'data-aegis-event-';\nconst EVENT_PREFIX = PREFIX + 'on-';\nconst EVENT_PREFIX_LENGTH = EVENT_PREFIX.length;\nconst DATA_PREFIX = 'aegisEventOn';\nconst DATA_PREFIX_LENGTH = DATA_PREFIX.length;\nconst signalSymbol = Symbol('aegis:signal');\nconst controllerSymbol = Symbol('aegis:controller');\nconst signalRegistry = new Map();\nconst controllerRegistry = new Map();\n\nexport const once = PREFIX + 'once';\nexport const passive = PREFIX + 'passive';\nexport const capture = PREFIX + 'capture';\nexport const signal = PREFIX + 'signal';\nexport const controller = PREFIX + 'controller';\nexport const onAbort = EVENT_PREFIX + 'abort';\nexport const onBlur = EVENT_PREFIX + 'blur';\nexport const onFocus = EVENT_PREFIX + 'focus';\nexport const onCancel = EVENT_PREFIX + 'cancel';\nexport const onAuxclick = EVENT_PREFIX + 'auxclick';\nexport const onBeforeinput = EVENT_PREFIX + 'beforeinput';\nexport const onBeforetoggle = EVENT_PREFIX + 'beforetoggle';\nexport const onCanplay = EVENT_PREFIX + 'canplay';\nexport const onCanplaythrough = EVENT_PREFIX + 'canplaythrough';\nexport const onChange = EVENT_PREFIX + 'change';\nexport const onClick = EVENT_PREFIX + 'click';\nexport const onClose = EVENT_PREFIX + 'close';\nexport const onContextmenu = EVENT_PREFIX + 'contextmenu';\nexport const onCopy = EVENT_PREFIX + 'copy';\nexport const onCuechange = EVENT_PREFIX + 'cuechange';\nexport const onCut = EVENT_PREFIX + 'cut';\nexport const onDblclick = EVENT_PREFIX + 'dblclick';\nexport const onDrag = EVENT_PREFIX + 'drag';\nexport const onDragend = EVENT_PREFIX + 'dragend';\nexport const onDragenter = EVENT_PREFIX + 'dragenter';\nexport const onDragexit = EVENT_PREFIX + 'dragexit';\nexport const onDragleave = EVENT_PREFIX + 'dragleave';\nexport const onDragover = EVENT_PREFIX + 'dragover';\nexport const onDragstart = EVENT_PREFIX + 'dragstart';\nexport const onDrop = EVENT_PREFIX + 'drop';\nexport const onDurationchange = EVENT_PREFIX + 'durationchange';\nexport const onEmptied = EVENT_PREFIX + 'emptied';\nexport const onEnded = EVENT_PREFIX + 'ended';\nexport const onFormdata = EVENT_PREFIX + 'formdata';\nexport const onInput = EVENT_PREFIX + 'input';\nexport const onInvalid = EVENT_PREFIX + 'invalid';\nexport const onKeydown = EVENT_PREFIX + 'keydown';\nexport const onKeypress = EVENT_PREFIX + 'keypress';\nexport const onKeyup = EVENT_PREFIX + 'keyup';\nexport const onLoad = EVENT_PREFIX + 'load';\nexport const onLoadeddata = EVENT_PREFIX + 'loadeddata';\nexport const onLoadedmetadata = EVENT_PREFIX + 'loadedmetadata';\nexport const onLoadstart = EVENT_PREFIX + 'loadstart';\nexport const onMousedown = EVENT_PREFIX + 'mousedown';\nexport const onMouseenter = EVENT_PREFIX + 'mouseenter';\nexport const onMouseleave = EVENT_PREFIX + 'mouseleave';\nexport const onMousemove = EVENT_PREFIX + 'mousemove';\nexport const onMouseout = EVENT_PREFIX + 'mouseout';\nexport const onMouseover = EVENT_PREFIX + 'mouseover';\nexport const onMouseup = EVENT_PREFIX + 'mouseup';\nexport const onWheel = EVENT_PREFIX + 'wheel';\nexport const onPaste = EVENT_PREFIX + 'paste';\nexport const onPause = EVENT_PREFIX + 'pause';\nexport const onPlay = EVENT_PREFIX + 'play';\nexport const onPlaying = EVENT_PREFIX + 'playing';\nexport const onProgress = EVENT_PREFIX + 'progress';\nexport const onRatechange = EVENT_PREFIX + 'ratechange';\nexport const onReset = EVENT_PREFIX + 'reset';\nexport const onResize = EVENT_PREFIX + 'resize';\nexport const onScroll = EVENT_PREFIX + 'scroll';\nexport const onScrollend = EVENT_PREFIX + 'scrollend';\nexport const onSecuritypolicyviolation = EVENT_PREFIX + 'securitypolicyviolation';\nexport const onSeeked = EVENT_PREFIX + 'seeked';\nexport const onSeeking = EVENT_PREFIX + 'seeking';\nexport const onSelect = EVENT_PREFIX + 'select';\nexport const onSlotchange = EVENT_PREFIX + 'slotchange';\nexport const onStalled = EVENT_PREFIX + 'stalled';\nexport const onSubmit = EVENT_PREFIX + 'submit';\nexport const onSuspend = EVENT_PREFIX + 'suspend';\nexport const onTimeupdate = EVENT_PREFIX + 'timeupdate';\nexport const onVolumechange = EVENT_PREFIX + 'volumechange';\nexport const onWaiting = EVENT_PREFIX + 'waiting';\nexport const onSelectstart = EVENT_PREFIX + 'selectstart';\nexport const onSelectionchange = EVENT_PREFIX + 'selectionchange';\nexport const onToggle = EVENT_PREFIX + 'toggle';\nexport const onPointercancel = EVENT_PREFIX + 'pointercancel';\nexport const onPointerdown = EVENT_PREFIX + 'pointerdown';\nexport const onPointerup = EVENT_PREFIX + 'pointerup';\nexport const onPointermove = EVENT_PREFIX + 'pointermove';\nexport const onPointerout = EVENT_PREFIX + 'pointerout';\nexport const onPointerover = EVENT_PREFIX + 'pointerover';\nexport const onPointerenter = EVENT_PREFIX + 'pointerenter';\nexport const onPointerleave = EVENT_PREFIX + 'pointerleave';\nexport const onGotpointercapture = EVENT_PREFIX + 'gotpointercapture';\nexport const onLostpointercapture = EVENT_PREFIX + 'lostpointercapture';\nexport const onMozfullscreenchange = EVENT_PREFIX + 'mozfullscreenchange';\nexport const onMozfullscreenerror = EVENT_PREFIX + 'mozfullscreenerror';\nexport const onAnimationcancel = EVENT_PREFIX + 'animationcancel';\nexport const onAnimationend = EVENT_PREFIX + 'animationend';\nexport const onAnimationiteration = EVENT_PREFIX + 'animationiteration';\nexport const onAnimationstart = EVENT_PREFIX + 'animationstart';\nexport const onTransitioncancel = EVENT_PREFIX + 'transitioncancel';\nexport const onTransitionend = EVENT_PREFIX + 'transitionend';\nexport const onTransitionrun = EVENT_PREFIX + 'transitionrun';\nexport const onTransitionstart = EVENT_PREFIX + 'transitionstart';\nexport const onWebkitanimationend = EVENT_PREFIX + 'webkitanimationend';\nexport const onWebkitanimationiteration = EVENT_PREFIX + 'webkitanimationiteration';\nexport const onWebkitanimationstart = EVENT_PREFIX + 'webkitanimationstart';\nexport const onWebkittransitionend = EVENT_PREFIX + 'webkittransitionend';\nexport const onError = EVENT_PREFIX + 'error';\n\nexport const eventAttrs = [\n\tonAbort,\n\tonBlur,\n\tonFocus,\n\tonCancel,\n\tonAuxclick,\n\tonBeforeinput,\n\tonBeforetoggle,\n\tonCanplay,\n\tonCanplaythrough,\n\tonChange,\n\tonClick,\n\tonClose,\n\tonContextmenu,\n\tonCopy,\n\tonCuechange,\n\tonCut,\n\tonDblclick,\n\tonDrag,\n\tonDragend,\n\tonDragenter,\n\tonDragexit,\n\tonDragleave,\n\tonDragover,\n\tonDragstart,\n\tonDrop,\n\tonDurationchange,\n\tonEmptied,\n\tonEnded,\n\tonFormdata,\n\tonInput,\n\tonInvalid,\n\tonKeydown,\n\tonKeypress,\n\tonKeyup,\n\tonLoad,\n\tonLoadeddata,\n\tonLoadedmetadata,\n\tonLoadstart,\n\tonMousedown,\n\tonMouseenter,\n\tonMouseleave,\n\tonMousemove,\n\tonMouseout,\n\tonMouseover,\n\tonMouseup,\n\tonWheel,\n\tonPaste,\n\tonPause,\n\tonPlay,\n\tonPlaying,\n\tonProgress,\n\tonRatechange,\n\tonReset,\n\tonResize,\n\tonScroll,\n\tonScrollend,\n\tonSecuritypolicyviolation,\n\tonSeeked,\n\tonSeeking,\n\tonSelect,\n\tonSlotchange,\n\tonStalled,\n\tonSubmit,\n\tonSuspend,\n\tonTimeupdate,\n\tonVolumechange,\n\tonWaiting,\n\tonSelectstart,\n\tonSelectionchange,\n\tonToggle,\n\tonPointercancel,\n\tonPointerdown,\n\tonPointerup,\n\tonPointermove,\n\tonPointerout,\n\tonPointerover,\n\tonPointerenter,\n\tonPointerleave,\n\tonGotpointercapture,\n\tonLostpointercapture,\n\tonMozfullscreenchange,\n\tonMozfullscreenerror,\n\tonAnimationcancel,\n\tonAnimationend,\n\tonAnimationiteration,\n\tonAnimationstart,\n\tonTransitioncancel,\n\tonTransitionend,\n\tonTransitionrun,\n\tonTransitionstart,\n\tonWebkitanimationend,\n\tonWebkitanimationiteration,\n\tonWebkitanimationstart,\n\tonWebkittransitionend,\n\tonError,\n];\n\nlet selector = eventAttrs.map(attr => `[${CSS.escape(attr)}]`).join(', ');\n\nconst attrToProp = attr => `on${attr[EVENT_PREFIX_LENGTH].toUpperCase()}${attr.substring(EVENT_PREFIX_LENGTH + 1)}`;\n\nexport const eventToProp = event => EVENT_PREFIX + event;\n\nexport const hasEventAttribute = event => eventAttrs.includes(EVENT_PREFIX + event);\n\nconst isEventDataAttr = ([name]) => name.startsWith(DATA_PREFIX);\n\nfunction _addListeners(el, { signal, attrFilter = EVENTS } = {}) {\n\tconst dataset = el.dataset;\n\n\tfor (const [attr, val] of Object.entries(dataset).filter(isEventDataAttr)) {\n\t\ttry {\n\t\t\tconst event = 'on' + attr.substring(DATA_PREFIX_LENGTH);\n\n\t\t\tif (attrFilter.hasOwnProperty(event) && hasCallback(val)) {\n\t\t\t\tel.addEventListener(event.substring(2).toLowerCase(), getCallback(val), {\n\t\t\t\t\tpassive: dataset.hasOwnProperty('aegisEventPassive'),\n\t\t\t\t\tcapture: dataset.hasOwnProperty('aegisEventCapture'),\n\t\t\t\t\tonce: dataset.hasOwnProperty('aegisEventOnce'),\n\t\t\t\t\tsignal: dataset.hasOwnProperty('aegisEventSignal') ? getSignal(dataset.aegisEventSignal) : signal,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch(err) {\n\t\t\treportError(err);\n\t\t}\n\t}\n}\n\nconst observer = new MutationObserver(records => {\n\trecords.forEach(record => {\n\t\tswitch(record.type) {\n\t\t\tcase 'childList':\n\t\t\t\t[...record.addedNodes]\n\t\t\t\t\t.filter(node => node.nodeType === Node.ELEMENT_NODE)\n\t\t\t\t\t.forEach(node => attachListeners(node));\n\t\t\t\tbreak;\n\n\t\t\tcase 'attributes':\n\t\t\t\tif (typeof record.oldValue === 'string' && hasCallback(record.oldValue)) {\n\t\t\t\t\trecord.target.removeEventListener(\n\t\t\t\t\t\trecord.attributeName.substring(EVENT_PREFIX_LENGTH),\n\t\t\t\t\t\tgetCallback(record.oldValue), {\n\t\t\t\t\t\t\tonce: record.target.hasAttribute(once),\n\t\t\t\t\t\t\tcapture: record.target.hasAttribute(capture),\n\t\t\t\t\t\t\tpassive: record.target.hasAttribute(passive),\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\trecord.target.hasAttribute(record.attributeName)\n\t\t\t\t\t&& hasCallback(record.target.getAttribute(record.attributeName))\n\t\t\t\t) {\n\t\t\t\t\trecord.target.addEventListener(\n\t\t\t\t\t\trecord.attributeName.substring(EVENT_PREFIX_LENGTH),\n\t\t\t\t\t\tgetCallback(record.target.getAttribute(record.attributeName)), {\n\t\t\t\t\t\t\tonce: record.target.hasAttribute(once),\n\t\t\t\t\t\t\tcapture: record.target.hasAttribute(capture),\n\t\t\t\t\t\t\tpassive: record.target.hasAttribute(passive),\n\t\t\t\t\t\t\tsignal: record.target.hasAttribute(signal) ? getSignal(record.target.getAttribute(signal)) : undefined,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t});\n});\n\nexport const EVENTS = {\n\tonAbort,\n\tonBlur,\n\tonFocus,\n\tonCancel,\n\tonAuxclick,\n\tonBeforeinput,\n\tonBeforetoggle,\n\tonCanplay,\n\tonCanplaythrough,\n\tonChange,\n\tonClick,\n\tonClose,\n\tonContextmenu,\n\tonCopy,\n\tonCuechange,\n\tonCut,\n\tonDblclick,\n\tonDrag,\n\tonDragend,\n\tonDragenter,\n\tonDragexit,\n\tonDragleave,\n\tonDragover,\n\tonDragstart,\n\tonDrop,\n\tonDurationchange,\n\tonEmptied,\n\tonEnded,\n\tonFormdata,\n\tonInput,\n\tonInvalid,\n\tonKeydown,\n\tonKeypress,\n\tonKeyup,\n\tonLoad,\n\tonLoadeddata,\n\tonLoadedmetadata,\n\tonLoadstart,\n\tonMousedown,\n\tonMouseenter,\n\tonMouseleave,\n\tonMousemove,\n\tonMouseout,\n\tonMouseover,\n\tonMouseup,\n\tonWheel,\n\tonPaste,\n\tonPause,\n\tonPlay,\n\tonPlaying,\n\tonProgress,\n\tonRatechange,\n\tonReset,\n\tonResize,\n\tonScroll,\n\tonScrollend,\n\tonSecuritypolicyviolation,\n\tonSeeked,\n\tonSeeking,\n\tonSelect,\n\tonSlotchange,\n\tonStalled,\n\tonSubmit,\n\tonSuspend,\n\tonTimeupdate,\n\tonVolumechange,\n\tonWaiting,\n\tonSelectstart,\n\tonSelectionchange,\n\tonToggle,\n\tonPointercancel,\n\tonPointerdown,\n\tonPointerup,\n\tonPointermove,\n\tonPointerout,\n\tonPointerover,\n\tonPointerenter,\n\tonPointerleave,\n\tonGotpointercapture,\n\tonLostpointercapture,\n\tonMozfullscreenchange,\n\tonMozfullscreenerror,\n\tonAnimationcancel,\n\tonAnimationend,\n\tonAnimationiteration,\n\tonAnimationstart,\n\tonTransitioncancel,\n\tonTransitionend,\n\tonTransitionrun,\n\tonTransitionstart,\n\tonWebkitanimationend,\n\tonWebkitanimationiteration,\n\tonWebkitanimationstart,\n\tonWebkittransitionend,\n\tonError,\n\tonce,\n\tpassive,\n\tcapture,\n};\n\n/**\n * Register an attribute to observe for adding/removing event listeners\n *\n * @param {string} attr Name of the attribute to observe\n * @param {object} options\n * @param {boolean} [options.addListeners=false] Whether or not to automatically add listeners\n * @param {Document|Element} [options.base=document.body] Root node to observe\n * @param {AbortSignal} [options.signal] An abort signal to remove any listeners when aborted\n * @returns {string} The resulting `data-*` attribute name\n */\nexport function registerEventAttribute(attr, {\n\taddListeners = false,\n\tbase = document.body,\n\tsignal,\n} = {}) {\n\tconst fullAttr = EVENT_PREFIX + attr.toLowerCase();\n\n\tif (! eventAttrs.includes(fullAttr)) {\n\t\tconst sel = `[${CSS.escape(fullAttr)}]`;\n\t\tconst prop = attrToProp(fullAttr);\n\t\teventAttrs.push(fullAttr);\n\t\tEVENTS[prop] = fullAttr;\n\t\tselector += `, ${sel}`;\n\n\t\tif (addListeners) {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tconst config = { attrFilter: { [prop]: sel }, signal };\n\t\t\t\t[base, ...base.querySelectorAll(sel)].forEach(el => _addListeners(el, config));\n\t\t\t});\n\t\t}\n\t}\n\n\treturn fullAttr;\n}\n\n/**\n * Registers an `AbortController` in the controller registry and returns the key for it\n *\n * @param {AbortController} controller\n * @returns {string} The randomly generated key with which the controller is registered\n * @throws {TypeError} If controller is not an `AbortController`\n * @throws {Error} Any `reason` if controller is already aborted\n */\nexport function registerController(controller) {\n\tif (! (controller instanceof AbortController)) {\n\t\tthrow new TypeError('Controller is not an `AbortSignal.');\n\t} else if (controller.signal.aborted) {\n\t\tthrow controller.signal.reason;\n\t} else if (typeof controller.signal[controllerSymbol] === 'string') {\n\t\treturn controller.signal[controllerSymbol];\n\t} else {\n\t\tconst key = 'aegis:event:controller:' + crypto.randomUUID();\n\t\tObject.defineProperty(controller.signal, controllerSymbol, { value: key, writable: false, enumerable: false });\n\t\tcontrollerRegistry.set(key, controller);\n\n\t\tcontroller.signal.addEventListener('abort', unregisterController, { once: true });\n\n\t\treturn key;\n\t}\n}\n\n/**\n * Removes a controller from the registry\n *\n * @param {AbortController|AbortSignal|string} key The registed key or the controller or signal it corresponds to\n * @returns {boolean} Whether or not the controller was successfully unregistered\n */\nexport function unregisterController(key) {\n\tif (key instanceof AbortController) {\n\t\treturn controllerRegistry.delete(key.signal[controllerSymbol]);\n\t} else if (key instanceof AbortSignal) {\n\t\treturn controllerRegistry.delete(key[controllerSymbol]);\n\t} else {\n\t\treturn controllerRegistry.delete(key);\n\t}\n}\n\n/**\n * Creates and registers an `AbortController` in the controller registry and returns the key for it\n *\n * @param {object} options\n * @param {AbortSignal} [options.signal] An optional `AbortSignal` to externally abort the controller with\n * @returns {string} The randomly generated key with which the controller is registered\n */\nexport function createController({ signal } = {}) {\n\tconst controller = new AbortController();\n\n\tif (signal instanceof AbortSignal) {\n\t\tsignal.addEventListener('abort', ({ target }) => controller.abort(target.reason), { signal: controller.signal});\n\t}\n\n\treturn registerController(controller);\n}\n\n/**\n * Get a registetd controller from the registry\n *\n * @param {string} key Generated key with which the controller was registered\n * @returns {AbortController|void} Any registered controller, if any\n */\nexport const getController = key => controllerRegistry.get(key);\n\nexport function abortController(key, reason) {\n\tconst controller = getController(key);\n\n\tif (! (controller instanceof AbortController)) {\n\t\treturn false;\n\t} else if (typeof reason === 'string') {\n\t\tcontroller.abort(new Error(reason));\n\t\treturn true;\n\t} else {\n\t\tcontroller.abort(reason);\n\t\treturn true;\n\t}\n}\n\n/**\n * Register an `AbortSignal` to be used in declarative HTML as a value for `data-aegis-event-signal`\n *\n * @param {AbortSignal} signal The signal to register\n * @returns {string} The registered key\n * @throws {TypeError} Thrown if not an `AbortSignal`\n */\nexport function registerSignal(signal) {\n\tif (! (signal instanceof AbortSignal)) {\n\t\tthrow new TypeError('Signal must be an `AbortSignal`.');\n\t} else if (typeof signal[signalSymbol] === 'string') {\n\t\treturn signal[signalSymbol];\n\t} else {\n\t\tconst key = 'aegis:event:signal:' + crypto.randomUUID();\n\t\tObject.defineProperty(signal, signalSymbol, { value: key, writable: false, enumerable: false });\n\t\tsignalRegistry.set(key, signal);\n\t\tsignal.addEventListener('abort', ({ target }) => unregisterSignal(target[signalSymbol]), { once: true });\n\n\t\treturn key;\n\t}\n}\n\n/**\n * Gets and `AbortSignal` from the registry\n *\n * @param {string} key The registered key for the signal\n * @returns {AbortSignal|void} The corresponding `AbortSignal`, if any\n */\nexport const getSignal = key => signalRegistry.get(key);\n\n/**\n * Removes an `AbortSignal` from the registry\n *\n * @param {AbortSignal|string} signal An `AbortSignal` or the registered key for one\n * @returns {boolean} Whether or not the signal was sucessfully unregistered\n * @throws {TypeError} Throws if `signal` is not an `AbortSignal` or the key for a registered signal\n */\nexport function unregisterSignal(signal) {\n\tif (signal instanceof AbortSignal) {\n\t\treturn signalRegistry.delete(signal[signalSymbol]);\n\t} else if (typeof signal === 'string') {\n\t\treturn signalRegistry.delete(signal);\n\t} else {\n\t\tthrow new TypeError('Signal must be an `AbortSignal` or registered key/attribute.');\n\t}\n}\n\n/**\n * Add listeners to an element and its children, matching a generated query based on registered attributes\n *\n * @param {Element|Document} target Root node to add listeners from\n * @param {object} options\n * @param {AbortSignal} [options.signal] Optional signal to remove event listeners\n * @returns {Element|Document} Returns the passed target node\n */\nexport function attachListeners(target, { signal } = {}) {\n\tconst nodes = target instanceof Element && target.matches(selector)\n\t\t? [target, ...target.querySelectorAll(selector)]\n\t\t: target.querySelectorAll(selector);\n\n\tnodes.forEach(el => _addListeners(el, { signal }));\n\n\treturn target;\n}\n\n/**\n * Add a node to the `MutationObserver` to observe attributes and add/remove event listeners\n *\n * @param {Document|Element} root Element to observe attributes on\n */\nexport function observeEvents(root = document) {\n\tattachListeners(root);\n\n\tobserver.observe(root, {\n\t\tsubtree: true,\n\t\tchildList:true,\n\t\tattributes: true,\n\t\tattributeOldValue: true,\n\t\tattributeFilter: eventAttrs,\n\t});\n}\n\n/**\n * Disconnects the `MutationObserver`, disabling observing of all attribute changes\n *\n * @returns {void}\n */\nexport const disconnectEventsObserver = () => observer.disconnect();\n\n/**\n * Register a global error handler callback\n *\n * @param {Function} callback Callback to register as a global error handler\n * @param {EventInit} config Typical event listener config object\n */\nexport function setGlobalErrorHandler(callback, { capture, once, passive, signal } = {}) {\n\tif (callback instanceof Function) {\n\t\tglobalThis.addEventListener('error', callback, { capture, once, passive, signal });\n\t} else {\n\t\tthrow new TypeError('Callback is not a function.');\n\t}\n}\n","import { getStateObj, diffState, notifyStateChange } from '@aegisjsproject/state';\nexport { url } from '@aegisjsproject/url/url.js';\nimport { onClick, onSubmit } from '@aegisjsproject/callback-registry/events.js';\n\nconst isModule = ! (document.currentScript instanceof HTMLScriptElement);\nconst SUPPORTS_IMPORTMAP = HTMLScriptElement.supports('importmap');\nconst ROUTES_REGISTRY = new Map();\nconst NO_BODY_METHODS = ['GET', 'HEAD', 'DELETE', 'OPTIONS'];\nconst DESC_SELECTOR = 'meta[name=\"description\"], meta[itemprop=\"description\"], meta[property=\"og:description\"], meta[name=\"twitter:description\"]';\nconst navObserver = new MutationObserver(entries => entries.forEach(entry => interceptNav(entry.target)));\nconst preloadObserver = new MutationObserver(entries => entries.forEach(_handlePreloadMutations));\nconst ROOT_ID = 'root';\nconst EVENT_TARGET = document;\nconst NAV_CLOSE_SYMBOL = Symbol.for('aegis:navigate:event:close');\nconst prefersReducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\nlet rootEl = document.getElementById(ROOT_ID) ?? document.body;\nlet rootSelector = '#' + ROOT_ID;\nconst SUPPORTS_TRUSTED_TYPES = 'trustedTypes' in globalThis;\nconst _isTrustedHTML = input => SUPPORTS_TRUSTED_TYPES && trustedTypes.isHTML(input);\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') && ! target.hasAttribute(onClick)) {\n\t\tpreloadOnHover(target, target.dataset);\n\t} else {\n\t\ttarget.querySelectorAll(`a:not(.no-router, [${onClick}])`).forEach(a => preloadOnHover(a, a.dataset));\n\t}\n}\n\nexport const NAV_EVENT = 'aegis:navigate';\n\nexport const EVENT_TYPES = {\n\tnavigate: 'aegis:router:navigate',\n\tback: 'aegis:router:back',\n\tforward: 'aegis:router:forward',\n\treload: 'aegis:router:reload',\n\tpop: 'aegis:router:pop',\n\tgo: 'aegis:router:go',\n\tload: 'aegis:router:load',\n\tsubmit: 'aegis:router:submit',\n};\n\nconst DEFAULT_REASONS = [EVENT_TYPES.back, EVENT_TYPES.forward, EVENT_TYPES.navigate, EVENT_TYPES.submit, EVENT_TYPES.reload, EVENT_TYPES.go];\n\nexport class AegisNavigationEvent extends CustomEvent {\n\t#reason;\n\t#url;\n\t#controller = new AbortController();\n\t#promises = [];\n\t#errors = [];\n\n\tconstructor(name = NAV_EVENT, reason = 'unknown', { bubbles = false, cancelable = true, detail = {\n\t\toldState: getStateObj(),\n\t\toldURL: new URL(location.href),\n\t} } = {}) {\n\t\tsuper(name, { bubbles, cancelable, detail });\n\t\tthis.#reason = reason;\n\t\tthis.#url = location.href;\n\t}\n\n\tget aborted() {\n\t\treturn this.#controller.signal.aborted;\n\t}\n\n\tget error() {\n\t\tswitch(this.#errors.length) {\n\t\t\tcase 0:\n\t\t\t\treturn null;\n\n\t\t\tcase 1:\n\t\t\t\treturn this.#errors[0];\n\n\t\t\tdefault:\n\t\t\t\treturn new AggregateError(this.#errors);\n\t\t}\n\t}\n\n\tget reason() {\n\t\treturn this.#reason;\n\t}\n\n\tget signal() {\n\t\treturn this.#controller.signal;\n\t}\n\n\tget url() {\n\t\treturn this.#url;\n\t}\n\n\tasync [NAV_CLOSE_SYMBOL]() {\n\t\tconst result = await Promise.allSettled(this.#promises).then(results => {\n\t\t\tthis.#errors.push(...results.filter(result => result.status === 'rejected').map(result => result.reason));\n\n\t\t\treturn this.cancelable && this.defaultPrevented;\n\t\t});\n\n\t\tthis.#controller.abort();\n\t\treturn result;\n\t}\n\n\tabort(reason) {\n\t\tthis.#controller.abort(reason);\n\t}\n\n\twaitUntil(promiseOrCallback, { signal } = {}) {\n\t\tconst { promise, resolve, reject } = Promise.withResolvers();\n\n\t\tthis.#promises.push(promise);\n\n\t\tif (signal instanceof AbortSignal && ! signal.aborted) {\n\t\t\tsignal.addEventListener('abort', ({ target }) =>{\n\t\t\t\treject(target.reason);\n\n\t\t\t\tif (this.cancelable && ! this.defaultPrevented) {\n\t\t\t\t\tsuper.preventDefault();\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tonce: true,\n\t\t\t\tsignal: this.#controller.signal,\n\t\t\t});\n\t\t}\n\n\t\tif (this.#controller.signal.aborted) {\n\t\t\treject(this.#controller.signal.reason);\n\t\t} else if (signal instanceof AbortSignal && signal.aborted) {\n\t\t\treject(signal.reason);\n\n\t\t\tif (this.cancelable && ! this.defaultPrevented) {\n\t\t\t\tsuper.preventDefault();\n\t\t\t}\n\t\t} else if (! this.defaultPrevented && promiseOrCallback instanceof Function) {\n\t\t\tPromise.try(() => promiseOrCallback(this, {\n\t\t\t\tsignal: signal instanceof AbortSignal ? AbortSignal.any([signal, this.#controller.signal]) : this.#controller.signal,\n\t\t\t\ttimestamp: performance.now()\n\t\t\t})).then(resolve, reject);\n\t\t} else if (! this.defaultPrevented && promiseOrCallback instanceof Promise) {\n\t\t\tpromiseOrCallback.then(resolve, reject);\n\t\t}\n\t}\n\n\t[Symbol.toStringTag]() {\n\t\treturn 'NavigationEvent';\n\t}\n\n\tstatic get defaultType() {\n\t\treturn NAV_EVENT;\n\t}\n\n\tstatic get reasons() {\n\t\treturn EVENT_TYPES;\n\t}\n}\n\n// Need this to be \"unsafe\" to not be restrictive on what modifications can be made to a page\nconst policy = SUPPORTS_TRUSTED_TYPES\n\t? trustedTypes.createPolicy('aegis-router#html', { createHTML: input => input })\n\t: Object.freeze({ createPolicy: input => input });\n\nasync function _popstateHandler(event) {\n\tconst diff = diffState(event.state ?? {});\n\tconst navigate = new AegisNavigationEvent(NAV_EVENT, EVENT_TYPES.pop, {\n\t\tdetail: { newState: event.state, oldState: null, oldURL: new URL(location.href), method: 'GET', formData: null },\n\t});\n\n\tEVENT_TARGET.dispatchEvent(navigate);\n\n\tif (! await navigate[NAV_CLOSE_SYMBOL]()) {\n\t\tconst old = history.scrollRestoration;\n\t\tconst [content] = await Promise.all([\n\t\t\tgetModule(new URL(location.href)),\n\t\t\tnotifyStateChange(diff),\n\t\t]);\n\n\t\thistory.scrollRestoration = 'auto';\n\t\t_updatePage(content);\n\t\thistory.scrollRestoration = old;\n\t}\n};\n\nfunction _createMeta(props = {}) {\n\tconst meta = document.createElement('meta');\n\n\tObject.entries(props).forEach(([key, val]) => meta.setAttribute(key, val));\n\treturn meta;\n}\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 {\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\tpassedSignal.addEventListener('abort', ({ target }) => {\n\t\t\t\treject(target.reason);\n\t\t\t}, { signal: controller.signal });\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 = _resolveModule(href);\n\n\t\t\tdocument.head.append(link);\n\n\t\t\treturn promise.then(() => link.remove()).catch(err => {\n\t\t\t\tif (link.isConnected) {\n\t\t\t\t\tlink.remove();\n\t\t\t\t}\n\n\t\t\t\treportError(err);\n\t\t\t});\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 _isModuleURL(src) {\n\tswitch(src[0]) {\n\t\tcase '/':\n\t\tcase '.':\n\t\t\treturn true;\n\n\t\tcase 'h':\n\t\t\treturn src.substring(0, '4') === 'http' && URL.canParse(src);\n\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction _resolveModule(src) {\n\tif (_isModuleURL(src)) {\n\t\treturn URL.parse(src, document.baseURI);\n\t} else if (! SUPPORTS_IMPORTMAP) {\n\t\tthrow new TypeError('Importmaps and module specifiers are not supported');\n\t} else if (! isModule) {\n\t\tthrow new TypeError('Cannot resolve a module specifier outside of a module script.');\n\t} else {\n\t\treturn import.meta.resolve(src);\n\t}\n}\n\nfunction _getLinkStateData(a) {\n\tconst entries = Object.entries(a.dataset)\n\t\t.filter(([name]) => name.startsWith('aegisState'))\n\t\t.map(([name, value]) => [name[10].toLowerCase() + name.substring(11), value]);\n\n\treturn Object.fromEntries(entries);\n}\n\nfunction _interceptLinkClick(event) {\n\tif (event.target.classList.contains('no-router') || event.target.hasAttribute(onClick)) {\n\t\tevent.target.removeEventListener(_interceptLinkClick);\n\t} else if (event.isTrusted && event.currentTarget.href.startsWith(location.origin)) {\n\t\tevent.preventDefault();\n\t\tconst state = _getLinkStateData(event.currentTarget);\n\t\tnavigate(event.currentTarget.href, state, {\n\t\t\tintegrity: event.currentTarget.dataset.integrity,\n\t\t\tcache: event.currentTarget.dataset.cache,\n\t\t\treferrerPolicy: event.currentTarget.dataset.referrerPolicy,\n\t\t});\n\t}\n};\n\nasync function _interceptFormSubmit(event) {\n\tif (event.target.classList.contains('no-router') || event.target.hasAttribute(onSubmit)) {\n\t\tevent.target.removeEventListener('submit', _interceptFormSubmit);\n\t} else if (event.isTrusted && event.target.action.startsWith(location.origin)) {\n\t\tevent.preventDefault();\n\t\tconst { method, action } = event.target;\n\t\tconst formData = new FormData(event.target);\n\n\t\tconst submit = new AegisNavigationEvent(NAV_EVENT, EVENT_TYPES.submit, {\n\t\t\tdetail: { oldState: getStateObj(), oldURL: new URL(location.href), formData },\n\t\t});\n\n\t\tEVENT_TARGET.dispatchEvent(submit);\n\n\t\tif (await submit[NAV_CLOSE_SYMBOL]()) {\n\t\t\treturn;\n\t\t} else if (NO_BODY_METHODS.includes(method.toUpperCase())) {\n\t\t\tconst url = new URL(action);\n\t\t\tconst params = new URLSearchParams(formData);\n\n\t\t\tfor (const [key, val] of params.entries()) {\n\t\t\t\turl.searchParams.append(key, val);\n\t\t\t}\n\n\t\t\tawait navigate(url, getStateObj(), { method });\n\t\t} else {\n\t\t\tawait navigate(action, getStateObj(), { method, formData });\n\t\t}\n\t}\n}\n\nasync function _getHTML(url, { signal, method = 'GET', body, integrity, cache = 'default', referrerPolicy = 'no-referrer' } = {}) {\n\tconst resp = await fetch(url, {\n\t\tmethod,\n\t\tbody: NO_BODY_METHODS.includes(method.toUpperCase()) ? null : body,\n\t\theaders: { 'Accept': 'text/html' },\n\t\tcache,\n\t\treferrerPolicy,\n\t\tintegrity,\n\t\tsignal,\n\t}).catch(err => err);\n\n\tif (resp.ok) {\n\t\tconst html = await resp.text();\n\t\treturn Document.parseHTMLUnsafe(policy.createHTML(html));\n\t} else if (resp instanceof Error) {\n\t\treturn resp;\n\t} else {\n\t\treturn _get404(url, method, { signal });\n\t}\n}\n\nfunction _updatePage(content) {\n\tconst timestamp = performance.now();\n\n\tif (content instanceof Document) {\n\t\tif (content.head.childElementCount !== 0) {\n\t\t\tsetTitle(content.title);\n\t\t\tsetDescription(content.querySelector(DESC_SELECTOR)?.content);\n\t\t}\n\n\t\tconst contentEl = typeof rootSelector === 'string' ? content.body.querySelector(rootSelector) ?? content.body : content.body;\n\n\t\trootEl.replaceChildren(...contentEl.childNodes);\n\t} else if (content instanceof HTMLTemplateElement) {\n\t\trootEl.replaceChildren(content.content);\n\t} else if (content instanceof Function && content.prototype instanceof HTMLElement) {\n\t\trootEl.replaceChildren(new content({ state: getStateObj(), url: new URL(location.href), timestamp }));\n\t} else if (content instanceof Node) {\n\t\trootEl.replaceChildren(content);\n\t} else if (content instanceof Function) {\n\t\t_updatePage(content());\n\t} else if (typeof content === 'string') {\n\t\trootEl.setHTMLUnsafe(policy.createHTML(content));\n\t} else if (_isTrustedHTML(content)) {\n\t\trootEl.setHTMLUnsafe(content);\n\t} else if (content instanceof Error) {\n\t\treportError(content);\n\t\trootEl.textContent = content.message;\n\t} else if (! (content === null || typeof content === 'undefined')) {\n\t\trootEl.textContent = content;\n\t}\n\n\tEVENT_TARGET.dispatchEvent(new AegisNavigationEvent(NAV_EVENT, EVENT_TYPES.load, { cancelable: false }));\n\n\tif (history.scrollRestoration === 'manual') {\n\t\tif (location.hash.length > 1) {\n\t\t\tconst target = document.getElementById(location.hash.substring(1)) ?? document.body;\n\t\t\ttarget.scrollIntoView({ behavior: prefersReducedMotion.matches ? 'instant' : 'smooth' });\n\t\t} else {\n\t\t\tconst autofocus = rootEl.querySelector('[autofocus]');\n\n\t\t\tif (autofocus instanceof Element) {\n\t\t\t\tautofocus.focus();\n\t\t\t} else {\n\t\t\t\tdocument.body.scrollIntoView({ behavior: prefersReducedMotion.matches ? 'instant' : 'smooth' });\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function _handleMetadata({ title, description } = {}, { state, matches, params, url, signal } = {}) {\n\tif (typeof title === 'string') {\n\t\tsetTitle(title);\n\t} else if (typeof title === 'function') {\n\t\tsetTitle(await title({ state, matches, params, url, signal }));\n\t}\n\n\tif (typeof description === 'string') {\n\t\tsetDescription(description);\n\t} else if (typeof description === 'function') {\n\t\tsetDescription(await description({ state, matches, params, url, signal }));\n\t}\n}\n\nasync function _handleModule(moduleSrc, { state = getStateObj(), matches = {}, params = {}, signal, ...args } = {}) {\n\tconst module = await Promise.try(() => {\n\t\tif (moduleSrc instanceof Function) {\n\t\t\treturn moduleSrc(args);\n\t\t} else if (typeof moduleSrc === 'string' || module instanceof URL) {\n\t\t\treturn _isModuleURL(moduleSrc)\n\t\t\t\t? import(URL.parse(moduleSrc, document.baseURI))\n\t\t\t\t: import(moduleSrc);\n\t\t} else {\n\t\t\treturn new TypeError('Invalid module src.');\n\t\t}\n\t}).catch(err => err);\n\n\tconst url = new URL(location.href);\n\tconst timestamp = performance.now();\n\n\tif (module instanceof URL) {\n\t\tawait navigate(module, state, args);\n\t} else if (module instanceof Error) {\n\t\treturn module.message;\n\t} else if (! ('default' in module)) {\n\t\treturn new Error(`${moduleSrc} has no default export.`);\n\t} else if (module.default instanceof Function && module.default.prototype instanceof HTMLElement) {\n\t\tif (typeof customElements.getName(module.default) !== 'string') {\n\t\t\tcustomElements.define(\n\t\t\t\tmodule.default[Symbol.for('tagName')] ?? `aegis-el-${crypto.randomUUID()}`,\n\t\t\t\tmodule.default\n\t\t\t);\n\t\t}\n\n\t\t_handleMetadata(module, { state, matches, params, url, signal });\n\n\t\treturn new module.default({\n\t\t\turl,\n\t\t\tmatches,\n\t\t\tparams,\n\t\t\tstate,\n\t\t\ttimestamp,\n\t\t\tsignal: getNavSignal({ signal }),\n\t\t\t...args\n\t\t});\n\t} else if (module.default instanceof Function) {\n\t\t_handleMetadata(module, { state, matches, params, url, signal });\n\t\treturn await module.default({\n\t\t\turl,\n\t\t\tmatches,\n\t\t\tparams,\n\t\t\tstate,\n\t\t\ttimestamp,\n\t\t\tsignal: getNavSignal({ signal }),\n\t\t\t...args\n\t\t});\n\t} else if (module.default instanceof Node || module.default instanceof Error) {\n\t\t_handleMetadata(module, { state, matches, params, url, signal });\n\t\t_updatePage(module.default);\n\t} else {\n\t\tthrow new TypeError(`${moduleSrc} has a missing or invalid default export.`);\n\t}\n}\n\nlet view404 = ({ url = location, method = 'GET' }) => {\n\tconst div = document.createElement('div');\n\tconst p = document.createElement('p');\n\tconst a = document.createElement('a');\n\n\tp.textContent = `${method.toUpperCase()} ${url.href} [404 Not Found]`;\n\ta.href = document.baseURI;\n\ta.textContent = 'Go Home';\n\n\ta.addEventListener('click', _interceptLinkClick);\n\tdiv.append(p, a);\n\n\treturn div;\n};\n\nasync function _get404(url = location, method = 'GET', { signal, formData, integrity } = {}) {\n\tconst timestamp = performance.now();\n\n\tif (typeof view404 === 'string') {\n\t\treturn await _handleModule(view404, { url, matches: null, signal, method, formData, timestamp, integrity });\n\t} else if (view404 instanceof Function) {\n\t\t_updatePage(view404({ timestamp, state: getStateObj(), url, matches: null, signal, method, formData, integrity }));\n\t}\n}\n\n/**\n * Finds the matching URL pattern for a given input.\n *\n * @param {string|URL} input - The input URL or path.\n * @returns {URLPattern|undefined} - The matching URL pattern, or undefined if no match is found.\n */\nexport const findPath = input => ROUTES_REGISTRY.keys().find(pattern => pattern.test(input));\n\n/**\n * Sets the 404 handler.\n *\n * @param {string} path - The path to the 404 handler module or the handler function itself