{"version":3,"file":"pjax.min.js","sources":["source://Pjax/utils/switchNodes.ts","source://Pjax/switchDOM.ts","source://Pjax/libs/executeScripts/index.ts","source://Pjax/preparePage.ts","source://Pjax/weakLoad.ts","source://Pjax/libs/LazyHistory/index.ts","source://Pjax/utils/Switches.ts","source://Pjax/libs/Submission/index.ts","source://Pjax/utils/DefaultTrigger.ts","source://Pjax/libs/executeScripts/Script.ts","source://Pjax/index.ts"],"sourcesContent":["import type * as Pjax from '..';\n\nimport Switches from './Switches';\n\nexport default async function switchNodes(sourceDocument: Document, {\n selectors,\n switches,\n signal = null,\n}: {\n selectors: Pjax.Options['selectors'];\n switches: Pjax.Options['switches'];\n signal?: AbortSignal | null;\n}): Promise {\n if (signal?.aborted) throw new DOMException('Aborted switches', 'AbortError');\n\n let focusCleared = false;\n const switchPromises: Promise[] = [];\n\n selectors.forEach((selector) => {\n const sourceNodeList = sourceDocument.querySelectorAll(selector);\n const targetNodeList = document.querySelectorAll(selector);\n\n // Throw when the structure is not match.\n if (sourceNodeList.length !== targetNodeList.length) {\n throw new DOMException(\n `Selector '${selector}' does not select the same amount of nodes`,\n 'IndexSizeError',\n );\n }\n\n const { activeElement } = document;\n\n // Start switching for each match.\n targetNodeList.forEach((targetNode, index) => {\n // Clear out focused controls before switching.\n if (!focusCleared && activeElement && targetNode.contains(activeElement)) {\n if (activeElement instanceof HTMLElement || activeElement instanceof SVGElement) {\n activeElement.blur();\n }\n focusCleared = true;\n }\n\n // Argument defined switch is prior to default switch.\n const targetSwitch: Pjax.Switch = switches?.[selector] || Switches.default;\n\n // Start switching. Package to promise. Ignore switch errors.\n const switchPromise = Promise.resolve()\n .then(() => targetSwitch(targetNode, sourceNodeList[index]))\n .catch(() => {});\n switchPromises.push(switchPromise);\n });\n });\n\n // Reject as soon as possible on abort.\n await Promise.race([\n Promise.all(switchPromises),\n new Promise((resolve, reject) => {\n signal?.addEventListener('abort', () => {\n reject(new DOMException('Aborted switches', 'AbortError'));\n });\n }),\n ]);\n\n return {\n focusCleared,\n };\n}\n","import type Pjax from '.';\nimport type { Options, EventDetail } from '.';\nimport switchNodes from './utils/switchNodes';\n\nexport default async function switchDOM(\n this: Pjax,\n requestInfo: RequestInfo,\n overrideOptions: Partial = {},\n): Promise {\n const {\n selectors,\n switches,\n cache,\n timeout,\n hooks,\n } = { ...this.options, ...overrideOptions };\n\n const eventDetail: EventDetail = {};\n\n const signal = this.abortController?.signal || null;\n eventDetail.signal = signal;\n\n /**\n * Specify request cache mode and abort signal.\n */\n const requestInit: RequestInit = { cache, signal };\n\n /**\n * Specify original referrer and referrerPolicy\n * since the later Request constructor steps discard the original ones.\n * @see [Request constructor steps | Fetch Standard]{@link https://fetch.spec.whatwg.org/#dom-request}\n */\n if (requestInfo instanceof Request) {\n requestInit.referrer = requestInfo.referrer;\n requestInit.referrerPolicy = requestInfo.referrerPolicy;\n }\n\n const rawRequest = new Request(requestInfo, requestInit);\n rawRequest.headers.set('X-Requested-With', 'Fetch');\n rawRequest.headers.set('X-Pjax', 'true');\n rawRequest.headers.set('X-Pjax-Selectors', JSON.stringify(selectors));\n\n const request = await hooks.request?.(rawRequest) || rawRequest;\n eventDetail.request = request;\n\n // Set timeout.\n eventDetail.timeout = timeout;\n let timeoutID: number | undefined;\n if (timeout > 0) {\n timeoutID = window.setTimeout(() => {\n this.abortController?.abort();\n }, timeout);\n eventDetail.timeoutID = timeoutID;\n }\n\n this.fire('send', eventDetail);\n\n try {\n const rawResponse = await fetch(request)\n .finally(() => {\n window.clearTimeout(timeoutID);\n });\n\n const response = await hooks.response?.(rawResponse) || rawResponse;\n eventDetail.response = response;\n\n this.fire('receive', eventDetail);\n\n // Push history state. Preserve hash as the fetch discards it.\n const newLocation = new URL(response.url);\n newLocation.hash = new URL(request.url).hash;\n if (window.location.href !== newLocation.href) {\n window.history.pushState(null, '', newLocation.href);\n }\n\n // Switch elements.\n const rawDocument = new DOMParser().parseFromString(await response.text(), 'text/html');\n const document = await hooks.document?.(rawDocument) || rawDocument;\n\n eventDetail.switches = switches;\n const rawSwitchesResult = await switchNodes(document, { selectors, switches, signal });\n\n const switchesResult = await hooks.switchesResult?.(rawSwitchesResult) || rawSwitchesResult;\n eventDetail.switchesResult = switchesResult;\n\n // Simulate initial page load.\n await this.preparePage(switchesResult, overrideOptions);\n } catch (error) {\n eventDetail.error = error;\n this.fire('error', eventDetail);\n throw error;\n } finally {\n this.fire('complete', eventDetail);\n }\n\n this.fire('success', eventDetail);\n}\n","import Script from './Script';\n\nclass Executor {\n declare signal: AbortSignal | null;\n\n constructor(signal: AbortSignal | null) {\n this.signal = signal;\n }\n\n /**\n * Execute script.\n * Throw only when aborted.\n * Wait only for blocking script.\n */\n async exec(script: Script) {\n if (this.signal?.aborted) throw new DOMException('Execution aborted', 'AbortError');\n const evalPromise = script.eval().catch(() => {});\n if (script.blocking) await evalPromise;\n }\n}\n\n/**\n * Find and execute scripts in order.\n * Needed since innerHTML does not run scripts.\n */\nexport default async function executeScripts(\n scriptEleList: Iterable,\n { signal = null }: { signal?: AbortSignal | null } = {},\n): Promise {\n if (signal?.aborted) throw new DOMException('Aborted execution', 'AbortError');\n\n const validScripts = Array.from(scriptEleList, (scriptEle) => new Script(scriptEle))\n .filter((script) => script.evaluable);\n\n const executor = new Executor(signal);\n\n // Evaluate external scripts first\n // to help browsers fetch them in parallel.\n // Each inline blocking script will be evaluated as soon as\n // all its previous blocking scripts are executed.\n const execution = validScripts.reduce((promise: Promise, script) => {\n if (script.external) {\n return Promise.all([promise, executor.exec(script)]);\n }\n return promise.then(() => executor.exec(script));\n }, Promise.resolve());\n\n // Reject as soon as possible on abort.\n await Promise.race([\n execution,\n new Promise((resolve, reject) => {\n signal?.addEventListener('abort', () => {\n reject(new DOMException('Aborted execution', 'AbortError'));\n });\n }),\n ]);\n}\n","import type Pjax from '.';\nimport type { Options, SwitchesResult } from '.';\n\nimport executeScripts from './libs/executeScripts';\n\n/**\n * Get the indicated part of the document.\n * Not using :target pseudo class here as it may not be updated by pushState.\n * @see [The indicated part of the document | HTML Standard]{@link https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-indicated-part-of-the-document}\n */\nconst getIndicatedPart = () => {\n let target: Element | null = null;\n const hashId = decodeURIComponent(window.location.hash.slice(1));\n if (hashId) target = document.getElementById(hashId) || document.getElementsByName(hashId)[0];\n if (!target && (!hashId || hashId.toLowerCase() === 'top')) target = document.scrollingElement;\n return target;\n};\n\n/**\n * After page elements are updated.\n */\nexport default async function preparePage(\n this: Pjax,\n switchesResult: SwitchesResult | null,\n overrideOptions: Partial = {},\n): Promise {\n const options = { ...this.options, ...overrideOptions };\n\n // If page elements are switched.\n if (switchesResult) {\n // Focus the FIRST autofocus if the previous focus is cleared.\n // https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute\n if (switchesResult.focusCleared) {\n const autofocus = document.querySelectorAll('[autofocus]')[0];\n if (autofocus instanceof HTMLElement || autofocus instanceof SVGElement) {\n autofocus.focus();\n }\n }\n\n // List newly added and labeled scripts.\n const scripts: HTMLScriptElement[] = [];\n if (options.scripts) {\n document.querySelectorAll(options.scripts).forEach((element) => {\n if (element instanceof HTMLScriptElement) scripts.push(element);\n });\n }\n options.selectors.forEach((selector) => {\n document.querySelectorAll(selector).forEach((element) => {\n if (element instanceof HTMLScriptElement) {\n scripts.push(element);\n } else {\n element.querySelectorAll('script').forEach((script) => {\n if (scripts.includes(script)) return;\n scripts.push(script);\n });\n }\n });\n });\n\n // Sort in document order.\n // https://stackoverflow.com/a/22613028\n scripts.sort((a, b) => (\n // Bitwise AND operator is required here.\n // eslint-disable-next-line no-bitwise\n a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING || -1\n ));\n\n // Execute.\n await executeScripts(scripts, { signal: this.abortController?.signal || null });\n }\n\n // Parse required scroll position.\n const { scrollTo } = options;\n\n // When scroll is allowed.\n if (scrollTo !== false) {\n // If switched, default to left top. Otherwise, default to no scroll.\n let parsedScrollTo: [number, number] | false = switchesResult ? [0, 0] : false;\n\n if (Array.isArray(scrollTo)) {\n parsedScrollTo = scrollTo;\n } else if (typeof scrollTo === 'number') {\n parsedScrollTo = [window.scrollX, scrollTo];\n } else {\n const target = getIndicatedPart();\n\n if (target) {\n target.scrollIntoView();\n parsedScrollTo = false;\n }\n }\n\n // Scroll.\n if (parsedScrollTo) window.scrollTo(parsedScrollTo[0], parsedScrollTo[1]);\n }\n}\n","import type Pjax from '.';\nimport type { Options } from '.';\n\n/**\n * Load a URL in Pjax way. Throw all errors.\n */\nexport default async function weakLoad(\n this: Pjax,\n requestInfo: RequestInfo,\n overrideOptions: Partial = {},\n): Promise {\n // Store scroll position.\n this.storeHistory();\n\n // Setup abort controller.\n const abortController = new AbortController();\n this.abortController?.abort();\n this.abortController = abortController;\n\n /**\n * The URL object of the target resource.\n * Used to identify fragment navigations.\n */\n const url = new URL(typeof requestInfo === 'string' ? requestInfo : requestInfo.url, document.baseURI);\n const path = url.pathname + url.search;\n const currentPath = this.location.pathname + this.location.search;\n\n /**\n * Identify fragment navigations.\n * Not using `.hash` here as it becomes the empty string for both empty and null fragment.\n * @see [Navigate fragment step | HTML Standard]{@link https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid-step}\n * @see [URL hash getter | URL Standard]{@link https://url.spec.whatwg.org/#dom-url-hash}\n */\n if (path === currentPath && url.href.includes('#')) {\n // pushState on different hash.\n if (window.location.hash !== url.hash) {\n window.history.pushState(null, '', url.href);\n }\n\n // Directly prepare for fragment navigation.\n await this.preparePage(null, overrideOptions);\n } else {\n // Switch DOM for normal navigation.\n await this.switchDOM(requestInfo, overrideOptions);\n }\n\n // Update Pjax location and prepare the page.\n this.history.pull();\n this.location.href = window.location.href;\n\n // Finish, remove abort controller.\n this.abortController = null;\n}\n","/**\n * Lazy Session History API\n * ===\n * Access the associated data of a history entry even after user navigations.\n *\n * On page navigation events (like popstate), `window.history.state` has already been changed and\n * we can't update the previous state anymore. To leave a last mark on the leaving page, we have to\n * either keep updating the state continuously - which usually causes performance issues,\n * or make use of other API.\n *\n * Internally, this module uses **session storage** to store data, and uses browsers' original\n * history state as keys to identify session storage items.\n */\n\n/**\n * A valid history state object.\n */\nexport interface HistoryState {\n [name: string]: unknown;\n}\n\nclass LazyHistory {\n /**\n * The index of current state.\n */\n declare private index: number;\n\n /**\n * The key used in `window.history.state` and session storage.\n */\n declare key: string;\n\n /**\n * The current state.\n */\n declare state: State | null;\n\n constructor(key: string) {\n this.key = key;\n\n this.pull();\n }\n\n /**\n * Keep up with current browser history entry.\n */\n pull(): void {\n // Get new state index.\n const historyState = window.history.state as HistoryState | null;\n const pulledIndex = historyState?.[this.key] as number | undefined;\n\n // Return if up-to-date.\n if (pulledIndex !== undefined && this.index === pulledIndex) return;\n\n // Get stored states.\n const stateListStr = window.sessionStorage.getItem(this.key);\n const stateList = stateListStr ? JSON.parse(stateListStr) as (State | null)[] : [];\n\n // Store current state.\n stateList[this.index] = this.state;\n window.sessionStorage.setItem(this.key, JSON.stringify(stateList));\n\n if (pulledIndex === undefined) {\n this.index = stateList.length;\n this.state = null;\n window.history.replaceState({\n ...historyState,\n [this.key]: this.index,\n }, document.title);\n } else {\n this.index = pulledIndex;\n this.state = stateListStr ? stateList[pulledIndex] : null;\n }\n }\n}\n\nexport default LazyHistory;\n","import type * as Pjax from '..';\n\n/**\n * Replace HTML contents by using innerHTML.\n */\nconst innerHTML: Pjax.Switch = (oldNode, newNode) => {\n // eslint-disable-next-line no-param-reassign\n oldNode.innerHTML = newNode.innerHTML;\n};\n\n/**\n * Replace all text by using textContent.\n */\nconst textContent: Pjax.Switch = (oldNode, newNode) => {\n // eslint-disable-next-line no-param-reassign\n oldNode.textContent = newNode.textContent;\n};\n\n/**\n * Replace readable text by using innerText.\n */\nconst innerText: Pjax.Switch = (oldEle, newEle) => {\n // eslint-disable-next-line no-param-reassign\n oldEle.innerText = newEle.innerText;\n};\n\n/**\n * Rewrite all attributes.\n */\nconst attributes: Pjax.Switch = (oldEle, newEle) => {\n let existingNames = oldEle.getAttributeNames();\n const targetNames = newEle.getAttributeNames();\n targetNames.forEach((target) => {\n oldEle.setAttribute(target, newEle.getAttribute(target) || '');\n existingNames = existingNames.filter((existing) => existing !== target);\n });\n existingNames.forEach((existing) => {\n oldEle.removeAttribute(existing);\n });\n};\n\n/**\n * Replace the whole element by using replaceWith.\n */\nconst replaceWith: Pjax.Switch = (oldNode, newNode) => {\n oldNode.replaceWith(newNode);\n};\n\nconst Switches = {\n default: replaceWith,\n innerHTML,\n textContent,\n innerText,\n attributes,\n replaceWith,\n};\n\nexport default Switches;\n","const capitalize = (str: T) => (\n `${str.charAt(0).toUpperCase()}${str.slice(1)}` as Capitalize\n);\n\ntype FormContentAttributeNames = 'action' | 'enctype' | 'method' | 'target';\n\n/**\n * A submit button must be a