diff --git a/lib/rdoc/generator/template/rails/_head.rhtml b/lib/rdoc/generator/template/rails/_head.rhtml index cc3be53f..e7ed0efd 100644 --- a/lib/rdoc/generator/template/rails/_head.rhtml +++ b/lib/rdoc/generator/template/rails/_head.rhtml @@ -6,7 +6,7 @@ - + diff --git a/lib/rdoc/generator/template/rails/resources/js/@hotwired--turbo.js b/lib/rdoc/generator/template/rails/resources/js/@hotwired--turbo.js index 303ecf2e..f1ebc032 100644 --- a/lib/rdoc/generator/template/rails/resources/js/@hotwired--turbo.js +++ b/lib/rdoc/generator/template/rails/resources/js/@hotwired--turbo.js @@ -2,3959 +2,3953 @@ Turbo 7.3.0 Copyright © 2023 37signals LLC */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Turbo = {})); -})(this, (function (exports) { 'use strict'; +(function () { + if (window.Reflect === undefined || + window.customElements === undefined || + window.customElements.polyfillWrapFlushCallback) { + return; + } + const BuiltInHTMLElement = HTMLElement; + const wrapperForTheName = { + HTMLElement: function HTMLElement() { + return Reflect.construct(BuiltInHTMLElement, [], this.constructor); + }, + }; + window.HTMLElement = wrapperForTheName["HTMLElement"]; + HTMLElement.prototype = BuiltInHTMLElement.prototype; + HTMLElement.prototype.constructor = HTMLElement; + Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement); +})(); - (function () { - if (window.Reflect === undefined || - window.customElements === undefined || - window.customElements.polyfillWrapFlushCallback) { - return; - } - const BuiltInHTMLElement = HTMLElement; - const wrapperForTheName = { - HTMLElement: function HTMLElement() { - return Reflect.construct(BuiltInHTMLElement, [], this.constructor); - }, - }; - window.HTMLElement = wrapperForTheName["HTMLElement"]; - HTMLElement.prototype = BuiltInHTMLElement.prototype; - HTMLElement.prototype.constructor = HTMLElement; - Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement); - })(); +/** + * The MIT License (MIT) + * + * Copyright (c) 2019 Javan Makhmali + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ - /** - * The MIT License (MIT) - * - * Copyright (c) 2019 Javan Makhmali - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ +(function(prototype) { + if (typeof prototype.requestSubmit == "function") return - (function(prototype) { - if (typeof prototype.requestSubmit == "function") return + prototype.requestSubmit = function(submitter) { + if (submitter) { + validateSubmitter(submitter, this); + submitter.click(); + } else { + submitter = document.createElement("input"); + submitter.type = "submit"; + submitter.hidden = true; + this.appendChild(submitter); + submitter.click(); + this.removeChild(submitter); + } + }; - prototype.requestSubmit = function(submitter) { - if (submitter) { - validateSubmitter(submitter, this); - submitter.click(); - } else { - submitter = document.createElement("input"); - submitter.type = "submit"; - submitter.hidden = true; - this.appendChild(submitter); - submitter.click(); - this.removeChild(submitter); - } - }; + function validateSubmitter(submitter, form) { + submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'"); + submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button"); + submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError"); + } - function validateSubmitter(submitter, form) { - submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'"); - submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button"); - submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError"); - } + function raise(errorConstructor, message, name) { + throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name) + } +})(HTMLFormElement.prototype); - function raise(errorConstructor, message, name) { - throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name) - } - })(HTMLFormElement.prototype); +const submittersByForm = new WeakMap(); +function findSubmitterFromClickTarget(target) { + const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null; + const candidate = element ? element.closest("input, button") : null; + return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null; +} +function clickCaptured(event) { + const submitter = findSubmitterFromClickTarget(event.target); + if (submitter && submitter.form) { + submittersByForm.set(submitter.form, submitter); + } +} +(function () { + if ("submitter" in Event.prototype) + return; + let prototype = window.Event.prototype; + if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) { + prototype = window.SubmitEvent.prototype; + } + else if ("SubmitEvent" in window) { + return; + } + addEventListener("click", clickCaptured, true); + Object.defineProperty(prototype, "submitter", { + get() { + if (this.type == "submit" && this.target instanceof HTMLFormElement) { + return submittersByForm.get(this.target); + } + }, + }); +})(); - const submittersByForm = new WeakMap(); - function findSubmitterFromClickTarget(target) { - const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null; - const candidate = element ? element.closest("input, button") : null; - return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null; +var FrameLoadingStyle; +(function (FrameLoadingStyle) { + FrameLoadingStyle["eager"] = "eager"; + FrameLoadingStyle["lazy"] = "lazy"; +})(FrameLoadingStyle || (FrameLoadingStyle = {})); +class FrameElement extends HTMLElement { + static get observedAttributes() { + return ["disabled", "complete", "loading", "src"]; } - function clickCaptured(event) { - const submitter = findSubmitterFromClickTarget(event.target); - if (submitter && submitter.form) { - submittersByForm.set(submitter.form, submitter); - } + constructor() { + super(); + this.loaded = Promise.resolve(); + this.delegate = new FrameElement.delegateConstructor(this); } - (function () { - if ("submitter" in Event.prototype) - return; - let prototype = window.Event.prototype; - if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) { - prototype = window.SubmitEvent.prototype; - } - else if ("SubmitEvent" in window) { - return; - } - addEventListener("click", clickCaptured, true); - Object.defineProperty(prototype, "submitter", { - get() { - if (this.type == "submit" && this.target instanceof HTMLFormElement) { - return submittersByForm.get(this.target); - } - }, - }); - })(); - - exports.FrameLoadingStyle = void 0; - (function (FrameLoadingStyle) { - FrameLoadingStyle["eager"] = "eager"; - FrameLoadingStyle["lazy"] = "lazy"; - })(exports.FrameLoadingStyle || (exports.FrameLoadingStyle = {})); - class FrameElement extends HTMLElement { - static get observedAttributes() { - return ["disabled", "complete", "loading", "src"]; - } - constructor() { - super(); - this.loaded = Promise.resolve(); - this.delegate = new FrameElement.delegateConstructor(this); - } - connectedCallback() { - this.delegate.connect(); - } - disconnectedCallback() { - this.delegate.disconnect(); - } - reload() { - return this.delegate.sourceURLReloaded(); - } - attributeChangedCallback(name) { - if (name == "loading") { - this.delegate.loadingStyleChanged(); - } - else if (name == "complete") { - this.delegate.completeChanged(); - } - else if (name == "src") { - this.delegate.sourceURLChanged(); - } - else { - this.delegate.disabledChanged(); - } + connectedCallback() { + this.delegate.connect(); + } + disconnectedCallback() { + this.delegate.disconnect(); + } + reload() { + return this.delegate.sourceURLReloaded(); + } + attributeChangedCallback(name) { + if (name == "loading") { + this.delegate.loadingStyleChanged(); } - get src() { - return this.getAttribute("src"); + else if (name == "complete") { + this.delegate.completeChanged(); } - set src(value) { - if (value) { - this.setAttribute("src", value); - } - else { - this.removeAttribute("src"); - } + else if (name == "src") { + this.delegate.sourceURLChanged(); } - get loading() { - return frameLoadingStyleFromString(this.getAttribute("loading") || ""); + else { + this.delegate.disabledChanged(); } - set loading(value) { - if (value) { - this.setAttribute("loading", value); - } - else { - this.removeAttribute("loading"); - } + } + get src() { + return this.getAttribute("src"); + } + set src(value) { + if (value) { + this.setAttribute("src", value); } - get disabled() { - return this.hasAttribute("disabled"); + else { + this.removeAttribute("src"); } - set disabled(value) { - if (value) { - this.setAttribute("disabled", ""); - } - else { - this.removeAttribute("disabled"); - } + } + get loading() { + return frameLoadingStyleFromString(this.getAttribute("loading") || ""); + } + set loading(value) { + if (value) { + this.setAttribute("loading", value); } - get autoscroll() { - return this.hasAttribute("autoscroll"); + else { + this.removeAttribute("loading"); } - set autoscroll(value) { - if (value) { - this.setAttribute("autoscroll", ""); - } - else { - this.removeAttribute("autoscroll"); - } + } + get disabled() { + return this.hasAttribute("disabled"); + } + set disabled(value) { + if (value) { + this.setAttribute("disabled", ""); } - get complete() { - return !this.delegate.isLoading; + else { + this.removeAttribute("disabled"); } - get isActive() { - return this.ownerDocument === document && !this.isPreview; + } + get autoscroll() { + return this.hasAttribute("autoscroll"); + } + set autoscroll(value) { + if (value) { + this.setAttribute("autoscroll", ""); } - get isPreview() { - var _a, _b; - return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview"); + else { + this.removeAttribute("autoscroll"); } } - function frameLoadingStyleFromString(style) { - switch (style.toLowerCase()) { - case "lazy": - return exports.FrameLoadingStyle.lazy; - default: - return exports.FrameLoadingStyle.eager; - } + get complete() { + return !this.delegate.isLoading; + } + get isActive() { + return this.ownerDocument === document && !this.isPreview; + } + get isPreview() { + var _a, _b; + return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview"); } +} +function frameLoadingStyleFromString(style) { + switch (style.toLowerCase()) { + case "lazy": + return FrameLoadingStyle.lazy; + default: + return FrameLoadingStyle.eager; + } +} + +function expandURL(locatable) { + return new URL(locatable.toString(), document.baseURI); +} +function getAnchor(url) { + let anchorMatch; + if (url.hash) { + return url.hash.slice(1); + } + else if ((anchorMatch = url.href.match(/#(.*)$/))) { + return anchorMatch[1]; + } +} +function getAction(form, submitter) { + const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action; + return expandURL(action); +} +function getExtension(url) { + return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || ""; +} +function isHTML(url) { + return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/); +} +function isPrefixedBy(baseURL, url) { + const prefix = getPrefix(url); + return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix); +} +function locationIsVisitable(location, rootLocation) { + return isPrefixedBy(location, rootLocation) && isHTML(location); +} +function getRequestURL(url) { + const anchor = getAnchor(url); + return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href; +} +function toCacheKey(url) { + return getRequestURL(url); +} +function urlsAreEqual(left, right) { + return expandURL(left).href == expandURL(right).href; +} +function getPathComponents(url) { + return url.pathname.split("/").slice(1); +} +function getLastPathComponent(url) { + return getPathComponents(url).slice(-1)[0]; +} +function getPrefix(url) { + return addTrailingSlash(url.origin + url.pathname); +} +function addTrailingSlash(value) { + return value.endsWith("/") ? value : value + "/"; +} - function expandURL(locatable) { - return new URL(locatable.toString(), document.baseURI); +class FetchResponse { + constructor(response) { + this.response = response; } - function getAnchor(url) { - let anchorMatch; - if (url.hash) { - return url.hash.slice(1); - } - else if ((anchorMatch = url.href.match(/#(.*)$/))) { - return anchorMatch[1]; - } + get succeeded() { + return this.response.ok; } - function getAction(form, submitter) { - const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action; - return expandURL(action); + get failed() { + return !this.succeeded; } - function getExtension(url) { - return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || ""; + get clientError() { + return this.statusCode >= 400 && this.statusCode <= 499; } - function isHTML(url) { - return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/); + get serverError() { + return this.statusCode >= 500 && this.statusCode <= 599; } - function isPrefixedBy(baseURL, url) { - const prefix = getPrefix(url); - return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix); + get redirected() { + return this.response.redirected; } - function locationIsVisitable(location, rootLocation) { - return isPrefixedBy(location, rootLocation) && isHTML(location); + get location() { + return expandURL(this.response.url); } - function getRequestURL(url) { - const anchor = getAnchor(url); - return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href; + get isHTML() { + return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/); } - function toCacheKey(url) { - return getRequestURL(url); + get statusCode() { + return this.response.status; } - function urlsAreEqual(left, right) { - return expandURL(left).href == expandURL(right).href; + get contentType() { + return this.header("Content-Type"); } - function getPathComponents(url) { - return url.pathname.split("/").slice(1); + get responseText() { + return this.response.clone().text(); } - function getLastPathComponent(url) { - return getPathComponents(url).slice(-1)[0]; + get responseHTML() { + if (this.isHTML) { + return this.response.clone().text(); + } + else { + return Promise.resolve(undefined); + } } - function getPrefix(url) { - return addTrailingSlash(url.origin + url.pathname); + header(name) { + return this.response.headers.get(name); } - function addTrailingSlash(value) { - return value.endsWith("/") ? value : value + "/"; +} + +function activateScriptElement(element) { + if (element.getAttribute("data-turbo-eval") == "false") { + return element; + } + else { + const createdScriptElement = document.createElement("script"); + const cspNonce = getMetaContent("csp-nonce"); + if (cspNonce) { + createdScriptElement.nonce = cspNonce; + } + createdScriptElement.textContent = element.textContent; + createdScriptElement.async = false; + copyElementAttributes(createdScriptElement, element); + return createdScriptElement; + } +} +function copyElementAttributes(destinationElement, sourceElement) { + for (const { name, value } of sourceElement.attributes) { + destinationElement.setAttribute(name, value); + } +} +function createDocumentFragment(html) { + const template = document.createElement("template"); + template.innerHTML = html; + return template.content; +} +function dispatch(eventName, { target, cancelable, detail } = {}) { + const event = new CustomEvent(eventName, { + cancelable, + bubbles: true, + composed: true, + detail, + }); + if (target && target.isConnected) { + target.dispatchEvent(event); + } + else { + document.documentElement.dispatchEvent(event); + } + return event; +} +function nextAnimationFrame() { + return new Promise((resolve) => requestAnimationFrame(() => resolve())); +} +function nextEventLoopTick() { + return new Promise((resolve) => setTimeout(() => resolve(), 0)); +} +function nextMicrotask() { + return Promise.resolve(); +} +function parseHTMLDocument(html = "") { + return new DOMParser().parseFromString(html, "text/html"); +} +function unindent(strings, ...values) { + const lines = interpolate(strings, values).replace(/^\n/, "").split("\n"); + const match = lines[0].match(/^\s+/); + const indent = match ? match[0].length : 0; + return lines.map((line) => line.slice(indent)).join("\n"); +} +function interpolate(strings, values) { + return strings.reduce((result, string, i) => { + const value = values[i] == undefined ? "" : values[i]; + return result + string + value; + }, ""); +} +function uuid() { + return Array.from({ length: 36 }) + .map((_, i) => { + if (i == 8 || i == 13 || i == 18 || i == 23) { + return "-"; + } + else if (i == 14) { + return "4"; + } + else if (i == 19) { + return (Math.floor(Math.random() * 4) + 8).toString(16); + } + else { + return Math.floor(Math.random() * 15).toString(16); + } + }) + .join(""); +} +function getAttribute(attributeName, ...elements) { + for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) { + if (typeof value == "string") + return value; } + return null; +} +function hasAttribute(attributeName, ...elements) { + return elements.some((element) => element && element.hasAttribute(attributeName)); +} +function markAsBusy(...elements) { + for (const element of elements) { + if (element.localName == "turbo-frame") { + element.setAttribute("busy", ""); + } + element.setAttribute("aria-busy", "true"); + } +} +function clearBusyState(...elements) { + for (const element of elements) { + if (element.localName == "turbo-frame") { + element.removeAttribute("busy"); + } + element.removeAttribute("aria-busy"); + } +} +function waitForLoad(element, timeoutInMilliseconds = 2000) { + return new Promise((resolve) => { + const onComplete = () => { + element.removeEventListener("error", onComplete); + element.removeEventListener("load", onComplete); + resolve(); + }; + element.addEventListener("load", onComplete, { once: true }); + element.addEventListener("error", onComplete, { once: true }); + setTimeout(resolve, timeoutInMilliseconds); + }); +} +function getHistoryMethodForAction(action) { + switch (action) { + case "replace": + return history.replaceState; + case "advance": + case "restore": + return history.pushState; + } +} +function isAction(action) { + return action == "advance" || action == "replace" || action == "restore"; +} +function getVisitAction(...elements) { + const action = getAttribute("data-turbo-action", ...elements); + return isAction(action) ? action : null; +} +function getMetaElement(name) { + return document.querySelector(`meta[name="${name}"]`); +} +function getMetaContent(name) { + const element = getMetaElement(name); + return element && element.content; +} +function setMetaContent(name, content) { + let element = getMetaElement(name); + if (!element) { + element = document.createElement("meta"); + element.setAttribute("name", name); + document.head.appendChild(element); + } + element.setAttribute("content", content); + return element; +} +function findClosestRecursively(element, selector) { + var _a; + if (element instanceof Element) { + return (element.closest(selector) || + findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector)); + } +} - class FetchResponse { - constructor(response) { - this.response = response; +var FetchMethod; +(function (FetchMethod) { + FetchMethod[FetchMethod["get"] = 0] = "get"; + FetchMethod[FetchMethod["post"] = 1] = "post"; + FetchMethod[FetchMethod["put"] = 2] = "put"; + FetchMethod[FetchMethod["patch"] = 3] = "patch"; + FetchMethod[FetchMethod["delete"] = 4] = "delete"; +})(FetchMethod || (FetchMethod = {})); +function fetchMethodFromString(method) { + switch (method.toLowerCase()) { + case "get": + return FetchMethod.get; + case "post": + return FetchMethod.post; + case "put": + return FetchMethod.put; + case "patch": + return FetchMethod.patch; + case "delete": + return FetchMethod.delete; + } +} +class FetchRequest { + constructor(delegate, method, location, body = new URLSearchParams(), target = null) { + this.abortController = new AbortController(); + this.resolveRequestPromise = (_value) => { }; + this.delegate = delegate; + this.method = method; + this.headers = this.defaultHeaders; + this.body = body; + this.url = location; + this.target = target; + } + get location() { + return this.url; + } + get params() { + return this.url.searchParams; + } + get entries() { + return this.body ? Array.from(this.body.entries()) : []; + } + cancel() { + this.abortController.abort(); + } + async perform() { + const { fetchOptions } = this; + this.delegate.prepareRequest(this); + await this.allowRequestToBeIntercepted(fetchOptions); + try { + this.delegate.requestStarted(this); + const response = await fetch(this.url.href, fetchOptions); + return await this.receive(response); + } + catch (error) { + if (error.name !== "AbortError") { + if (this.willDelegateErrorHandling(error)) { + this.delegate.requestErrored(this, error); + } + throw error; + } + } + finally { + this.delegate.requestFinished(this); + } + } + async receive(response) { + const fetchResponse = new FetchResponse(response); + const event = dispatch("turbo:before-fetch-response", { + cancelable: true, + detail: { fetchResponse }, + target: this.target, + }); + if (event.defaultPrevented) { + this.delegate.requestPreventedHandlingResponse(this, fetchResponse); } - get succeeded() { - return this.response.ok; + else if (fetchResponse.succeeded) { + this.delegate.requestSucceededWithResponse(this, fetchResponse); + } + else { + this.delegate.requestFailedWithResponse(this, fetchResponse); } - get failed() { - return !this.succeeded; + return fetchResponse; + } + get fetchOptions() { + var _a; + return { + method: FetchMethod[this.method].toUpperCase(), + credentials: "same-origin", + headers: this.headers, + redirect: "follow", + body: this.isSafe ? null : this.body, + signal: this.abortSignal, + referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href, + }; + } + get defaultHeaders() { + return { + Accept: "text/html, application/xhtml+xml", + }; + } + get isSafe() { + return this.method === FetchMethod.get; + } + get abortSignal() { + return this.abortController.signal; + } + acceptResponseType(mimeType) { + this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", "); + } + async allowRequestToBeIntercepted(fetchOptions) { + const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve)); + const event = dispatch("turbo:before-fetch-request", { + cancelable: true, + detail: { + fetchOptions, + url: this.url, + resume: this.resolveRequestPromise, + }, + target: this.target, + }); + if (event.defaultPrevented) + await requestInterception; + } + willDelegateErrorHandling(error) { + const event = dispatch("turbo:fetch-request-error", { + target: this.target, + cancelable: true, + detail: { request: this, error: error }, + }); + return !event.defaultPrevented; + } +} + +class AppearanceObserver { + constructor(delegate, element) { + this.started = false; + this.intersect = (entries) => { + const lastEntry = entries.slice(-1)[0]; + if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) { + this.delegate.elementAppearedInViewport(this.element); + } + }; + this.delegate = delegate; + this.element = element; + this.intersectionObserver = new IntersectionObserver(this.intersect); + } + start() { + if (!this.started) { + this.started = true; + this.intersectionObserver.observe(this.element); } - get clientError() { - return this.statusCode >= 400 && this.statusCode <= 499; + } + stop() { + if (this.started) { + this.started = false; + this.intersectionObserver.unobserve(this.element); } - get serverError() { - return this.statusCode >= 500 && this.statusCode <= 599; + } +} + +class StreamMessage { + static wrap(message) { + if (typeof message == "string") { + return new this(createDocumentFragment(message)); } - get redirected() { - return this.response.redirected; + else { + return message; } - get location() { - return expandURL(this.response.url); + } + constructor(fragment) { + this.fragment = importStreamElements(fragment); + } +} +StreamMessage.contentType = "text/vnd.turbo-stream.html"; +function importStreamElements(fragment) { + for (const element of fragment.querySelectorAll("turbo-stream")) { + const streamElement = document.importNode(element, true); + for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) { + inertScriptElement.replaceWith(activateScriptElement(inertScriptElement)); } - get isHTML() { - return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/); + element.replaceWith(streamElement); + } + return fragment; +} + +var FormSubmissionState; +(function (FormSubmissionState) { + FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized"; + FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting"; + FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting"; + FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving"; + FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping"; + FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped"; +})(FormSubmissionState || (FormSubmissionState = {})); +var FormEnctype; +(function (FormEnctype) { + FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded"; + FormEnctype["multipart"] = "multipart/form-data"; + FormEnctype["plain"] = "text/plain"; +})(FormEnctype || (FormEnctype = {})); +function formEnctypeFromString(encoding) { + switch (encoding.toLowerCase()) { + case FormEnctype.multipart: + return FormEnctype.multipart; + case FormEnctype.plain: + return FormEnctype.plain; + default: + return FormEnctype.urlEncoded; + } +} +class FormSubmission { + static confirmMethod(message, _element, _submitter) { + return Promise.resolve(confirm(message)); + } + constructor(delegate, formElement, submitter, mustRedirect = false) { + this.state = FormSubmissionState.initialized; + this.delegate = delegate; + this.formElement = formElement; + this.submitter = submitter; + this.formData = buildFormData(formElement, submitter); + this.location = expandURL(this.action); + if (this.method == FetchMethod.get) { + mergeFormDataEntries(this.location, [...this.body.entries()]); + } + this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement); + this.mustRedirect = mustRedirect; + } + get method() { + var _a; + const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || ""; + return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get; + } + get action() { + var _a; + const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null; + if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) { + return this.submitter.getAttribute("formaction") || ""; } - get statusCode() { - return this.response.status; + else { + return this.formElement.getAttribute("action") || formElementAction || ""; } - get contentType() { - return this.header("Content-Type"); + } + get body() { + if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) { + return new URLSearchParams(this.stringFormData); } - get responseText() { - return this.response.clone().text(); + else { + return this.formData; } - get responseHTML() { - if (this.isHTML) { - return this.response.clone().text(); - } - else { - return Promise.resolve(undefined); + } + get enctype() { + var _a; + return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype); + } + get isSafe() { + return this.fetchRequest.isSafe; + } + get stringFormData() { + return [...this.formData].reduce((entries, [name, value]) => { + return entries.concat(typeof value == "string" ? [[name, value]] : []); + }, []); + } + async start() { + const { initialized, requesting } = FormSubmissionState; + const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement); + if (typeof confirmationMessage === "string") { + const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter); + if (!answer) { + return; } } - header(name) { - return this.response.headers.get(name); + if (this.state == initialized) { + this.state = requesting; + return this.fetchRequest.perform(); } } - - function activateScriptElement(element) { - if (element.getAttribute("data-turbo-eval") == "false") { - return element; + stop() { + const { stopping, stopped } = FormSubmissionState; + if (this.state != stopping && this.state != stopped) { + this.state = stopping; + this.fetchRequest.cancel(); + return true; } - else { - const createdScriptElement = document.createElement("script"); - const cspNonce = getMetaContent("csp-nonce"); - if (cspNonce) { - createdScriptElement.nonce = cspNonce; + } + prepareRequest(request) { + if (!request.isSafe) { + const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token"); + if (token) { + request.headers["X-CSRF-Token"] = token; } - createdScriptElement.textContent = element.textContent; - createdScriptElement.async = false; - copyElementAttributes(createdScriptElement, element); - return createdScriptElement; } + if (this.requestAcceptsTurboStreamResponse(request)) { + request.acceptResponseType(StreamMessage.contentType); + } + } + requestStarted(_request) { + var _a; + this.state = FormSubmissionState.waiting; + (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", ""); + this.setSubmitsWith(); + dispatch("turbo:submit-start", { + target: this.formElement, + detail: { formSubmission: this }, + }); + this.delegate.formSubmissionStarted(this); + } + requestPreventedHandlingResponse(request, response) { + this.result = { success: response.succeeded, fetchResponse: response }; } - function copyElementAttributes(destinationElement, sourceElement) { - for (const { name, value } of sourceElement.attributes) { - destinationElement.setAttribute(name, value); + requestSucceededWithResponse(request, response) { + if (response.clientError || response.serverError) { + this.delegate.formSubmissionFailedWithResponse(this, response); + } + else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) { + const error = new Error("Form responses must redirect to another location"); + this.delegate.formSubmissionErrored(this, error); + } + else { + this.state = FormSubmissionState.receiving; + this.result = { success: true, fetchResponse: response }; + this.delegate.formSubmissionSucceededWithResponse(this, response); } } - function createDocumentFragment(html) { - const template = document.createElement("template"); - template.innerHTML = html; - return template.content; + requestFailedWithResponse(request, response) { + this.result = { success: false, fetchResponse: response }; + this.delegate.formSubmissionFailedWithResponse(this, response); } - function dispatch(eventName, { target, cancelable, detail } = {}) { - const event = new CustomEvent(eventName, { - cancelable, - bubbles: true, - composed: true, - detail, + requestErrored(request, error) { + this.result = { success: false, error }; + this.delegate.formSubmissionErrored(this, error); + } + requestFinished(_request) { + var _a; + this.state = FormSubmissionState.stopped; + (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled"); + this.resetSubmitterText(); + dispatch("turbo:submit-end", { + target: this.formElement, + detail: Object.assign({ formSubmission: this }, this.result), }); - if (target && target.isConnected) { - target.dispatchEvent(event); + this.delegate.formSubmissionFinished(this); + } + setSubmitsWith() { + if (!this.submitter || !this.submitsWith) + return; + if (this.submitter.matches("button")) { + this.originalSubmitText = this.submitter.innerHTML; + this.submitter.innerHTML = this.submitsWith; } - else { - document.documentElement.dispatchEvent(event); + else if (this.submitter.matches("input")) { + const input = this.submitter; + this.originalSubmitText = input.value; + input.value = this.submitsWith; + } + } + resetSubmitterText() { + if (!this.submitter || !this.originalSubmitText) + return; + if (this.submitter.matches("button")) { + this.submitter.innerHTML = this.originalSubmitText; } - return event; + else if (this.submitter.matches("input")) { + const input = this.submitter; + input.value = this.originalSubmitText; + } + } + requestMustRedirect(request) { + return !request.isSafe && this.mustRedirect; } - function nextAnimationFrame() { - return new Promise((resolve) => requestAnimationFrame(() => resolve())); + requestAcceptsTurboStreamResponse(request) { + return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement); } - function nextEventLoopTick() { - return new Promise((resolve) => setTimeout(() => resolve(), 0)); + get submitsWith() { + var _a; + return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with"); + } +} +function buildFormData(formElement, submitter) { + const formData = new FormData(formElement); + const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name"); + const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value"); + if (name) { + formData.append(name, value || ""); + } + return formData; +} +function getCookieValue(cookieName) { + if (cookieName != null) { + const cookies = document.cookie ? document.cookie.split("; ") : []; + const cookie = cookies.find((cookie) => cookie.startsWith(cookieName)); + if (cookie) { + const value = cookie.split("=").slice(1).join("="); + return value ? decodeURIComponent(value) : undefined; + } + } +} +function responseSucceededWithoutRedirect(response) { + return response.statusCode == 200 && !response.redirected; +} +function mergeFormDataEntries(url, entries) { + const searchParams = new URLSearchParams(); + for (const [name, value] of entries) { + if (value instanceof File) + continue; + searchParams.append(name, value); + } + url.search = searchParams.toString(); + return url; +} + +class Snapshot { + constructor(element) { + this.element = element; } - function nextMicrotask() { - return Promise.resolve(); + get activeElement() { + return this.element.ownerDocument.activeElement; } - function parseHTMLDocument(html = "") { - return new DOMParser().parseFromString(html, "text/html"); + get children() { + return [...this.element.children]; } - function unindent(strings, ...values) { - const lines = interpolate(strings, values).replace(/^\n/, "").split("\n"); - const match = lines[0].match(/^\s+/); - const indent = match ? match[0].length : 0; - return lines.map((line) => line.slice(indent)).join("\n"); + hasAnchor(anchor) { + return this.getElementForAnchor(anchor) != null; } - function interpolate(strings, values) { - return strings.reduce((result, string, i) => { - const value = values[i] == undefined ? "" : values[i]; - return result + string + value; - }, ""); + getElementForAnchor(anchor) { + return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null; } - function uuid() { - return Array.from({ length: 36 }) - .map((_, i) => { - if (i == 8 || i == 13 || i == 18 || i == 23) { - return "-"; - } - else if (i == 14) { - return "4"; - } - else if (i == 19) { - return (Math.floor(Math.random() * 4) + 8).toString(16); - } - else { - return Math.floor(Math.random() * 15).toString(16); - } - }) - .join(""); + get isConnected() { + return this.element.isConnected; } - function getAttribute(attributeName, ...elements) { - for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) { - if (typeof value == "string") - return value; + get firstAutofocusableElement() { + const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])"; + for (const element of this.element.querySelectorAll("[autofocus]")) { + if (element.closest(inertDisabledOrHidden) == null) + return element; + else + continue; } return null; } - function hasAttribute(attributeName, ...elements) { - return elements.some((element) => element && element.hasAttribute(attributeName)); + get permanentElements() { + return queryPermanentElementsAll(this.element); } - function markAsBusy(...elements) { - for (const element of elements) { - if (element.localName == "turbo-frame") { - element.setAttribute("busy", ""); + getPermanentElementById(id) { + return getPermanentElementById(this.element, id); + } + getPermanentElementMapForSnapshot(snapshot) { + const permanentElementMap = {}; + for (const currentPermanentElement of this.permanentElements) { + const { id } = currentPermanentElement; + const newPermanentElement = snapshot.getPermanentElementById(id); + if (newPermanentElement) { + permanentElementMap[id] = [currentPermanentElement, newPermanentElement]; } - element.setAttribute("aria-busy", "true"); } + return permanentElementMap; } - function clearBusyState(...elements) { - for (const element of elements) { - if (element.localName == "turbo-frame") { - element.removeAttribute("busy"); +} +function getPermanentElementById(node, id) { + return node.querySelector(`#${id}[data-turbo-permanent]`); +} +function queryPermanentElementsAll(node) { + return node.querySelectorAll("[id][data-turbo-permanent]"); +} + +class FormSubmitObserver { + constructor(delegate, eventTarget) { + this.started = false; + this.submitCaptured = () => { + this.eventTarget.removeEventListener("submit", this.submitBubbled, false); + this.eventTarget.addEventListener("submit", this.submitBubbled, false); + }; + this.submitBubbled = ((event) => { + if (!event.defaultPrevented) { + const form = event.target instanceof HTMLFormElement ? event.target : undefined; + const submitter = event.submitter || undefined; + if (form && + submissionDoesNotDismissDialog(form, submitter) && + submissionDoesNotTargetIFrame(form, submitter) && + this.delegate.willSubmitForm(form, submitter)) { + event.preventDefault(); + event.stopImmediatePropagation(); + this.delegate.formSubmitted(form, submitter); + } } - element.removeAttribute("aria-busy"); + }); + this.delegate = delegate; + this.eventTarget = eventTarget; + } + start() { + if (!this.started) { + this.eventTarget.addEventListener("submit", this.submitCaptured, true); + this.started = true; } } - function waitForLoad(element, timeoutInMilliseconds = 2000) { - return new Promise((resolve) => { - const onComplete = () => { - element.removeEventListener("error", onComplete); - element.removeEventListener("load", onComplete); - resolve(); - }; - element.addEventListener("load", onComplete, { once: true }); - element.addEventListener("error", onComplete, { once: true }); - setTimeout(resolve, timeoutInMilliseconds); - }); + stop() { + if (this.started) { + this.eventTarget.removeEventListener("submit", this.submitCaptured, true); + this.started = false; + } } - function getHistoryMethodForAction(action) { - switch (action) { - case "replace": - return history.replaceState; - case "advance": - case "restore": - return history.pushState; +} +function submissionDoesNotDismissDialog(form, submitter) { + const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method"); + return method != "dialog"; +} +function submissionDoesNotTargetIFrame(form, submitter) { + if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) { + const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target; + for (const element of document.getElementsByName(target)) { + if (element instanceof HTMLIFrameElement) + return false; } + return true; } - function isAction(action) { - return action == "advance" || action == "replace" || action == "restore"; + else { + return true; } - function getVisitAction(...elements) { - const action = getAttribute("data-turbo-action", ...elements); - return isAction(action) ? action : null; +} + +class View { + constructor(delegate, element) { + this.resolveRenderPromise = (_value) => { }; + this.resolveInterceptionPromise = (_value) => { }; + this.delegate = delegate; + this.element = element; + } + scrollToAnchor(anchor) { + const element = this.snapshot.getElementForAnchor(anchor); + if (element) { + this.scrollToElement(element); + this.focusElement(element); + } + else { + this.scrollToPosition({ x: 0, y: 0 }); + } } - function getMetaElement(name) { - return document.querySelector(`meta[name="${name}"]`); + scrollToAnchorFromLocation(location) { + this.scrollToAnchor(getAnchor(location)); } - function getMetaContent(name) { - const element = getMetaElement(name); - return element && element.content; + scrollToElement(element) { + element.scrollIntoView(); } - function setMetaContent(name, content) { - let element = getMetaElement(name); - if (!element) { - element = document.createElement("meta"); - element.setAttribute("name", name); - document.head.appendChild(element); + focusElement(element) { + if (element instanceof HTMLElement) { + if (element.hasAttribute("tabindex")) { + element.focus(); + } + else { + element.setAttribute("tabindex", "-1"); + element.focus(); + element.removeAttribute("tabindex"); + } } - element.setAttribute("content", content); - return element; } - function findClosestRecursively(element, selector) { - var _a; - if (element instanceof Element) { - return (element.closest(selector) || - findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector)); - } + scrollToPosition({ x, y }) { + this.scrollRoot.scrollTo(x, y); } - - var FetchMethod; - (function (FetchMethod) { - FetchMethod[FetchMethod["get"] = 0] = "get"; - FetchMethod[FetchMethod["post"] = 1] = "post"; - FetchMethod[FetchMethod["put"] = 2] = "put"; - FetchMethod[FetchMethod["patch"] = 3] = "patch"; - FetchMethod[FetchMethod["delete"] = 4] = "delete"; - })(FetchMethod || (FetchMethod = {})); - function fetchMethodFromString(method) { - switch (method.toLowerCase()) { - case "get": - return FetchMethod.get; - case "post": - return FetchMethod.post; - case "put": - return FetchMethod.put; - case "patch": - return FetchMethod.patch; - case "delete": - return FetchMethod.delete; - } - } - class FetchRequest { - constructor(delegate, method, location, body = new URLSearchParams(), target = null) { - this.abortController = new AbortController(); - this.resolveRequestPromise = (_value) => { }; - this.delegate = delegate; - this.method = method; - this.headers = this.defaultHeaders; - this.body = body; - this.url = location; - this.target = target; - } - get location() { - return this.url; - } - get params() { - return this.url.searchParams; - } - get entries() { - return this.body ? Array.from(this.body.entries()) : []; - } - cancel() { - this.abortController.abort(); - } - async perform() { - const { fetchOptions } = this; - this.delegate.prepareRequest(this); - await this.allowRequestToBeIntercepted(fetchOptions); + scrollToTop() { + this.scrollToPosition({ x: 0, y: 0 }); + } + get scrollRoot() { + return window; + } + async render(renderer) { + const { isPreview, shouldRender, newSnapshot: snapshot } = renderer; + if (shouldRender) { try { - this.delegate.requestStarted(this); - const response = await fetch(this.url.href, fetchOptions); - return await this.receive(response); - } - catch (error) { - if (error.name !== "AbortError") { - if (this.willDelegateErrorHandling(error)) { - this.delegate.requestErrored(this, error); - } - throw error; - } + this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve)); + this.renderer = renderer; + await this.prepareToRenderSnapshot(renderer); + const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve)); + const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement }; + const immediateRender = this.delegate.allowsImmediateRender(snapshot, options); + if (!immediateRender) + await renderInterception; + await this.renderSnapshot(renderer); + this.delegate.viewRenderedSnapshot(snapshot, isPreview); + this.delegate.preloadOnLoadLinksForView(this.element); + this.finishRenderingSnapshot(renderer); } finally { - this.delegate.requestFinished(this); - } - } - async receive(response) { - const fetchResponse = new FetchResponse(response); - const event = dispatch("turbo:before-fetch-response", { - cancelable: true, - detail: { fetchResponse }, - target: this.target, - }); - if (event.defaultPrevented) { - this.delegate.requestPreventedHandlingResponse(this, fetchResponse); - } - else if (fetchResponse.succeeded) { - this.delegate.requestSucceededWithResponse(this, fetchResponse); + delete this.renderer; + this.resolveRenderPromise(undefined); + delete this.renderPromise; } - else { - this.delegate.requestFailedWithResponse(this, fetchResponse); - } - return fetchResponse; } - get fetchOptions() { - var _a; - return { - method: FetchMethod[this.method].toUpperCase(), - credentials: "same-origin", - headers: this.headers, - redirect: "follow", - body: this.isSafe ? null : this.body, - signal: this.abortSignal, - referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href, - }; + else { + this.invalidate(renderer.reloadReason); } - get defaultHeaders() { - return { - Accept: "text/html, application/xhtml+xml", - }; + } + invalidate(reason) { + this.delegate.viewInvalidated(reason); + } + async prepareToRenderSnapshot(renderer) { + this.markAsPreview(renderer.isPreview); + await renderer.prepareToRender(); + } + markAsPreview(isPreview) { + if (isPreview) { + this.element.setAttribute("data-turbo-preview", ""); } - get isSafe() { - return this.method === FetchMethod.get; - } - get abortSignal() { - return this.abortController.signal; - } - acceptResponseType(mimeType) { - this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", "); - } - async allowRequestToBeIntercepted(fetchOptions) { - const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve)); - const event = dispatch("turbo:before-fetch-request", { - cancelable: true, - detail: { - fetchOptions, - url: this.url, - resume: this.resolveRequestPromise, - }, - target: this.target, - }); - if (event.defaultPrevented) - await requestInterception; - } - willDelegateErrorHandling(error) { - const event = dispatch("turbo:fetch-request-error", { - target: this.target, - cancelable: true, - detail: { request: this, error: error }, - }); - return !event.defaultPrevented; + else { + this.element.removeAttribute("data-turbo-preview"); } } + async renderSnapshot(renderer) { + await renderer.render(); + } + finishRenderingSnapshot(renderer) { + renderer.finishRendering(); + } +} - class AppearanceObserver { - constructor(delegate, element) { - this.started = false; - this.intersect = (entries) => { - const lastEntry = entries.slice(-1)[0]; - if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) { - this.delegate.elementAppearedInViewport(this.element); - } - }; - this.delegate = delegate; - this.element = element; - this.intersectionObserver = new IntersectionObserver(this.intersect); - } - start() { - if (!this.started) { - this.started = true; - this.intersectionObserver.observe(this.element); - } - } - stop() { - if (this.started) { - this.started = false; - this.intersectionObserver.unobserve(this.element); - } - } +class FrameView extends View { + missing() { + this.element.innerHTML = `Content missing`; + } + get snapshot() { + return new Snapshot(this.element); } +} - class StreamMessage { - static wrap(message) { - if (typeof message == "string") { - return new this(createDocumentFragment(message)); +class LinkInterceptor { + constructor(delegate, element) { + this.clickBubbled = (event) => { + if (this.respondsToEventTarget(event.target)) { + this.clickEvent = event; } else { - return message; + delete this.clickEvent; } - } - constructor(fragment) { - this.fragment = importStreamElements(fragment); - } - } - StreamMessage.contentType = "text/vnd.turbo-stream.html"; - function importStreamElements(fragment) { - for (const element of fragment.querySelectorAll("turbo-stream")) { - const streamElement = document.importNode(element, true); - for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) { - inertScriptElement.replaceWith(activateScriptElement(inertScriptElement)); + }; + this.linkClicked = ((event) => { + if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) { + if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) { + this.clickEvent.preventDefault(); + event.preventDefault(); + this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent); + } } - element.replaceWith(streamElement); - } - return fragment; + delete this.clickEvent; + }); + this.willVisit = ((_event) => { + delete this.clickEvent; + }); + this.delegate = delegate; + this.element = element; + } + start() { + this.element.addEventListener("click", this.clickBubbled); + document.addEventListener("turbo:click", this.linkClicked); + document.addEventListener("turbo:before-visit", this.willVisit); + } + stop() { + this.element.removeEventListener("click", this.clickBubbled); + document.removeEventListener("turbo:click", this.linkClicked); + document.removeEventListener("turbo:before-visit", this.willVisit); } + respondsToEventTarget(target) { + const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null; + return element && element.closest("turbo-frame, html") == this.element; + } +} - var FormSubmissionState; - (function (FormSubmissionState) { - FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized"; - FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting"; - FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting"; - FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving"; - FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping"; - FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped"; - })(FormSubmissionState || (FormSubmissionState = {})); - var FormEnctype; - (function (FormEnctype) { - FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded"; - FormEnctype["multipart"] = "multipart/form-data"; - FormEnctype["plain"] = "text/plain"; - })(FormEnctype || (FormEnctype = {})); - function formEnctypeFromString(encoding) { - switch (encoding.toLowerCase()) { - case FormEnctype.multipart: - return FormEnctype.multipart; - case FormEnctype.plain: - return FormEnctype.plain; - default: - return FormEnctype.urlEncoded; +class LinkClickObserver { + constructor(delegate, eventTarget) { + this.started = false; + this.clickCaptured = () => { + this.eventTarget.removeEventListener("click", this.clickBubbled, false); + this.eventTarget.addEventListener("click", this.clickBubbled, false); + }; + this.clickBubbled = (event) => { + if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) { + const target = (event.composedPath && event.composedPath()[0]) || event.target; + const link = this.findLinkFromClickTarget(target); + if (link && doesNotTargetIFrame(link)) { + const location = this.getLocationForLink(link); + if (this.delegate.willFollowLinkToLocation(link, location, event)) { + event.preventDefault(); + this.delegate.followedLinkToLocation(link, location); + } + } + } + }; + this.delegate = delegate; + this.eventTarget = eventTarget; + } + start() { + if (!this.started) { + this.eventTarget.addEventListener("click", this.clickCaptured, true); + this.started = true; } } - class FormSubmission { - static confirmMethod(message, _element, _submitter) { - return Promise.resolve(confirm(message)); + stop() { + if (this.started) { + this.eventTarget.removeEventListener("click", this.clickCaptured, true); + this.started = false; } - constructor(delegate, formElement, submitter, mustRedirect = false) { - this.state = FormSubmissionState.initialized; - this.delegate = delegate; - this.formElement = formElement; - this.submitter = submitter; - this.formData = buildFormData(formElement, submitter); - this.location = expandURL(this.action); - if (this.method == FetchMethod.get) { - mergeFormDataEntries(this.location, [...this.body.entries()]); - } - this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement); - this.mustRedirect = mustRedirect; - } - get method() { - var _a; - const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || ""; - return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get; - } - get action() { - var _a; - const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null; - if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) { - return this.submitter.getAttribute("formaction") || ""; - } - else { - return this.formElement.getAttribute("action") || formElementAction || ""; - } - } - get body() { - if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) { - return new URLSearchParams(this.stringFormData); - } - else { - return this.formData; - } + } + clickEventIsSignificant(event) { + return !((event.target && event.target.isContentEditable) || + event.defaultPrevented || + event.which > 1 || + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey); + } + findLinkFromClickTarget(target) { + return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])"); + } + getLocationForLink(link) { + return expandURL(link.getAttribute("href") || ""); + } +} +function doesNotTargetIFrame(anchor) { + if (anchor.hasAttribute("target")) { + for (const element of document.getElementsByName(anchor.target)) { + if (element instanceof HTMLIFrameElement) + return false; } - get enctype() { - var _a; - return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype); + return true; + } + else { + return true; + } +} + +class FormLinkClickObserver { + constructor(delegate, element) { + this.delegate = delegate; + this.linkInterceptor = new LinkClickObserver(this, element); + } + start() { + this.linkInterceptor.start(); + } + stop() { + this.linkInterceptor.stop(); + } + willFollowLinkToLocation(link, location, originalEvent) { + return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && + link.hasAttribute("data-turbo-method")); + } + followedLinkToLocation(link, location) { + const form = document.createElement("form"); + const type = "hidden"; + for (const [name, value] of location.searchParams) { + form.append(Object.assign(document.createElement("input"), { type, name, value })); + } + const action = Object.assign(location, { search: "" }); + form.setAttribute("data-turbo", "true"); + form.setAttribute("action", action.href); + form.setAttribute("hidden", ""); + const method = link.getAttribute("data-turbo-method"); + if (method) + form.setAttribute("method", method); + const turboFrame = link.getAttribute("data-turbo-frame"); + if (turboFrame) + form.setAttribute("data-turbo-frame", turboFrame); + const turboAction = getVisitAction(link); + if (turboAction) + form.setAttribute("data-turbo-action", turboAction); + const turboConfirm = link.getAttribute("data-turbo-confirm"); + if (turboConfirm) + form.setAttribute("data-turbo-confirm", turboConfirm); + const turboStream = link.hasAttribute("data-turbo-stream"); + if (turboStream) + form.setAttribute("data-turbo-stream", ""); + this.delegate.submittedFormLinkToLocation(link, location, form); + document.body.appendChild(form); + form.addEventListener("turbo:submit-end", () => form.remove(), { once: true }); + requestAnimationFrame(() => form.requestSubmit()); + } +} + +class Bardo { + static async preservingPermanentElements(delegate, permanentElementMap, callback) { + const bardo = new this(delegate, permanentElementMap); + bardo.enter(); + await callback(); + bardo.leave(); + } + constructor(delegate, permanentElementMap) { + this.delegate = delegate; + this.permanentElementMap = permanentElementMap; + } + enter() { + for (const id in this.permanentElementMap) { + const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id]; + this.delegate.enteringBardo(currentPermanentElement, newPermanentElement); + this.replaceNewPermanentElementWithPlaceholder(newPermanentElement); + } + } + leave() { + for (const id in this.permanentElementMap) { + const [currentPermanentElement] = this.permanentElementMap[id]; + this.replaceCurrentPermanentElementWithClone(currentPermanentElement); + this.replacePlaceholderWithPermanentElement(currentPermanentElement); + this.delegate.leavingBardo(currentPermanentElement); + } + } + replaceNewPermanentElementWithPlaceholder(permanentElement) { + const placeholder = createPlaceholderForPermanentElement(permanentElement); + permanentElement.replaceWith(placeholder); + } + replaceCurrentPermanentElementWithClone(permanentElement) { + const clone = permanentElement.cloneNode(true); + permanentElement.replaceWith(clone); + } + replacePlaceholderWithPermanentElement(permanentElement) { + const placeholder = this.getPlaceholderById(permanentElement.id); + placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement); + } + getPlaceholderById(id) { + return this.placeholders.find((element) => element.content == id); + } + get placeholders() { + return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]; + } +} +function createPlaceholderForPermanentElement(permanentElement) { + const element = document.createElement("meta"); + element.setAttribute("name", "turbo-permanent-placeholder"); + element.setAttribute("content", permanentElement.id); + return element; +} + +class Renderer { + constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) { + this.activeElement = null; + this.currentSnapshot = currentSnapshot; + this.newSnapshot = newSnapshot; + this.isPreview = isPreview; + this.willRender = willRender; + this.renderElement = renderElement; + this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject })); + } + get shouldRender() { + return true; + } + get reloadReason() { + return; + } + prepareToRender() { + return; + } + finishRendering() { + if (this.resolvingFunctions) { + this.resolvingFunctions.resolve(); + delete this.resolvingFunctions; } - get isSafe() { - return this.fetchRequest.isSafe; + } + async preservingPermanentElements(callback) { + await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback); + } + focusFirstAutofocusableElement() { + const element = this.connectedSnapshot.firstAutofocusableElement; + if (elementIsFocusable(element)) { + element.focus(); } - get stringFormData() { - return [...this.formData].reduce((entries, [name, value]) => { - return entries.concat(typeof value == "string" ? [[name, value]] : []); - }, []); + } + enteringBardo(currentPermanentElement) { + if (this.activeElement) + return; + if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) { + this.activeElement = this.currentSnapshot.activeElement; } - async start() { - const { initialized, requesting } = FormSubmissionState; - const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement); - if (typeof confirmationMessage === "string") { - const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter); - if (!answer) { - return; - } - } - if (this.state == initialized) { - this.state = requesting; - return this.fetchRequest.perform(); - } + } + leavingBardo(currentPermanentElement) { + if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) { + this.activeElement.focus(); + this.activeElement = null; } - stop() { - const { stopping, stopped } = FormSubmissionState; - if (this.state != stopping && this.state != stopped) { - this.state = stopping; - this.fetchRequest.cancel(); + } + get connectedSnapshot() { + return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot; + } + get currentElement() { + return this.currentSnapshot.element; + } + get newElement() { + return this.newSnapshot.element; + } + get permanentElementMap() { + return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot); + } +} +function elementIsFocusable(element) { + return element && typeof element.focus == "function"; +} + +class FrameRenderer extends Renderer { + static renderElement(currentElement, newElement) { + var _a; + const destinationRange = document.createRange(); + destinationRange.selectNodeContents(currentElement); + destinationRange.deleteContents(); + const frameElement = newElement; + const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange(); + if (sourceRange) { + sourceRange.selectNodeContents(frameElement); + currentElement.appendChild(sourceRange.extractContents()); + } + } + constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) { + super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender); + this.delegate = delegate; + } + get shouldRender() { + return true; + } + async render() { + await nextAnimationFrame(); + this.preservingPermanentElements(() => { + this.loadFrameElement(); + }); + this.scrollFrameIntoView(); + await nextAnimationFrame(); + this.focusFirstAutofocusableElement(); + await nextAnimationFrame(); + this.activateScriptElements(); + } + loadFrameElement() { + this.delegate.willRenderFrame(this.currentElement, this.newElement); + this.renderElement(this.currentElement, this.newElement); + } + scrollFrameIntoView() { + if (this.currentElement.autoscroll || this.newElement.autoscroll) { + const element = this.currentElement.firstElementChild; + const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end"); + const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto"); + if (element) { + element.scrollIntoView({ block, behavior }); return true; } } - prepareRequest(request) { - if (!request.isSafe) { - const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token"); - if (token) { - request.headers["X-CSRF-Token"] = token; - } - } - if (this.requestAcceptsTurboStreamResponse(request)) { - request.acceptResponseType(StreamMessage.contentType); - } - } - requestStarted(_request) { - var _a; - this.state = FormSubmissionState.waiting; - (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", ""); - this.setSubmitsWith(); - dispatch("turbo:submit-start", { - target: this.formElement, - detail: { formSubmission: this }, - }); - this.delegate.formSubmissionStarted(this); - } - requestPreventedHandlingResponse(request, response) { - this.result = { success: response.succeeded, fetchResponse: response }; - } - requestSucceededWithResponse(request, response) { - if (response.clientError || response.serverError) { - this.delegate.formSubmissionFailedWithResponse(this, response); - } - else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) { - const error = new Error("Form responses must redirect to another location"); - this.delegate.formSubmissionErrored(this, error); - } - else { - this.state = FormSubmissionState.receiving; - this.result = { success: true, fetchResponse: response }; - this.delegate.formSubmissionSucceededWithResponse(this, response); - } - } - requestFailedWithResponse(request, response) { - this.result = { success: false, fetchResponse: response }; - this.delegate.formSubmissionFailedWithResponse(this, response); - } - requestErrored(request, error) { - this.result = { success: false, error }; - this.delegate.formSubmissionErrored(this, error); - } - requestFinished(_request) { - var _a; - this.state = FormSubmissionState.stopped; - (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled"); - this.resetSubmitterText(); - dispatch("turbo:submit-end", { - target: this.formElement, - detail: Object.assign({ formSubmission: this }, this.result), - }); - this.delegate.formSubmissionFinished(this); - } - setSubmitsWith() { - if (!this.submitter || !this.submitsWith) - return; - if (this.submitter.matches("button")) { - this.originalSubmitText = this.submitter.innerHTML; - this.submitter.innerHTML = this.submitsWith; - } - else if (this.submitter.matches("input")) { - const input = this.submitter; - this.originalSubmitText = input.value; - input.value = this.submitsWith; - } - } - resetSubmitterText() { - if (!this.submitter || !this.originalSubmitText) - return; - if (this.submitter.matches("button")) { - this.submitter.innerHTML = this.originalSubmitText; - } - else if (this.submitter.matches("input")) { - const input = this.submitter; - input.value = this.originalSubmitText; - } - } - requestMustRedirect(request) { - return !request.isSafe && this.mustRedirect; - } - requestAcceptsTurboStreamResponse(request) { - return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement); - } - get submitsWith() { - var _a; - return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with"); - } + return false; } - function buildFormData(formElement, submitter) { - const formData = new FormData(formElement); - const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name"); - const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value"); - if (name) { - formData.append(name, value || ""); + activateScriptElements() { + for (const inertScriptElement of this.newScriptElements) { + const activatedScriptElement = activateScriptElement(inertScriptElement); + inertScriptElement.replaceWith(activatedScriptElement); } - return formData; } - function getCookieValue(cookieName) { - if (cookieName != null) { - const cookies = document.cookie ? document.cookie.split("; ") : []; - const cookie = cookies.find((cookie) => cookie.startsWith(cookieName)); - if (cookie) { - const value = cookie.split("=").slice(1).join("="); - return value ? decodeURIComponent(value) : undefined; - } - } + get newScriptElements() { + return this.currentElement.querySelectorAll("script"); } - function responseSucceededWithoutRedirect(response) { - return response.statusCode == 200 && !response.redirected; +} +function readScrollLogicalPosition(value, defaultValue) { + if (value == "end" || value == "start" || value == "center" || value == "nearest") { + return value; } - function mergeFormDataEntries(url, entries) { - const searchParams = new URLSearchParams(); - for (const [name, value] of entries) { - if (value instanceof File) - continue; - searchParams.append(name, value); - } - url.search = searchParams.toString(); - return url; + else { + return defaultValue; + } +} +function readScrollBehavior(value, defaultValue) { + if (value == "auto" || value == "smooth") { + return value; + } + else { + return defaultValue; } +} - class Snapshot { - constructor(element) { - this.element = element; - } - get activeElement() { - return this.element.ownerDocument.activeElement; - } - get children() { - return [...this.element.children]; - } - hasAnchor(anchor) { - return this.getElementForAnchor(anchor) != null; - } - getElementForAnchor(anchor) { - return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null; - } - get isConnected() { - return this.element.isConnected; - } - get firstAutofocusableElement() { - const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])"; - for (const element of this.element.querySelectorAll("[autofocus]")) { - if (element.closest(inertDisabledOrHidden) == null) - return element; - else - continue; - } - return null; +class ProgressBar { + static get defaultCSS() { + return unindent ` + .turbo-progress-bar { + position: fixed; + display: block; + top: 0; + left: 0; + height: 3px; + background: #0076ff; + z-index: 2147483647; + transition: + width ${ProgressBar.animationDuration}ms ease-out, + opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in; + transform: translate3d(0, 0, 0); + } + `; + } + constructor() { + this.hiding = false; + this.value = 0; + this.visible = false; + this.trickle = () => { + this.setValue(this.value + Math.random() / 100); + }; + this.stylesheetElement = this.createStylesheetElement(); + this.progressElement = this.createProgressElement(); + this.installStylesheetElement(); + this.setValue(0); + } + show() { + if (!this.visible) { + this.visible = true; + this.installProgressElement(); + this.startTrickling(); + } + } + hide() { + if (this.visible && !this.hiding) { + this.hiding = true; + this.fadeProgressElement(() => { + this.uninstallProgressElement(); + this.stopTrickling(); + this.visible = false; + this.hiding = false; + }); } - get permanentElements() { - return queryPermanentElementsAll(this.element); + } + setValue(value) { + this.value = value; + this.refresh(); + } + installStylesheetElement() { + document.head.insertBefore(this.stylesheetElement, document.head.firstChild); + } + installProgressElement() { + this.progressElement.style.width = "0"; + this.progressElement.style.opacity = "1"; + document.documentElement.insertBefore(this.progressElement, document.body); + this.refresh(); + } + fadeProgressElement(callback) { + this.progressElement.style.opacity = "0"; + setTimeout(callback, ProgressBar.animationDuration * 1.5); + } + uninstallProgressElement() { + if (this.progressElement.parentNode) { + document.documentElement.removeChild(this.progressElement); } - getPermanentElementById(id) { - return getPermanentElementById(this.element, id); + } + startTrickling() { + if (!this.trickleInterval) { + this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration); } - getPermanentElementMapForSnapshot(snapshot) { - const permanentElementMap = {}; - for (const currentPermanentElement of this.permanentElements) { - const { id } = currentPermanentElement; - const newPermanentElement = snapshot.getPermanentElementById(id); - if (newPermanentElement) { - permanentElementMap[id] = [currentPermanentElement, newPermanentElement]; - } - } - return permanentElementMap; + } + stopTrickling() { + window.clearInterval(this.trickleInterval); + delete this.trickleInterval; + } + refresh() { + requestAnimationFrame(() => { + this.progressElement.style.width = `${10 + this.value * 90}%`; + }); + } + createStylesheetElement() { + const element = document.createElement("style"); + element.type = "text/css"; + element.textContent = ProgressBar.defaultCSS; + if (this.cspNonce) { + element.nonce = this.cspNonce; } + return element; } - function getPermanentElementById(node, id) { - return node.querySelector(`#${id}[data-turbo-permanent]`); + createProgressElement() { + const element = document.createElement("div"); + element.className = "turbo-progress-bar"; + return element; } - function queryPermanentElementsAll(node) { - return node.querySelectorAll("[id][data-turbo-permanent]"); + get cspNonce() { + return getMetaContent("csp-nonce"); } +} +ProgressBar.animationDuration = 300; - class FormSubmitObserver { - constructor(delegate, eventTarget) { - this.started = false; - this.submitCaptured = () => { - this.eventTarget.removeEventListener("submit", this.submitBubbled, false); - this.eventTarget.addEventListener("submit", this.submitBubbled, false); - }; - this.submitBubbled = ((event) => { - if (!event.defaultPrevented) { - const form = event.target instanceof HTMLFormElement ? event.target : undefined; - const submitter = event.submitter || undefined; - if (form && - submissionDoesNotDismissDialog(form, submitter) && - submissionDoesNotTargetIFrame(form, submitter) && - this.delegate.willSubmitForm(form, submitter)) { - event.preventDefault(); - event.stopImmediatePropagation(); - this.delegate.formSubmitted(form, submitter); - } - } - }); - this.delegate = delegate; - this.eventTarget = eventTarget; - } - start() { - if (!this.started) { - this.eventTarget.addEventListener("submit", this.submitCaptured, true); - this.started = true; +class HeadSnapshot extends Snapshot { + constructor() { + super(...arguments); + this.detailsByOuterHTML = this.children + .filter((element) => !elementIsNoscript(element)) + .map((element) => elementWithoutNonce(element)) + .reduce((result, element) => { + const { outerHTML } = element; + const details = outerHTML in result + ? result[outerHTML] + : { + type: elementType(element), + tracked: elementIsTracked(element), + elements: [], + }; + return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) }); + }, {}); + } + get trackedElementSignature() { + return Object.keys(this.detailsByOuterHTML) + .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked) + .join(""); + } + getScriptElementsNotInSnapshot(snapshot) { + return this.getElementsMatchingTypeNotInSnapshot("script", snapshot); + } + getStylesheetElementsNotInSnapshot(snapshot) { + return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot); + } + getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) { + return Object.keys(this.detailsByOuterHTML) + .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML)) + .map((outerHTML) => this.detailsByOuterHTML[outerHTML]) + .filter(({ type }) => type == matchedType) + .map(({ elements: [element] }) => element); + } + get provisionalElements() { + return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { + const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML]; + if (type == null && !tracked) { + return [...result, ...elements]; } - } - stop() { - if (this.started) { - this.eventTarget.removeEventListener("submit", this.submitCaptured, true); - this.started = false; + else if (elements.length > 1) { + return [...result, ...elements.slice(1)]; } - } + else { + return result; + } + }, []); + } + getMetaValue(name) { + const element = this.findMetaElementByName(name); + return element ? element.getAttribute("content") : null; + } + findMetaElementByName(name) { + return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { + const { elements: [element], } = this.detailsByOuterHTML[outerHTML]; + return elementIsMetaElementWithName(element, name) ? element : result; + }, undefined); + } +} +function elementType(element) { + if (elementIsScript(element)) { + return "script"; + } + else if (elementIsStylesheet(element)) { + return "stylesheet"; + } +} +function elementIsTracked(element) { + return element.getAttribute("data-turbo-track") == "reload"; +} +function elementIsScript(element) { + const tagName = element.localName; + return tagName == "script"; +} +function elementIsNoscript(element) { + const tagName = element.localName; + return tagName == "noscript"; +} +function elementIsStylesheet(element) { + const tagName = element.localName; + return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet"); +} +function elementIsMetaElementWithName(element, name) { + const tagName = element.localName; + return tagName == "meta" && element.getAttribute("name") == name; +} +function elementWithoutNonce(element) { + if (element.hasAttribute("nonce")) { + element.setAttribute("nonce", ""); + } + return element; +} + +class PageSnapshot extends Snapshot { + static fromHTMLString(html = "") { + return this.fromDocument(parseHTMLDocument(html)); } - function submissionDoesNotDismissDialog(form, submitter) { - const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method"); - return method != "dialog"; + static fromElement(element) { + return this.fromDocument(element.ownerDocument); } - function submissionDoesNotTargetIFrame(form, submitter) { - if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) { - const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target; - for (const element of document.getElementsByName(target)) { - if (element instanceof HTMLIFrameElement) - return false; - } - return true; + static fromDocument({ head, body }) { + return new this(body, new HeadSnapshot(head)); + } + constructor(element, headSnapshot) { + super(element); + this.headSnapshot = headSnapshot; + } + clone() { + const clonedElement = this.element.cloneNode(true); + const selectElements = this.element.querySelectorAll("select"); + const clonedSelectElements = clonedElement.querySelectorAll("select"); + for (const [index, source] of selectElements.entries()) { + const clone = clonedSelectElements[index]; + for (const option of clone.selectedOptions) + option.selected = false; + for (const option of source.selectedOptions) + clone.options[option.index].selected = true; } - else { - return true; + for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) { + clonedPasswordInput.value = ""; } + return new PageSnapshot(clonedElement, this.headSnapshot); + } + get headElement() { + return this.headSnapshot.element; + } + get rootLocation() { + var _a; + const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/"; + return expandURL(root); + } + get cacheControlValue() { + return this.getSetting("cache-control"); + } + get isPreviewable() { + return this.cacheControlValue != "no-preview"; } + get isCacheable() { + return this.cacheControlValue != "no-cache"; + } + get isVisitable() { + return this.getSetting("visit-control") != "reload"; + } + getSetting(name) { + return this.headSnapshot.getMetaValue(`turbo-${name}`); + } +} - class View { - constructor(delegate, element) { - this.resolveRenderPromise = (_value) => { }; - this.resolveInterceptionPromise = (_value) => { }; - this.delegate = delegate; - this.element = element; - } - scrollToAnchor(anchor) { - const element = this.snapshot.getElementForAnchor(anchor); - if (element) { - this.scrollToElement(element); - this.focusElement(element); +var TimingMetric; +(function (TimingMetric) { + TimingMetric["visitStart"] = "visitStart"; + TimingMetric["requestStart"] = "requestStart"; + TimingMetric["requestEnd"] = "requestEnd"; + TimingMetric["visitEnd"] = "visitEnd"; +})(TimingMetric || (TimingMetric = {})); +var VisitState; +(function (VisitState) { + VisitState["initialized"] = "initialized"; + VisitState["started"] = "started"; + VisitState["canceled"] = "canceled"; + VisitState["failed"] = "failed"; + VisitState["completed"] = "completed"; +})(VisitState || (VisitState = {})); +const defaultOptions = { + action: "advance", + historyChanged: false, + visitCachedSnapshot: () => { }, + willRender: true, + updateHistory: true, + shouldCacheSnapshot: true, + acceptsStreamResponse: false, +}; +var SystemStatusCode; +(function (SystemStatusCode) { + SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure"; + SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure"; + SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch"; +})(SystemStatusCode || (SystemStatusCode = {})); +class Visit { + constructor(delegate, location, restorationIdentifier, options = {}) { + this.identifier = uuid(); + this.timingMetrics = {}; + this.followedRedirect = false; + this.historyChanged = false; + this.scrolled = false; + this.shouldCacheSnapshot = true; + this.acceptsStreamResponse = false; + this.snapshotCached = false; + this.state = VisitState.initialized; + this.delegate = delegate; + this.location = location; + this.restorationIdentifier = restorationIdentifier || uuid(); + const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options); + this.action = action; + this.historyChanged = historyChanged; + this.referrer = referrer; + this.snapshot = snapshot; + this.snapshotHTML = snapshotHTML; + this.response = response; + this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action); + this.visitCachedSnapshot = visitCachedSnapshot; + this.willRender = willRender; + this.updateHistory = updateHistory; + this.scrolled = !willRender; + this.shouldCacheSnapshot = shouldCacheSnapshot; + this.acceptsStreamResponse = acceptsStreamResponse; + } + get adapter() { + return this.delegate.adapter; + } + get view() { + return this.delegate.view; + } + get history() { + return this.delegate.history; + } + get restorationData() { + return this.history.getRestorationDataForIdentifier(this.restorationIdentifier); + } + get silent() { + return this.isSamePage; + } + start() { + if (this.state == VisitState.initialized) { + this.recordTimingMetric(TimingMetric.visitStart); + this.state = VisitState.started; + this.adapter.visitStarted(this); + this.delegate.visitStarted(this); + } + } + cancel() { + if (this.state == VisitState.started) { + if (this.request) { + this.request.cancel(); } - else { - this.scrollToPosition({ x: 0, y: 0 }); - } - } - scrollToAnchorFromLocation(location) { - this.scrollToAnchor(getAnchor(location)); - } - scrollToElement(element) { - element.scrollIntoView(); + this.cancelRender(); + this.state = VisitState.canceled; } - focusElement(element) { - if (element instanceof HTMLElement) { - if (element.hasAttribute("tabindex")) { - element.focus(); - } - else { - element.setAttribute("tabindex", "-1"); - element.focus(); - element.removeAttribute("tabindex"); - } + } + complete() { + if (this.state == VisitState.started) { + this.recordTimingMetric(TimingMetric.visitEnd); + this.state = VisitState.completed; + this.followRedirect(); + if (!this.followedRedirect) { + this.adapter.visitCompleted(this); + this.delegate.visitCompleted(this); } } - scrollToPosition({ x, y }) { - this.scrollRoot.scrollTo(x, y); + } + fail() { + if (this.state == VisitState.started) { + this.state = VisitState.failed; + this.adapter.visitFailed(this); } - scrollToTop() { - this.scrollToPosition({ x: 0, y: 0 }); + } + changeHistory() { + var _a; + if (!this.historyChanged && this.updateHistory) { + const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action; + const method = getHistoryMethodForAction(actionForHistory); + this.history.update(method, this.location, this.restorationIdentifier); + this.historyChanged = true; } - get scrollRoot() { - return window; - } - async render(renderer) { - const { isPreview, shouldRender, newSnapshot: snapshot } = renderer; - if (shouldRender) { - try { - this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve)); - this.renderer = renderer; - await this.prepareToRenderSnapshot(renderer); - const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve)); - const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement }; - const immediateRender = this.delegate.allowsImmediateRender(snapshot, options); - if (!immediateRender) - await renderInterception; - await this.renderSnapshot(renderer); - this.delegate.viewRenderedSnapshot(snapshot, isPreview); - this.delegate.preloadOnLoadLinksForView(this.element); - this.finishRenderingSnapshot(renderer); - } - finally { - delete this.renderer; - this.resolveRenderPromise(undefined); - delete this.renderPromise; - } - } - else { - this.invalidate(renderer.reloadReason); - } + } + issueRequest() { + if (this.hasPreloadedResponse()) { + this.simulateRequest(); } - invalidate(reason) { - this.delegate.viewInvalidated(reason); + else if (this.shouldIssueRequest() && !this.request) { + this.request = new FetchRequest(this, FetchMethod.get, this.location); + this.request.perform(); } - async prepareToRenderSnapshot(renderer) { - this.markAsPreview(renderer.isPreview); - await renderer.prepareToRender(); + } + simulateRequest() { + if (this.response) { + this.startRequest(); + this.recordResponse(); + this.finishRequest(); } - markAsPreview(isPreview) { - if (isPreview) { - this.element.setAttribute("data-turbo-preview", ""); + } + startRequest() { + this.recordTimingMetric(TimingMetric.requestStart); + this.adapter.visitRequestStarted(this); + } + recordResponse(response = this.response) { + this.response = response; + if (response) { + const { statusCode } = response; + if (isSuccessful(statusCode)) { + this.adapter.visitRequestCompleted(this); } else { - this.element.removeAttribute("data-turbo-preview"); + this.adapter.visitRequestFailedWithStatusCode(this, statusCode); } } - async renderSnapshot(renderer) { - await renderer.render(); - } - finishRenderingSnapshot(renderer) { - renderer.finishRendering(); + } + finishRequest() { + this.recordTimingMetric(TimingMetric.requestEnd); + this.adapter.visitRequestFinished(this); + } + loadResponse() { + if (this.response) { + const { statusCode, responseHTML } = this.response; + this.render(async () => { + if (this.shouldCacheSnapshot) + this.cacheSnapshot(); + if (this.view.renderPromise) + await this.view.renderPromise; + if (isSuccessful(statusCode) && responseHTML != null) { + await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this); + this.performScroll(); + this.adapter.visitRendered(this); + this.complete(); + } + else { + await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this); + this.adapter.visitRendered(this); + this.fail(); + } + }); } } - - class FrameView extends View { - missing() { - this.element.innerHTML = `Content missing`; + getCachedSnapshot() { + const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot(); + if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) { + if (this.action == "restore" || snapshot.isPreviewable) { + return snapshot; + } } - get snapshot() { - return new Snapshot(this.element); + } + getPreloadedSnapshot() { + if (this.snapshotHTML) { + return PageSnapshot.fromHTMLString(this.snapshotHTML); } } - - class LinkInterceptor { - constructor(delegate, element) { - this.clickBubbled = (event) => { - if (this.respondsToEventTarget(event.target)) { - this.clickEvent = event; + hasCachedSnapshot() { + return this.getCachedSnapshot() != null; + } + loadCachedSnapshot() { + const snapshot = this.getCachedSnapshot(); + if (snapshot) { + const isPreview = this.shouldIssueRequest(); + this.render(async () => { + this.cacheSnapshot(); + if (this.isSamePage) { + this.adapter.visitRendered(this); } else { - delete this.clickEvent; - } - }; - this.linkClicked = ((event) => { - if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) { - if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) { - this.clickEvent.preventDefault(); - event.preventDefault(); - this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent); + if (this.view.renderPromise) + await this.view.renderPromise; + await this.view.renderPage(snapshot, isPreview, this.willRender, this); + this.performScroll(); + this.adapter.visitRendered(this); + if (!isPreview) { + this.complete(); } } - delete this.clickEvent; - }); - this.willVisit = ((_event) => { - delete this.clickEvent; }); - this.delegate = delegate; - this.element = element; } - start() { - this.element.addEventListener("click", this.clickBubbled); - document.addEventListener("turbo:click", this.linkClicked); - document.addEventListener("turbo:before-visit", this.willVisit); - } - stop() { - this.element.removeEventListener("click", this.clickBubbled); - document.removeEventListener("turbo:click", this.linkClicked); - document.removeEventListener("turbo:before-visit", this.willVisit); - } - respondsToEventTarget(target) { - const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null; - return element && element.closest("turbo-frame, html") == this.element; + } + followRedirect() { + var _a; + if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) { + this.adapter.visitProposedToLocation(this.redirectedToLocation, { + action: "replace", + response: this.response, + shouldCacheSnapshot: false, + willRender: false, + }); + this.followedRedirect = true; } } - - class LinkClickObserver { - constructor(delegate, eventTarget) { - this.started = false; - this.clickCaptured = () => { - this.eventTarget.removeEventListener("click", this.clickBubbled, false); - this.eventTarget.addEventListener("click", this.clickBubbled, false); - }; - this.clickBubbled = (event) => { - if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) { - const target = (event.composedPath && event.composedPath()[0]) || event.target; - const link = this.findLinkFromClickTarget(target); - if (link && doesNotTargetIFrame(link)) { - const location = this.getLocationForLink(link); - if (this.delegate.willFollowLinkToLocation(link, location, event)) { - event.preventDefault(); - this.delegate.followedLinkToLocation(link, location); - } - } - } - }; - this.delegate = delegate; - this.eventTarget = eventTarget; + goToSamePageAnchor() { + if (this.isSamePage) { + this.render(async () => { + this.cacheSnapshot(); + this.performScroll(); + this.changeHistory(); + this.adapter.visitRendered(this); + }); } - start() { - if (!this.started) { - this.eventTarget.addEventListener("click", this.clickCaptured, true); - this.started = true; - } + } + prepareRequest(request) { + if (this.acceptsStreamResponse) { + request.acceptResponseType(StreamMessage.contentType); } - stop() { - if (this.started) { - this.eventTarget.removeEventListener("click", this.clickCaptured, true); - this.started = false; - } + } + requestStarted() { + this.startRequest(); + } + requestPreventedHandlingResponse(_request, _response) { } + async requestSucceededWithResponse(request, response) { + const responseHTML = await response.responseHTML; + const { redirected, statusCode } = response; + if (responseHTML == undefined) { + this.recordResponse({ + statusCode: SystemStatusCode.contentTypeMismatch, + redirected, + }); } - clickEventIsSignificant(event) { - return !((event.target && event.target.isContentEditable) || - event.defaultPrevented || - event.which > 1 || - event.altKey || - event.ctrlKey || - event.metaKey || - event.shiftKey); + else { + this.redirectedToLocation = response.redirected ? response.location : undefined; + this.recordResponse({ statusCode: statusCode, responseHTML, redirected }); } - findLinkFromClickTarget(target) { - return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])"); + } + async requestFailedWithResponse(request, response) { + const responseHTML = await response.responseHTML; + const { redirected, statusCode } = response; + if (responseHTML == undefined) { + this.recordResponse({ + statusCode: SystemStatusCode.contentTypeMismatch, + redirected, + }); } - getLocationForLink(link) { - return expandURL(link.getAttribute("href") || ""); + else { + this.recordResponse({ statusCode: statusCode, responseHTML, redirected }); } } - function doesNotTargetIFrame(anchor) { - if (anchor.hasAttribute("target")) { - for (const element of document.getElementsByName(anchor.target)) { - if (element instanceof HTMLIFrameElement) - return false; + requestErrored(_request, _error) { + this.recordResponse({ + statusCode: SystemStatusCode.networkFailure, + redirected: false, + }); + } + requestFinished() { + this.finishRequest(); + } + performScroll() { + if (!this.scrolled && !this.view.forceReloaded) { + if (this.action == "restore") { + this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop(); } + else { + this.scrollToAnchor() || this.view.scrollToTop(); + } + if (this.isSamePage) { + this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location); + } + this.scrolled = true; + } + } + scrollToRestoredPosition() { + const { scrollPosition } = this.restorationData; + if (scrollPosition) { + this.view.scrollToPosition(scrollPosition); return true; } - else { + } + scrollToAnchor() { + const anchor = getAnchor(this.location); + if (anchor != null) { + this.view.scrollToAnchor(anchor); return true; } } - - class FormLinkClickObserver { - constructor(delegate, element) { - this.delegate = delegate; - this.linkInterceptor = new LinkClickObserver(this, element); + recordTimingMetric(metric) { + this.timingMetrics[metric] = new Date().getTime(); + } + getTimingMetrics() { + return Object.assign({}, this.timingMetrics); + } + getHistoryMethodForAction(action) { + switch (action) { + case "replace": + return history.replaceState; + case "advance": + case "restore": + return history.pushState; } - start() { - this.linkInterceptor.start(); + } + hasPreloadedResponse() { + return typeof this.response == "object"; + } + shouldIssueRequest() { + if (this.isSamePage) { + return false; } - stop() { - this.linkInterceptor.stop(); + else if (this.action == "restore") { + return !this.hasCachedSnapshot(); } - willFollowLinkToLocation(link, location, originalEvent) { - return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && - link.hasAttribute("data-turbo-method")); - } - followedLinkToLocation(link, location) { - const form = document.createElement("form"); - const type = "hidden"; - for (const [name, value] of location.searchParams) { - form.append(Object.assign(document.createElement("input"), { type, name, value })); - } - const action = Object.assign(location, { search: "" }); - form.setAttribute("data-turbo", "true"); - form.setAttribute("action", action.href); - form.setAttribute("hidden", ""); - const method = link.getAttribute("data-turbo-method"); - if (method) - form.setAttribute("method", method); - const turboFrame = link.getAttribute("data-turbo-frame"); - if (turboFrame) - form.setAttribute("data-turbo-frame", turboFrame); - const turboAction = getVisitAction(link); - if (turboAction) - form.setAttribute("data-turbo-action", turboAction); - const turboConfirm = link.getAttribute("data-turbo-confirm"); - if (turboConfirm) - form.setAttribute("data-turbo-confirm", turboConfirm); - const turboStream = link.hasAttribute("data-turbo-stream"); - if (turboStream) - form.setAttribute("data-turbo-stream", ""); - this.delegate.submittedFormLinkToLocation(link, location, form); - document.body.appendChild(form); - form.addEventListener("turbo:submit-end", () => form.remove(), { once: true }); - requestAnimationFrame(() => form.requestSubmit()); + else { + return this.willRender; } } - - class Bardo { - static async preservingPermanentElements(delegate, permanentElementMap, callback) { - const bardo = new this(delegate, permanentElementMap); - bardo.enter(); - await callback(); - bardo.leave(); - } - constructor(delegate, permanentElementMap) { - this.delegate = delegate; - this.permanentElementMap = permanentElementMap; - } - enter() { - for (const id in this.permanentElementMap) { - const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id]; - this.delegate.enteringBardo(currentPermanentElement, newPermanentElement); - this.replaceNewPermanentElementWithPlaceholder(newPermanentElement); - } - } - leave() { - for (const id in this.permanentElementMap) { - const [currentPermanentElement] = this.permanentElementMap[id]; - this.replaceCurrentPermanentElementWithClone(currentPermanentElement); - this.replacePlaceholderWithPermanentElement(currentPermanentElement); - this.delegate.leavingBardo(currentPermanentElement); - } - } - replaceNewPermanentElementWithPlaceholder(permanentElement) { - const placeholder = createPlaceholderForPermanentElement(permanentElement); - permanentElement.replaceWith(placeholder); - } - replaceCurrentPermanentElementWithClone(permanentElement) { - const clone = permanentElement.cloneNode(true); - permanentElement.replaceWith(clone); - } - replacePlaceholderWithPermanentElement(permanentElement) { - const placeholder = this.getPlaceholderById(permanentElement.id); - placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement); - } - getPlaceholderById(id) { - return this.placeholders.find((element) => element.content == id); - } - get placeholders() { - return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]; + cacheSnapshot() { + if (!this.snapshotCached) { + this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot)); + this.snapshotCached = true; } } - function createPlaceholderForPermanentElement(permanentElement) { - const element = document.createElement("meta"); - element.setAttribute("name", "turbo-permanent-placeholder"); - element.setAttribute("content", permanentElement.id); - return element; + async render(callback) { + this.cancelRender(); + await new Promise((resolve) => { + this.frame = requestAnimationFrame(() => resolve()); + }); + await callback(); + delete this.frame; } - - class Renderer { - constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) { - this.activeElement = null; - this.currentSnapshot = currentSnapshot; - this.newSnapshot = newSnapshot; - this.isPreview = isPreview; - this.willRender = willRender; - this.renderElement = renderElement; - this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject })); - } - get shouldRender() { - return true; - } - get reloadReason() { - return; - } - prepareToRender() { - return; - } - finishRendering() { - if (this.resolvingFunctions) { - this.resolvingFunctions.resolve(); - delete this.resolvingFunctions; - } - } - async preservingPermanentElements(callback) { - await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback); - } - focusFirstAutofocusableElement() { - const element = this.connectedSnapshot.firstAutofocusableElement; - if (elementIsFocusable(element)) { - element.focus(); - } - } - enteringBardo(currentPermanentElement) { - if (this.activeElement) - return; - if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) { - this.activeElement = this.currentSnapshot.activeElement; - } - } - leavingBardo(currentPermanentElement) { - if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) { - this.activeElement.focus(); - this.activeElement = null; - } - } - get connectedSnapshot() { - return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot; - } - get currentElement() { - return this.currentSnapshot.element; - } - get newElement() { - return this.newSnapshot.element; - } - get permanentElementMap() { - return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot); + cancelRender() { + if (this.frame) { + cancelAnimationFrame(this.frame); + delete this.frame; } } - function elementIsFocusable(element) { - return element && typeof element.focus == "function"; - } +} +function isSuccessful(statusCode) { + return statusCode >= 200 && statusCode < 300; +} - class FrameRenderer extends Renderer { - static renderElement(currentElement, newElement) { - var _a; - const destinationRange = document.createRange(); - destinationRange.selectNodeContents(currentElement); - destinationRange.deleteContents(); - const frameElement = newElement; - const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange(); - if (sourceRange) { - sourceRange.selectNodeContents(frameElement); - currentElement.appendChild(sourceRange.extractContents()); - } - } - constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) { - super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender); - this.delegate = delegate; - } - get shouldRender() { - return true; - } - async render() { - await nextAnimationFrame(); - this.preservingPermanentElements(() => { - this.loadFrameElement(); - }); - this.scrollFrameIntoView(); - await nextAnimationFrame(); - this.focusFirstAutofocusableElement(); - await nextAnimationFrame(); - this.activateScriptElements(); - } - loadFrameElement() { - this.delegate.willRenderFrame(this.currentElement, this.newElement); - this.renderElement(this.currentElement, this.newElement); - } - scrollFrameIntoView() { - if (this.currentElement.autoscroll || this.newElement.autoscroll) { - const element = this.currentElement.firstElementChild; - const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end"); - const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto"); - if (element) { - element.scrollIntoView({ block, behavior }); - return true; - } - } - return false; - } - activateScriptElements() { - for (const inertScriptElement of this.newScriptElements) { - const activatedScriptElement = activateScriptElement(inertScriptElement); - inertScriptElement.replaceWith(activatedScriptElement); - } - } - get newScriptElements() { - return this.currentElement.querySelectorAll("script"); - } +class BrowserAdapter { + constructor(session) { + this.progressBar = new ProgressBar(); + this.showProgressBar = () => { + this.progressBar.show(); + }; + this.session = session; } - function readScrollLogicalPosition(value, defaultValue) { - if (value == "end" || value == "start" || value == "center" || value == "nearest") { - return value; - } - else { - return defaultValue; - } + visitProposedToLocation(location, options) { + this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options); } - function readScrollBehavior(value, defaultValue) { - if (value == "auto" || value == "smooth") { - return value; - } - else { - return defaultValue; - } + visitStarted(visit) { + this.location = visit.location; + visit.loadCachedSnapshot(); + visit.issueRequest(); + visit.goToSamePageAnchor(); } - - class ProgressBar { - static get defaultCSS() { - return unindent ` - .turbo-progress-bar { - position: fixed; - display: block; - top: 0; - left: 0; - height: 3px; - background: #0076ff; - z-index: 2147483647; - transition: - width ${ProgressBar.animationDuration}ms ease-out, - opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in; - transform: translate3d(0, 0, 0); - } - `; + visitRequestStarted(visit) { + this.progressBar.setValue(0); + if (visit.hasCachedSnapshot() || visit.action != "restore") { + this.showVisitProgressBarAfterDelay(); } - constructor() { - this.hiding = false; - this.value = 0; - this.visible = false; - this.trickle = () => { - this.setValue(this.value + Math.random() / 100); - }; - this.stylesheetElement = this.createStylesheetElement(); - this.progressElement = this.createProgressElement(); - this.installStylesheetElement(); - this.setValue(0); - } - show() { - if (!this.visible) { - this.visible = true; - this.installProgressElement(); - this.startTrickling(); - } - } - hide() { - if (this.visible && !this.hiding) { - this.hiding = true; - this.fadeProgressElement(() => { - this.uninstallProgressElement(); - this.stopTrickling(); - this.visible = false; - this.hiding = false; + else { + this.showProgressBar(); + } + } + visitRequestCompleted(visit) { + visit.loadResponse(); + } + visitRequestFailedWithStatusCode(visit, statusCode) { + switch (statusCode) { + case SystemStatusCode.networkFailure: + case SystemStatusCode.timeoutFailure: + case SystemStatusCode.contentTypeMismatch: + return this.reload({ + reason: "request_failed", + context: { + statusCode, + }, }); - } - } - setValue(value) { - this.value = value; - this.refresh(); - } - installStylesheetElement() { - document.head.insertBefore(this.stylesheetElement, document.head.firstChild); - } - installProgressElement() { - this.progressElement.style.width = "0"; - this.progressElement.style.opacity = "1"; - document.documentElement.insertBefore(this.progressElement, document.body); - this.refresh(); - } - fadeProgressElement(callback) { - this.progressElement.style.opacity = "0"; - setTimeout(callback, ProgressBar.animationDuration * 1.5); - } - uninstallProgressElement() { - if (this.progressElement.parentNode) { - document.documentElement.removeChild(this.progressElement); - } - } - startTrickling() { - if (!this.trickleInterval) { - this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration); - } - } - stopTrickling() { - window.clearInterval(this.trickleInterval); - delete this.trickleInterval; - } - refresh() { - requestAnimationFrame(() => { - this.progressElement.style.width = `${10 + this.value * 90}%`; - }); - } - createStylesheetElement() { - const element = document.createElement("style"); - element.type = "text/css"; - element.textContent = ProgressBar.defaultCSS; - if (this.cspNonce) { - element.nonce = this.cspNonce; - } - return element; - } - createProgressElement() { - const element = document.createElement("div"); - element.className = "turbo-progress-bar"; - return element; - } - get cspNonce() { - return getMetaContent("csp-nonce"); + default: + return visit.loadResponse(); } } - ProgressBar.animationDuration = 300; - - class HeadSnapshot extends Snapshot { - constructor() { - super(...arguments); - this.detailsByOuterHTML = this.children - .filter((element) => !elementIsNoscript(element)) - .map((element) => elementWithoutNonce(element)) - .reduce((result, element) => { - const { outerHTML } = element; - const details = outerHTML in result - ? result[outerHTML] - : { - type: elementType(element), - tracked: elementIsTracked(element), - elements: [], - }; - return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) }); - }, {}); - } - get trackedElementSignature() { - return Object.keys(this.detailsByOuterHTML) - .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked) - .join(""); - } - getScriptElementsNotInSnapshot(snapshot) { - return this.getElementsMatchingTypeNotInSnapshot("script", snapshot); - } - getStylesheetElementsNotInSnapshot(snapshot) { - return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot); - } - getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) { - return Object.keys(this.detailsByOuterHTML) - .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML)) - .map((outerHTML) => this.detailsByOuterHTML[outerHTML]) - .filter(({ type }) => type == matchedType) - .map(({ elements: [element] }) => element); - } - get provisionalElements() { - return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { - const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML]; - if (type == null && !tracked) { - return [...result, ...elements]; - } - else if (elements.length > 1) { - return [...result, ...elements.slice(1)]; - } - else { - return result; - } - }, []); - } - getMetaValue(name) { - const element = this.findMetaElementByName(name); - return element ? element.getAttribute("content") : null; - } - findMetaElementByName(name) { - return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { - const { elements: [element], } = this.detailsByOuterHTML[outerHTML]; - return elementIsMetaElementWithName(element, name) ? element : result; - }, undefined); - } + visitRequestFinished(_visit) { + this.progressBar.setValue(1); + this.hideVisitProgressBar(); } - function elementType(element) { - if (elementIsScript(element)) { - return "script"; - } - else if (elementIsStylesheet(element)) { - return "stylesheet"; - } + visitCompleted(_visit) { } + pageInvalidated(reason) { + this.reload(reason); } - function elementIsTracked(element) { - return element.getAttribute("data-turbo-track") == "reload"; + visitFailed(_visit) { } + visitRendered(_visit) { } + formSubmissionStarted(_formSubmission) { + this.progressBar.setValue(0); + this.showFormProgressBarAfterDelay(); } - function elementIsScript(element) { - const tagName = element.localName; - return tagName == "script"; + formSubmissionFinished(_formSubmission) { + this.progressBar.setValue(1); + this.hideFormProgressBar(); } - function elementIsNoscript(element) { - const tagName = element.localName; - return tagName == "noscript"; + showVisitProgressBarAfterDelay() { + this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); } - function elementIsStylesheet(element) { - const tagName = element.localName; - return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet"); + hideVisitProgressBar() { + this.progressBar.hide(); + if (this.visitProgressBarTimeout != null) { + window.clearTimeout(this.visitProgressBarTimeout); + delete this.visitProgressBarTimeout; + } } - function elementIsMetaElementWithName(element, name) { - const tagName = element.localName; - return tagName == "meta" && element.getAttribute("name") == name; + showFormProgressBarAfterDelay() { + if (this.formProgressBarTimeout == null) { + this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); + } } - function elementWithoutNonce(element) { - if (element.hasAttribute("nonce")) { - element.setAttribute("nonce", ""); + hideFormProgressBar() { + this.progressBar.hide(); + if (this.formProgressBarTimeout != null) { + window.clearTimeout(this.formProgressBarTimeout); + delete this.formProgressBarTimeout; } - return element; } + reload(reason) { + var _a; + dispatch("turbo:reload", { detail: reason }); + window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href; + } + get navigator() { + return this.session.navigator; + } +} - class PageSnapshot extends Snapshot { - static fromHTMLString(html = "") { - return this.fromDocument(parseHTMLDocument(html)); - } - static fromElement(element) { - return this.fromDocument(element.ownerDocument); - } - static fromDocument({ head, body }) { - return new this(body, new HeadSnapshot(head)); - } - constructor(element, headSnapshot) { - super(element); - this.headSnapshot = headSnapshot; - } - clone() { - const clonedElement = this.element.cloneNode(true); - const selectElements = this.element.querySelectorAll("select"); - const clonedSelectElements = clonedElement.querySelectorAll("select"); - for (const [index, source] of selectElements.entries()) { - const clone = clonedSelectElements[index]; - for (const option of clone.selectedOptions) - option.selected = false; - for (const option of source.selectedOptions) - clone.options[option.index].selected = true; +class CacheObserver { + constructor() { + this.selector = "[data-turbo-temporary]"; + this.deprecatedSelector = "[data-turbo-cache=false]"; + this.started = false; + this.removeTemporaryElements = ((_event) => { + for (const element of this.temporaryElements) { + element.remove(); } - for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) { - clonedPasswordInput.value = ""; - } - return new PageSnapshot(clonedElement, this.headSnapshot); - } - get headElement() { - return this.headSnapshot.element; - } - get rootLocation() { - var _a; - const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/"; - return expandURL(root); - } - get cacheControlValue() { - return this.getSetting("cache-control"); - } - get isPreviewable() { - return this.cacheControlValue != "no-preview"; - } - get isCacheable() { - return this.cacheControlValue != "no-cache"; + }); + } + start() { + if (!this.started) { + this.started = true; + addEventListener("turbo:before-cache", this.removeTemporaryElements, false); } - get isVisitable() { - return this.getSetting("visit-control") != "reload"; + } + stop() { + if (this.started) { + this.started = false; + removeEventListener("turbo:before-cache", this.removeTemporaryElements, false); } - getSetting(name) { - return this.headSnapshot.getMetaValue(`turbo-${name}`); + } + get temporaryElements() { + return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation]; + } + get temporaryElementsWithDeprecation() { + const elements = document.querySelectorAll(this.deprecatedSelector); + if (elements.length) { + console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`); } + return [...elements]; } +} - var TimingMetric; - (function (TimingMetric) { - TimingMetric["visitStart"] = "visitStart"; - TimingMetric["requestStart"] = "requestStart"; - TimingMetric["requestEnd"] = "requestEnd"; - TimingMetric["visitEnd"] = "visitEnd"; - })(TimingMetric || (TimingMetric = {})); - var VisitState; - (function (VisitState) { - VisitState["initialized"] = "initialized"; - VisitState["started"] = "started"; - VisitState["canceled"] = "canceled"; - VisitState["failed"] = "failed"; - VisitState["completed"] = "completed"; - })(VisitState || (VisitState = {})); - const defaultOptions = { - action: "advance", - historyChanged: false, - visitCachedSnapshot: () => { }, - willRender: true, - updateHistory: true, - shouldCacheSnapshot: true, - acceptsStreamResponse: false, - }; - var SystemStatusCode; - (function (SystemStatusCode) { - SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure"; - SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure"; - SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch"; - })(SystemStatusCode || (SystemStatusCode = {})); - class Visit { - constructor(delegate, location, restorationIdentifier, options = {}) { - this.identifier = uuid(); - this.timingMetrics = {}; - this.followedRedirect = false; - this.historyChanged = false; - this.scrolled = false; - this.shouldCacheSnapshot = true; - this.acceptsStreamResponse = false; - this.snapshotCached = false; - this.state = VisitState.initialized; - this.delegate = delegate; - this.location = location; - this.restorationIdentifier = restorationIdentifier || uuid(); - const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options); - this.action = action; - this.historyChanged = historyChanged; - this.referrer = referrer; - this.snapshot = snapshot; - this.snapshotHTML = snapshotHTML; - this.response = response; - this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action); - this.visitCachedSnapshot = visitCachedSnapshot; - this.willRender = willRender; - this.updateHistory = updateHistory; - this.scrolled = !willRender; - this.shouldCacheSnapshot = shouldCacheSnapshot; - this.acceptsStreamResponse = acceptsStreamResponse; - } - get adapter() { - return this.delegate.adapter; - } - get view() { - return this.delegate.view; - } - get history() { - return this.delegate.history; - } - get restorationData() { - return this.history.getRestorationDataForIdentifier(this.restorationIdentifier); - } - get silent() { - return this.isSamePage; - } - start() { - if (this.state == VisitState.initialized) { - this.recordTimingMetric(TimingMetric.visitStart); - this.state = VisitState.started; - this.adapter.visitStarted(this); - this.delegate.visitStarted(this); - } - } - cancel() { - if (this.state == VisitState.started) { - if (this.request) { - this.request.cancel(); - } - this.cancelRender(); - this.state = VisitState.canceled; - } - } - complete() { - if (this.state == VisitState.started) { - this.recordTimingMetric(TimingMetric.visitEnd); - this.state = VisitState.completed; - this.followRedirect(); - if (!this.followedRedirect) { - this.adapter.visitCompleted(this); - this.delegate.visitCompleted(this); - } - } - } - fail() { - if (this.state == VisitState.started) { - this.state = VisitState.failed; - this.adapter.visitFailed(this); - } - } - changeHistory() { - var _a; - if (!this.historyChanged && this.updateHistory) { - const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action; - const method = getHistoryMethodForAction(actionForHistory); - this.history.update(method, this.location, this.restorationIdentifier); - this.historyChanged = true; - } - } - issueRequest() { - if (this.hasPreloadedResponse()) { - this.simulateRequest(); - } - else if (this.shouldIssueRequest() && !this.request) { - this.request = new FetchRequest(this, FetchMethod.get, this.location); - this.request.perform(); - } - } - simulateRequest() { - if (this.response) { - this.startRequest(); - this.recordResponse(); - this.finishRequest(); - } - } - startRequest() { - this.recordTimingMetric(TimingMetric.requestStart); - this.adapter.visitRequestStarted(this); - } - recordResponse(response = this.response) { - this.response = response; - if (response) { - const { statusCode } = response; - if (isSuccessful(statusCode)) { - this.adapter.visitRequestCompleted(this); - } - else { - this.adapter.visitRequestFailedWithStatusCode(this, statusCode); - } - } - } - finishRequest() { - this.recordTimingMetric(TimingMetric.requestEnd); - this.adapter.visitRequestFinished(this); - } - loadResponse() { - if (this.response) { - const { statusCode, responseHTML } = this.response; - this.render(async () => { - if (this.shouldCacheSnapshot) - this.cacheSnapshot(); - if (this.view.renderPromise) - await this.view.renderPromise; - if (isSuccessful(statusCode) && responseHTML != null) { - await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this); - this.performScroll(); - this.adapter.visitRendered(this); - this.complete(); - } - else { - await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this); - this.adapter.visitRendered(this); - this.fail(); - } - }); - } - } - getCachedSnapshot() { - const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot(); - if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) { - if (this.action == "restore" || snapshot.isPreviewable) { - return snapshot; - } - } - } - getPreloadedSnapshot() { - if (this.snapshotHTML) { - return PageSnapshot.fromHTMLString(this.snapshotHTML); - } - } - hasCachedSnapshot() { - return this.getCachedSnapshot() != null; - } - loadCachedSnapshot() { - const snapshot = this.getCachedSnapshot(); - if (snapshot) { - const isPreview = this.shouldIssueRequest(); - this.render(async () => { - this.cacheSnapshot(); - if (this.isSamePage) { - this.adapter.visitRendered(this); - } - else { - if (this.view.renderPromise) - await this.view.renderPromise; - await this.view.renderPage(snapshot, isPreview, this.willRender, this); - this.performScroll(); - this.adapter.visitRendered(this); - if (!isPreview) { - this.complete(); - } - } - }); - } - } - followRedirect() { - var _a; - if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) { - this.adapter.visitProposedToLocation(this.redirectedToLocation, { - action: "replace", - response: this.response, - shouldCacheSnapshot: false, - willRender: false, - }); - this.followedRedirect = true; - } - } - goToSamePageAnchor() { - if (this.isSamePage) { - this.render(async () => { - this.cacheSnapshot(); - this.performScroll(); - this.changeHistory(); - this.adapter.visitRendered(this); - }); - } - } - prepareRequest(request) { - if (this.acceptsStreamResponse) { - request.acceptResponseType(StreamMessage.contentType); - } - } - requestStarted() { - this.startRequest(); - } - requestPreventedHandlingResponse(_request, _response) { } - async requestSucceededWithResponse(request, response) { - const responseHTML = await response.responseHTML; - const { redirected, statusCode } = response; - if (responseHTML == undefined) { - this.recordResponse({ - statusCode: SystemStatusCode.contentTypeMismatch, - redirected, - }); - } - else { - this.redirectedToLocation = response.redirected ? response.location : undefined; - this.recordResponse({ statusCode: statusCode, responseHTML, redirected }); - } - } - async requestFailedWithResponse(request, response) { - const responseHTML = await response.responseHTML; - const { redirected, statusCode } = response; - if (responseHTML == undefined) { - this.recordResponse({ - statusCode: SystemStatusCode.contentTypeMismatch, - redirected, - }); - } - else { - this.recordResponse({ statusCode: statusCode, responseHTML, redirected }); - } - } - requestErrored(_request, _error) { - this.recordResponse({ - statusCode: SystemStatusCode.networkFailure, - redirected: false, - }); - } - requestFinished() { - this.finishRequest(); - } - performScroll() { - if (!this.scrolled && !this.view.forceReloaded) { - if (this.action == "restore") { - this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop(); - } - else { - this.scrollToAnchor() || this.view.scrollToTop(); - } - if (this.isSamePage) { - this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location); - } - this.scrolled = true; - } - } - scrollToRestoredPosition() { - const { scrollPosition } = this.restorationData; - if (scrollPosition) { - this.view.scrollToPosition(scrollPosition); - return true; - } - } - scrollToAnchor() { - const anchor = getAnchor(this.location); - if (anchor != null) { - this.view.scrollToAnchor(anchor); - return true; - } - } - recordTimingMetric(metric) { - this.timingMetrics[metric] = new Date().getTime(); - } - getTimingMetrics() { - return Object.assign({}, this.timingMetrics); - } - getHistoryMethodForAction(action) { - switch (action) { - case "replace": - return history.replaceState; - case "advance": - case "restore": - return history.pushState; - } - } - hasPreloadedResponse() { - return typeof this.response == "object"; - } - shouldIssueRequest() { - if (this.isSamePage) { - return false; - } - else if (this.action == "restore") { - return !this.hasCachedSnapshot(); - } - else { - return this.willRender; - } - } - cacheSnapshot() { - if (!this.snapshotCached) { - this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot)); - this.snapshotCached = true; - } - } - async render(callback) { - this.cancelRender(); - await new Promise((resolve) => { - this.frame = requestAnimationFrame(() => resolve()); - }); - await callback(); - delete this.frame; - } - cancelRender() { - if (this.frame) { - cancelAnimationFrame(this.frame); - delete this.frame; - } - } +class FrameRedirector { + constructor(session, element) { + this.session = session; + this.element = element; + this.linkInterceptor = new LinkInterceptor(this, element); + this.formSubmitObserver = new FormSubmitObserver(this, element); } - function isSuccessful(statusCode) { - return statusCode >= 200 && statusCode < 300; + start() { + this.linkInterceptor.start(); + this.formSubmitObserver.start(); } - - class BrowserAdapter { - constructor(session) { - this.progressBar = new ProgressBar(); - this.showProgressBar = () => { - this.progressBar.show(); - }; - this.session = session; - } - visitProposedToLocation(location, options) { - this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options); - } - visitStarted(visit) { - this.location = visit.location; - visit.loadCachedSnapshot(); - visit.issueRequest(); - visit.goToSamePageAnchor(); - } - visitRequestStarted(visit) { - this.progressBar.setValue(0); - if (visit.hasCachedSnapshot() || visit.action != "restore") { - this.showVisitProgressBarAfterDelay(); - } - else { - this.showProgressBar(); - } - } - visitRequestCompleted(visit) { - visit.loadResponse(); - } - visitRequestFailedWithStatusCode(visit, statusCode) { - switch (statusCode) { - case SystemStatusCode.networkFailure: - case SystemStatusCode.timeoutFailure: - case SystemStatusCode.contentTypeMismatch: - return this.reload({ - reason: "request_failed", - context: { - statusCode, - }, - }); - default: - return visit.loadResponse(); - } - } - visitRequestFinished(_visit) { - this.progressBar.setValue(1); - this.hideVisitProgressBar(); - } - visitCompleted(_visit) { } - pageInvalidated(reason) { - this.reload(reason); - } - visitFailed(_visit) { } - visitRendered(_visit) { } - formSubmissionStarted(_formSubmission) { - this.progressBar.setValue(0); - this.showFormProgressBarAfterDelay(); - } - formSubmissionFinished(_formSubmission) { - this.progressBar.setValue(1); - this.hideFormProgressBar(); + stop() { + this.linkInterceptor.stop(); + this.formSubmitObserver.stop(); + } + shouldInterceptLinkClick(element, _location, _event) { + return this.shouldRedirect(element); + } + linkClickIntercepted(element, url, event) { + const frame = this.findFrameElement(element); + if (frame) { + frame.delegate.linkClickIntercepted(element, url, event); } - showVisitProgressBarAfterDelay() { - this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); + } + willSubmitForm(element, submitter) { + return (element.closest("turbo-frame") == null && + this.shouldSubmit(element, submitter) && + this.shouldRedirect(element, submitter)); + } + formSubmitted(element, submitter) { + const frame = this.findFrameElement(element, submitter); + if (frame) { + frame.delegate.formSubmitted(element, submitter); } - hideVisitProgressBar() { - this.progressBar.hide(); - if (this.visitProgressBarTimeout != null) { - window.clearTimeout(this.visitProgressBarTimeout); - delete this.visitProgressBarTimeout; - } + } + shouldSubmit(form, submitter) { + var _a; + const action = getAction(form, submitter); + const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); + const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/"); + return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation); + } + shouldRedirect(element, submitter) { + const isNavigatable = element instanceof HTMLFormElement + ? this.session.submissionIsNavigatable(element, submitter) + : this.session.elementIsNavigatable(element); + if (isNavigatable) { + const frame = this.findFrameElement(element, submitter); + return frame ? frame != element.closest("turbo-frame") : false; } - showFormProgressBarAfterDelay() { - if (this.formProgressBarTimeout == null) { - this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); - } + else { + return false; } - hideFormProgressBar() { - this.progressBar.hide(); - if (this.formProgressBarTimeout != null) { - window.clearTimeout(this.formProgressBarTimeout); - delete this.formProgressBarTimeout; - } - } - reload(reason) { - var _a; - dispatch("turbo:reload", { detail: reason }); - window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href; - } - get navigator() { - return this.session.navigator; - } - } - - class CacheObserver { - constructor() { - this.selector = "[data-turbo-temporary]"; - this.deprecatedSelector = "[data-turbo-cache=false]"; - this.started = false; - this.removeTemporaryElements = ((_event) => { - for (const element of this.temporaryElements) { - element.remove(); - } - }); - } - start() { - if (!this.started) { - this.started = true; - addEventListener("turbo:before-cache", this.removeTemporaryElements, false); - } - } - stop() { - if (this.started) { - this.started = false; - removeEventListener("turbo:before-cache", this.removeTemporaryElements, false); - } - } - get temporaryElements() { - return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation]; - } - get temporaryElementsWithDeprecation() { - const elements = document.querySelectorAll(this.deprecatedSelector); - if (elements.length) { - console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`); - } - return [...elements]; - } - } - - class FrameRedirector { - constructor(session, element) { - this.session = session; - this.element = element; - this.linkInterceptor = new LinkInterceptor(this, element); - this.formSubmitObserver = new FormSubmitObserver(this, element); - } - start() { - this.linkInterceptor.start(); - this.formSubmitObserver.start(); - } - stop() { - this.linkInterceptor.stop(); - this.formSubmitObserver.stop(); - } - shouldInterceptLinkClick(element, _location, _event) { - return this.shouldRedirect(element); - } - linkClickIntercepted(element, url, event) { - const frame = this.findFrameElement(element); - if (frame) { - frame.delegate.linkClickIntercepted(element, url, event); - } - } - willSubmitForm(element, submitter) { - return (element.closest("turbo-frame") == null && - this.shouldSubmit(element, submitter) && - this.shouldRedirect(element, submitter)); - } - formSubmitted(element, submitter) { - const frame = this.findFrameElement(element, submitter); - if (frame) { - frame.delegate.formSubmitted(element, submitter); - } - } - shouldSubmit(form, submitter) { - var _a; - const action = getAction(form, submitter); - const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); - const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/"); - return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation); - } - shouldRedirect(element, submitter) { - const isNavigatable = element instanceof HTMLFormElement - ? this.session.submissionIsNavigatable(element, submitter) - : this.session.elementIsNavigatable(element); - if (isNavigatable) { - const frame = this.findFrameElement(element, submitter); - return frame ? frame != element.closest("turbo-frame") : false; - } - else { - return false; - } - } - findFrameElement(element, submitter) { - const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame"); - if (id && id != "_top") { - const frame = this.element.querySelector(`#${id}:not([disabled])`); - if (frame instanceof FrameElement) { - return frame; - } - } - } - } - - class History { - constructor(delegate) { - this.restorationIdentifier = uuid(); - this.restorationData = {}; - this.started = false; - this.pageLoaded = false; - this.onPopState = (event) => { - if (this.shouldHandlePopState()) { - const { turbo } = event.state || {}; - if (turbo) { - this.location = new URL(window.location.href); - const { restorationIdentifier } = turbo; - this.restorationIdentifier = restorationIdentifier; - this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier); - } - } - }; - this.onPageLoad = async (_event) => { - await nextMicrotask(); - this.pageLoaded = true; - }; - this.delegate = delegate; - } - start() { - if (!this.started) { - addEventListener("popstate", this.onPopState, false); - addEventListener("load", this.onPageLoad, false); - this.started = true; - this.replace(new URL(window.location.href)); - } - } - stop() { - if (this.started) { - removeEventListener("popstate", this.onPopState, false); - removeEventListener("load", this.onPageLoad, false); - this.started = false; - } - } - push(location, restorationIdentifier) { - this.update(history.pushState, location, restorationIdentifier); - } - replace(location, restorationIdentifier) { - this.update(history.replaceState, location, restorationIdentifier); - } - update(method, location, restorationIdentifier = uuid()) { - const state = { turbo: { restorationIdentifier } }; - method.call(history, state, "", location.href); - this.location = location; - this.restorationIdentifier = restorationIdentifier; - } - getRestorationDataForIdentifier(restorationIdentifier) { - return this.restorationData[restorationIdentifier] || {}; - } - updateRestorationData(additionalData) { - const { restorationIdentifier } = this; - const restorationData = this.restorationData[restorationIdentifier]; - this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData); - } - assumeControlOfScrollRestoration() { - var _a; - if (!this.previousScrollRestoration) { - this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto"; - history.scrollRestoration = "manual"; - } - } - relinquishControlOfScrollRestoration() { - if (this.previousScrollRestoration) { - history.scrollRestoration = this.previousScrollRestoration; - delete this.previousScrollRestoration; - } - } - shouldHandlePopState() { - return this.pageIsLoaded(); - } - pageIsLoaded() { - return this.pageLoaded || document.readyState == "complete"; - } - } - - class Navigator { - constructor(delegate) { - this.delegate = delegate; - } - proposeVisit(location, options = {}) { - if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) { - if (locationIsVisitable(location, this.view.snapshot.rootLocation)) { - this.delegate.visitProposedToLocation(location, options); - } - else { - window.location.href = location.toString(); - } - } - } - startVisit(locatable, restorationIdentifier, options = {}) { - this.stop(); - this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options)); - this.currentVisit.start(); - } - submitForm(form, submitter) { - this.stop(); - this.formSubmission = new FormSubmission(this, form, submitter, true); - this.formSubmission.start(); - } - stop() { - if (this.formSubmission) { - this.formSubmission.stop(); - delete this.formSubmission; - } - if (this.currentVisit) { - this.currentVisit.cancel(); - delete this.currentVisit; - } - } - get adapter() { - return this.delegate.adapter; - } - get view() { - return this.delegate.view; - } - get history() { - return this.delegate.history; - } - formSubmissionStarted(formSubmission) { - if (typeof this.adapter.formSubmissionStarted === "function") { - this.adapter.formSubmissionStarted(formSubmission); - } - } - async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) { - if (formSubmission == this.formSubmission) { - const responseHTML = await fetchResponse.responseHTML; - if (responseHTML) { - const shouldCacheSnapshot = formSubmission.isSafe; - if (!shouldCacheSnapshot) { - this.view.clearSnapshotCache(); - } - const { statusCode, redirected } = fetchResponse; - const action = this.getActionForFormSubmission(formSubmission); - const visitOptions = { - action, - shouldCacheSnapshot, - response: { statusCode, responseHTML, redirected }, - }; - this.proposeVisit(fetchResponse.location, visitOptions); - } - } - } - async formSubmissionFailedWithResponse(formSubmission, fetchResponse) { - const responseHTML = await fetchResponse.responseHTML; - if (responseHTML) { - const snapshot = PageSnapshot.fromHTMLString(responseHTML); - if (fetchResponse.serverError) { - await this.view.renderError(snapshot, this.currentVisit); - } - else { - await this.view.renderPage(snapshot, false, true, this.currentVisit); - } - this.view.scrollToTop(); - this.view.clearSnapshotCache(); - } - } - formSubmissionErrored(formSubmission, error) { - console.error(error); - } - formSubmissionFinished(formSubmission) { - if (typeof this.adapter.formSubmissionFinished === "function") { - this.adapter.formSubmissionFinished(formSubmission); - } - } - visitStarted(visit) { - this.delegate.visitStarted(visit); - } - visitCompleted(visit) { - this.delegate.visitCompleted(visit); - } - locationWithActionIsSamePage(location, action) { - const anchor = getAnchor(location); - const currentAnchor = getAnchor(this.view.lastRenderedLocation); - const isRestorationToTop = action === "restore" && typeof anchor === "undefined"; - return (action !== "replace" && - getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && - (isRestorationToTop || (anchor != null && anchor !== currentAnchor))); - } - visitScrolledToSamePageLocation(oldURL, newURL) { - this.delegate.visitScrolledToSamePageLocation(oldURL, newURL); - } - get location() { - return this.history.location; - } - get restorationIdentifier() { - return this.history.restorationIdentifier; - } - getActionForFormSubmission({ submitter, formElement }) { - return getVisitAction(submitter, formElement) || "advance"; - } - } - - var PageStage; - (function (PageStage) { - PageStage[PageStage["initial"] = 0] = "initial"; - PageStage[PageStage["loading"] = 1] = "loading"; - PageStage[PageStage["interactive"] = 2] = "interactive"; - PageStage[PageStage["complete"] = 3] = "complete"; - })(PageStage || (PageStage = {})); - class PageObserver { - constructor(delegate) { - this.stage = PageStage.initial; - this.started = false; - this.interpretReadyState = () => { - const { readyState } = this; - if (readyState == "interactive") { - this.pageIsInteractive(); - } - else if (readyState == "complete") { - this.pageIsComplete(); - } - }; - this.pageWillUnload = () => { - this.delegate.pageWillUnload(); - }; - this.delegate = delegate; - } - start() { - if (!this.started) { - if (this.stage == PageStage.initial) { - this.stage = PageStage.loading; - } - document.addEventListener("readystatechange", this.interpretReadyState, false); - addEventListener("pagehide", this.pageWillUnload, false); - this.started = true; - } - } - stop() { - if (this.started) { - document.removeEventListener("readystatechange", this.interpretReadyState, false); - removeEventListener("pagehide", this.pageWillUnload, false); - this.started = false; - } - } - pageIsInteractive() { - if (this.stage == PageStage.loading) { - this.stage = PageStage.interactive; - this.delegate.pageBecameInteractive(); - } - } - pageIsComplete() { - this.pageIsInteractive(); - if (this.stage == PageStage.interactive) { - this.stage = PageStage.complete; - this.delegate.pageLoaded(); - } - } - get readyState() { - return document.readyState; - } - } - - class ScrollObserver { - constructor(delegate) { - this.started = false; - this.onScroll = () => { - this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset }); - }; - this.delegate = delegate; - } - start() { - if (!this.started) { - addEventListener("scroll", this.onScroll, false); - this.onScroll(); - this.started = true; - } - } - stop() { - if (this.started) { - removeEventListener("scroll", this.onScroll, false); - this.started = false; - } - } - updatePosition(position) { - this.delegate.scrollPositionChanged(position); - } - } - - class StreamMessageRenderer { - render({ fragment }) { - Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment)); - } - enteringBardo(currentPermanentElement, newPermanentElement) { - newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true)); - } - leavingBardo() { } - } - function getPermanentElementMapForFragment(fragment) { - const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement); - const permanentElementMap = {}; - for (const permanentElementInDocument of permanentElementsInDocument) { - const { id } = permanentElementInDocument; - for (const streamElement of fragment.querySelectorAll("turbo-stream")) { - const elementInStream = getPermanentElementById(streamElement.templateElement.content, id); - if (elementInStream) { - permanentElementMap[id] = [permanentElementInDocument, elementInStream]; - } - } - } - return permanentElementMap; - } - - class StreamObserver { - constructor(delegate) { - this.sources = new Set(); - this.started = false; - this.inspectFetchResponse = ((event) => { - const response = fetchResponseFromEvent(event); - if (response && fetchResponseIsStream(response)) { - event.preventDefault(); - this.receiveMessageResponse(response); - } - }); - this.receiveMessageEvent = (event) => { - if (this.started && typeof event.data == "string") { - this.receiveMessageHTML(event.data); - } - }; - this.delegate = delegate; - } - start() { - if (!this.started) { - this.started = true; - addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); - } - } - stop() { - if (this.started) { - this.started = false; - removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); - } - } - connectStreamSource(source) { - if (!this.streamSourceIsConnected(source)) { - this.sources.add(source); - source.addEventListener("message", this.receiveMessageEvent, false); - } - } - disconnectStreamSource(source) { - if (this.streamSourceIsConnected(source)) { - this.sources.delete(source); - source.removeEventListener("message", this.receiveMessageEvent, false); - } - } - streamSourceIsConnected(source) { - return this.sources.has(source); - } - async receiveMessageResponse(response) { - const html = await response.responseHTML; - if (html) { - this.receiveMessageHTML(html); - } - } - receiveMessageHTML(html) { - this.delegate.receivedMessageFromStream(StreamMessage.wrap(html)); - } - } - function fetchResponseFromEvent(event) { - var _a; - const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse; - if (fetchResponse instanceof FetchResponse) { - return fetchResponse; - } - } - function fetchResponseIsStream(response) { - var _a; - const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : ""; - return contentType.startsWith(StreamMessage.contentType); - } - - class ErrorRenderer extends Renderer { - static renderElement(currentElement, newElement) { - const { documentElement, body } = document; - documentElement.replaceChild(newElement, body); - } - async render() { - this.replaceHeadAndBody(); - this.activateScriptElements(); - } - replaceHeadAndBody() { - const { documentElement, head } = document; - documentElement.replaceChild(this.newHead, head); - this.renderElement(this.currentElement, this.newElement); - } - activateScriptElements() { - for (const replaceableElement of this.scriptElements) { - const parentNode = replaceableElement.parentNode; - if (parentNode) { - const element = activateScriptElement(replaceableElement); - parentNode.replaceChild(element, replaceableElement); - } - } - } - get newHead() { - return this.newSnapshot.headSnapshot.element; - } - get scriptElements() { - return document.documentElement.querySelectorAll("script"); - } - } - - class PageRenderer extends Renderer { - static renderElement(currentElement, newElement) { - if (document.body && newElement instanceof HTMLBodyElement) { - document.body.replaceWith(newElement); - } - else { - document.documentElement.appendChild(newElement); - } - } - get shouldRender() { - return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical; - } - get reloadReason() { - if (!this.newSnapshot.isVisitable) { - return { - reason: "turbo_visit_control_is_reload", - }; - } - if (!this.trackedElementsAreIdentical) { - return { - reason: "tracked_element_mismatch", - }; - } - } - async prepareToRender() { - await this.mergeHead(); - } - async render() { - if (this.willRender) { - await this.replaceBody(); - } - } - finishRendering() { - super.finishRendering(); - if (!this.isPreview) { - this.focusFirstAutofocusableElement(); - } - } - get currentHeadSnapshot() { - return this.currentSnapshot.headSnapshot; - } - get newHeadSnapshot() { - return this.newSnapshot.headSnapshot; - } - get newElement() { - return this.newSnapshot.element; - } - async mergeHead() { - const mergedHeadElements = this.mergeProvisionalElements(); - const newStylesheetElements = this.copyNewHeadStylesheetElements(); - this.copyNewHeadScriptElements(); - await mergedHeadElements; - await newStylesheetElements; - } - async replaceBody() { - await this.preservingPermanentElements(async () => { - this.activateNewBody(); - await this.assignNewBody(); - }); - } - get trackedElementsAreIdentical() { - return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature; - } - async copyNewHeadStylesheetElements() { - const loadingElements = []; - for (const element of this.newHeadStylesheetElements) { - loadingElements.push(waitForLoad(element)); - document.head.appendChild(element); - } - await Promise.all(loadingElements); - } - copyNewHeadScriptElements() { - for (const element of this.newHeadScriptElements) { - document.head.appendChild(activateScriptElement(element)); - } - } - async mergeProvisionalElements() { - const newHeadElements = [...this.newHeadProvisionalElements]; - for (const element of this.currentHeadProvisionalElements) { - if (!this.isCurrentElementInElementList(element, newHeadElements)) { - document.head.removeChild(element); - } - } - for (const element of newHeadElements) { - document.head.appendChild(element); - } - } - isCurrentElementInElementList(element, elementList) { - for (const [index, newElement] of elementList.entries()) { - if (element.tagName == "TITLE") { - if (newElement.tagName != "TITLE") { - continue; - } - if (element.innerHTML == newElement.innerHTML) { - elementList.splice(index, 1); - return true; - } - } - if (newElement.isEqualNode(element)) { - elementList.splice(index, 1); - return true; - } - } - return false; - } - removeCurrentHeadProvisionalElements() { - for (const element of this.currentHeadProvisionalElements) { - document.head.removeChild(element); - } - } - copyNewHeadProvisionalElements() { - for (const element of this.newHeadProvisionalElements) { - document.head.appendChild(element); - } - } - activateNewBody() { - document.adoptNode(this.newElement); - this.activateNewBodyScriptElements(); - } - activateNewBodyScriptElements() { - for (const inertScriptElement of this.newBodyScriptElements) { - const activatedScriptElement = activateScriptElement(inertScriptElement); - inertScriptElement.replaceWith(activatedScriptElement); - } - } - async assignNewBody() { - await this.renderElement(this.currentElement, this.newElement); - } - get newHeadStylesheetElements() { - return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot); - } - get newHeadScriptElements() { - return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot); - } - get currentHeadProvisionalElements() { - return this.currentHeadSnapshot.provisionalElements; - } - get newHeadProvisionalElements() { - return this.newHeadSnapshot.provisionalElements; - } - get newBodyScriptElements() { - return this.newElement.querySelectorAll("script"); - } - } - - class SnapshotCache { - constructor(size) { - this.keys = []; - this.snapshots = {}; - this.size = size; - } - has(location) { - return toCacheKey(location) in this.snapshots; - } - get(location) { - if (this.has(location)) { - const snapshot = this.read(location); - this.touch(location); - return snapshot; - } - } - put(location, snapshot) { - this.write(location, snapshot); - this.touch(location); - return snapshot; - } - clear() { - this.snapshots = {}; - } - read(location) { - return this.snapshots[toCacheKey(location)]; - } - write(location, snapshot) { - this.snapshots[toCacheKey(location)] = snapshot; - } - touch(location) { - const key = toCacheKey(location); - const index = this.keys.indexOf(key); - if (index > -1) - this.keys.splice(index, 1); - this.keys.unshift(key); - this.trim(); - } - trim() { - for (const key of this.keys.splice(this.size)) { - delete this.snapshots[key]; + } + findFrameElement(element, submitter) { + const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame"); + if (id && id != "_top") { + const frame = this.element.querySelector(`#${id}:not([disabled])`); + if (frame instanceof FrameElement) { + return frame; } } } +} - class PageView extends View { - constructor() { - super(...arguments); - this.snapshotCache = new SnapshotCache(10); - this.lastRenderedLocation = new URL(location.href); - this.forceReloaded = false; - } - renderPage(snapshot, isPreview = false, willRender = true, visit) { - const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender); - if (!renderer.shouldRender) { - this.forceReloaded = true; - } - else { - visit === null || visit === void 0 ? void 0 : visit.changeHistory(); - } - return this.render(renderer); - } - renderError(snapshot, visit) { - visit === null || visit === void 0 ? void 0 : visit.changeHistory(); - const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false); - return this.render(renderer); - } - clearSnapshotCache() { - this.snapshotCache.clear(); - } - async cacheSnapshot(snapshot = this.snapshot) { - if (snapshot.isCacheable) { - this.delegate.viewWillCacheSnapshot(); - const { lastRenderedLocation: location } = this; - await nextEventLoopTick(); - const cachedSnapshot = snapshot.clone(); - this.snapshotCache.put(location, cachedSnapshot); - return cachedSnapshot; - } - } - getCachedSnapshotForLocation(location) { - return this.snapshotCache.get(location); - } - get snapshot() { - return PageSnapshot.fromElement(this.element); - } - } - - class Preloader { - constructor(delegate) { - this.selector = "a[data-turbo-preload]"; - this.delegate = delegate; - } - get snapshotCache() { - return this.delegate.navigator.view.snapshotCache; - } - start() { - if (document.readyState === "loading") { - return document.addEventListener("DOMContentLoaded", () => { - this.preloadOnLoadLinksForView(document.body); - }); - } - else { - this.preloadOnLoadLinksForView(document.body); - } - } - preloadOnLoadLinksForView(element) { - for (const link of element.querySelectorAll(this.selector)) { - this.preloadURL(link); - } - } - async preloadURL(link) { - const location = new URL(link.href); - if (this.snapshotCache.has(location)) { - return; - } - try { - const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } }); - const responseText = await response.text(); - const snapshot = PageSnapshot.fromHTMLString(responseText); - this.snapshotCache.put(location, snapshot); - } - catch (_) { - } - } - } - - class Session { - constructor() { - this.navigator = new Navigator(this); - this.history = new History(this); - this.preloader = new Preloader(this); - this.view = new PageView(this, document.documentElement); - this.adapter = new BrowserAdapter(this); - this.pageObserver = new PageObserver(this); - this.cacheObserver = new CacheObserver(); - this.linkClickObserver = new LinkClickObserver(this, window); - this.formSubmitObserver = new FormSubmitObserver(this, document); - this.scrollObserver = new ScrollObserver(this); - this.streamObserver = new StreamObserver(this); - this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement); - this.frameRedirector = new FrameRedirector(this, document.documentElement); - this.streamMessageRenderer = new StreamMessageRenderer(); - this.drive = true; - this.enabled = true; - this.progressBarDelay = 500; - this.started = false; - this.formMode = "on"; - } - start() { - if (!this.started) { - this.pageObserver.start(); - this.cacheObserver.start(); - this.formLinkClickObserver.start(); - this.linkClickObserver.start(); - this.formSubmitObserver.start(); - this.scrollObserver.start(); - this.streamObserver.start(); - this.frameRedirector.start(); - this.history.start(); - this.preloader.start(); - this.started = true; - this.enabled = true; - } - } - disable() { - this.enabled = false; - } - stop() { - if (this.started) { - this.pageObserver.stop(); - this.cacheObserver.stop(); - this.formLinkClickObserver.stop(); - this.linkClickObserver.stop(); - this.formSubmitObserver.stop(); - this.scrollObserver.stop(); - this.streamObserver.stop(); - this.frameRedirector.stop(); - this.history.stop(); - this.started = false; - } - } - registerAdapter(adapter) { - this.adapter = adapter; - } - visit(location, options = {}) { - const frameElement = options.frame ? document.getElementById(options.frame) : null; - if (frameElement instanceof FrameElement) { - frameElement.src = location.toString(); - frameElement.loaded; - } - else { - this.navigator.proposeVisit(expandURL(location), options); - } - } - connectStreamSource(source) { - this.streamObserver.connectStreamSource(source); - } - disconnectStreamSource(source) { - this.streamObserver.disconnectStreamSource(source); - } - renderStreamMessage(message) { - this.streamMessageRenderer.render(StreamMessage.wrap(message)); - } - clearCache() { - this.view.clearSnapshotCache(); - } - setProgressBarDelay(delay) { - this.progressBarDelay = delay; - } - setFormMode(mode) { - this.formMode = mode; - } - get location() { - return this.history.location; - } - get restorationIdentifier() { - return this.history.restorationIdentifier; - } - historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) { - if (this.enabled) { - this.navigator.startVisit(location, restorationIdentifier, { - action: "restore", - historyChanged: true, - }); - } - else { - this.adapter.pageInvalidated({ - reason: "turbo_disabled", - }); - } - } - scrollPositionChanged(position) { - this.history.updateRestorationData({ scrollPosition: position }); - } - willSubmitFormLinkToLocation(link, location) { - return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation); - } - submittedFormLinkToLocation() { } - willFollowLinkToLocation(link, location, event) { - return (this.elementIsNavigatable(link) && - locationIsVisitable(location, this.snapshot.rootLocation) && - this.applicationAllowsFollowingLinkToLocation(link, location, event)); - } - followedLinkToLocation(link, location) { - const action = this.getActionForLink(link); - const acceptsStreamResponse = link.hasAttribute("data-turbo-stream"); - this.visit(location.href, { action, acceptsStreamResponse }); - } - allowsVisitingLocationWithAction(location, action) { - return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location); - } - visitProposedToLocation(location, options) { - extendURLWithDeprecatedProperties(location); - this.adapter.visitProposedToLocation(location, options); - } - visitStarted(visit) { - if (!visit.acceptsStreamResponse) { - markAsBusy(document.documentElement); - } - extendURLWithDeprecatedProperties(visit.location); - if (!visit.silent) { - this.notifyApplicationAfterVisitingLocation(visit.location, visit.action); - } - } - visitCompleted(visit) { - clearBusyState(document.documentElement); - this.notifyApplicationAfterPageLoad(visit.getTimingMetrics()); - } - locationWithActionIsSamePage(location, action) { - return this.navigator.locationWithActionIsSamePage(location, action); - } - visitScrolledToSamePageLocation(oldURL, newURL) { - this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL); - } - willSubmitForm(form, submitter) { - const action = getAction(form, submitter); - return (this.submissionIsNavigatable(form, submitter) && - locationIsVisitable(expandURL(action), this.snapshot.rootLocation)); - } - formSubmitted(form, submitter) { - this.navigator.submitForm(form, submitter); - } - pageBecameInteractive() { - this.view.lastRenderedLocation = this.location; - this.notifyApplicationAfterPageLoad(); - } - pageLoaded() { - this.history.assumeControlOfScrollRestoration(); - } - pageWillUnload() { - this.history.relinquishControlOfScrollRestoration(); - } - receivedMessageFromStream(message) { - this.renderStreamMessage(message); - } - viewWillCacheSnapshot() { - var _a; - if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) { - this.notifyApplicationBeforeCachingSnapshot(); - } - } - allowsImmediateRender({ element }, options) { - const event = this.notifyApplicationBeforeRender(element, options); - const { defaultPrevented, detail: { render }, } = event; - if (this.view.renderer && render) { - this.view.renderer.renderElement = render; - } - return !defaultPrevented; - } - viewRenderedSnapshot(_snapshot, _isPreview) { - this.view.lastRenderedLocation = this.history.location; - this.notifyApplicationAfterRender(); - } - preloadOnLoadLinksForView(element) { - this.preloader.preloadOnLoadLinksForView(element); - } - viewInvalidated(reason) { - this.adapter.pageInvalidated(reason); - } - frameLoaded(frame) { - this.notifyApplicationAfterFrameLoad(frame); - } - frameRendered(fetchResponse, frame) { - this.notifyApplicationAfterFrameRender(fetchResponse, frame); - } - applicationAllowsFollowingLinkToLocation(link, location, ev) { - const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev); - return !event.defaultPrevented; - } - applicationAllowsVisitingLocation(location) { - const event = this.notifyApplicationBeforeVisitingLocation(location); - return !event.defaultPrevented; - } - notifyApplicationAfterClickingLinkToLocation(link, location, event) { - return dispatch("turbo:click", { - target: link, - detail: { url: location.href, originalEvent: event }, - cancelable: true, - }); - } - notifyApplicationBeforeVisitingLocation(location) { - return dispatch("turbo:before-visit", { - detail: { url: location.href }, - cancelable: true, - }); +class History { + constructor(delegate) { + this.restorationIdentifier = uuid(); + this.restorationData = {}; + this.started = false; + this.pageLoaded = false; + this.onPopState = (event) => { + if (this.shouldHandlePopState()) { + const { turbo } = event.state || {}; + if (turbo) { + this.location = new URL(window.location.href); + const { restorationIdentifier } = turbo; + this.restorationIdentifier = restorationIdentifier; + this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier); + } + } + }; + this.onPageLoad = async (_event) => { + await nextMicrotask(); + this.pageLoaded = true; + }; + this.delegate = delegate; + } + start() { + if (!this.started) { + addEventListener("popstate", this.onPopState, false); + addEventListener("load", this.onPageLoad, false); + this.started = true; + this.replace(new URL(window.location.href)); } - notifyApplicationAfterVisitingLocation(location, action) { - return dispatch("turbo:visit", { detail: { url: location.href, action } }); + } + stop() { + if (this.started) { + removeEventListener("popstate", this.onPopState, false); + removeEventListener("load", this.onPageLoad, false); + this.started = false; } - notifyApplicationBeforeCachingSnapshot() { - return dispatch("turbo:before-cache"); + } + push(location, restorationIdentifier) { + this.update(history.pushState, location, restorationIdentifier); + } + replace(location, restorationIdentifier) { + this.update(history.replaceState, location, restorationIdentifier); + } + update(method, location, restorationIdentifier = uuid()) { + const state = { turbo: { restorationIdentifier } }; + method.call(history, state, "", location.href); + this.location = location; + this.restorationIdentifier = restorationIdentifier; + } + getRestorationDataForIdentifier(restorationIdentifier) { + return this.restorationData[restorationIdentifier] || {}; + } + updateRestorationData(additionalData) { + const { restorationIdentifier } = this; + const restorationData = this.restorationData[restorationIdentifier]; + this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData); + } + assumeControlOfScrollRestoration() { + var _a; + if (!this.previousScrollRestoration) { + this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto"; + history.scrollRestoration = "manual"; } - notifyApplicationBeforeRender(newBody, options) { - return dispatch("turbo:before-render", { - detail: Object.assign({ newBody }, options), - cancelable: true, - }); + } + relinquishControlOfScrollRestoration() { + if (this.previousScrollRestoration) { + history.scrollRestoration = this.previousScrollRestoration; + delete this.previousScrollRestoration; } - notifyApplicationAfterRender() { - return dispatch("turbo:render"); + } + shouldHandlePopState() { + return this.pageIsLoaded(); + } + pageIsLoaded() { + return this.pageLoaded || document.readyState == "complete"; + } +} + +class Navigator { + constructor(delegate) { + this.delegate = delegate; + } + proposeVisit(location, options = {}) { + if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) { + if (locationIsVisitable(location, this.view.snapshot.rootLocation)) { + this.delegate.visitProposedToLocation(location, options); + } + else { + window.location.href = location.toString(); + } } - notifyApplicationAfterPageLoad(timing = {}) { - return dispatch("turbo:load", { - detail: { url: this.location.href, timing }, - }); + } + startVisit(locatable, restorationIdentifier, options = {}) { + this.stop(); + this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options)); + this.currentVisit.start(); + } + submitForm(form, submitter) { + this.stop(); + this.formSubmission = new FormSubmission(this, form, submitter, true); + this.formSubmission.start(); + } + stop() { + if (this.formSubmission) { + this.formSubmission.stop(); + delete this.formSubmission; } - notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) { - dispatchEvent(new HashChangeEvent("hashchange", { - oldURL: oldURL.toString(), - newURL: newURL.toString(), - })); + if (this.currentVisit) { + this.currentVisit.cancel(); + delete this.currentVisit; } - notifyApplicationAfterFrameLoad(frame) { - return dispatch("turbo:frame-load", { target: frame }); + } + get adapter() { + return this.delegate.adapter; + } + get view() { + return this.delegate.view; + } + get history() { + return this.delegate.history; + } + formSubmissionStarted(formSubmission) { + if (typeof this.adapter.formSubmissionStarted === "function") { + this.adapter.formSubmissionStarted(formSubmission); } - notifyApplicationAfterFrameRender(fetchResponse, frame) { - return dispatch("turbo:frame-render", { - detail: { fetchResponse }, - target: frame, - cancelable: true, - }); + } + async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) { + if (formSubmission == this.formSubmission) { + const responseHTML = await fetchResponse.responseHTML; + if (responseHTML) { + const shouldCacheSnapshot = formSubmission.isSafe; + if (!shouldCacheSnapshot) { + this.view.clearSnapshotCache(); + } + const { statusCode, redirected } = fetchResponse; + const action = this.getActionForFormSubmission(formSubmission); + const visitOptions = { + action, + shouldCacheSnapshot, + response: { statusCode, responseHTML, redirected }, + }; + this.proposeVisit(fetchResponse.location, visitOptions); + } } - submissionIsNavigatable(form, submitter) { - if (this.formMode == "off") { - return false; + } + async formSubmissionFailedWithResponse(formSubmission, fetchResponse) { + const responseHTML = await fetchResponse.responseHTML; + if (responseHTML) { + const snapshot = PageSnapshot.fromHTMLString(responseHTML); + if (fetchResponse.serverError) { + await this.view.renderError(snapshot, this.currentVisit); } else { - const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true; - if (this.formMode == "optin") { - return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null; - } - else { - return submitterIsNavigatable && this.elementIsNavigatable(form); - } + await this.view.renderPage(snapshot, false, true, this.currentVisit); } + this.view.scrollToTop(); + this.view.clearSnapshotCache(); } - elementIsNavigatable(element) { - const container = findClosestRecursively(element, "[data-turbo]"); - const withinFrame = findClosestRecursively(element, "turbo-frame"); - if (this.drive || withinFrame) { - if (container) { - return container.getAttribute("data-turbo") != "false"; - } - else { - return true; - } + } + formSubmissionErrored(formSubmission, error) { + console.error(error); + } + formSubmissionFinished(formSubmission) { + if (typeof this.adapter.formSubmissionFinished === "function") { + this.adapter.formSubmissionFinished(formSubmission); + } + } + visitStarted(visit) { + this.delegate.visitStarted(visit); + } + visitCompleted(visit) { + this.delegate.visitCompleted(visit); + } + locationWithActionIsSamePage(location, action) { + const anchor = getAnchor(location); + const currentAnchor = getAnchor(this.view.lastRenderedLocation); + const isRestorationToTop = action === "restore" && typeof anchor === "undefined"; + return (action !== "replace" && + getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && + (isRestorationToTop || (anchor != null && anchor !== currentAnchor))); + } + visitScrolledToSamePageLocation(oldURL, newURL) { + this.delegate.visitScrolledToSamePageLocation(oldURL, newURL); + } + get location() { + return this.history.location; + } + get restorationIdentifier() { + return this.history.restorationIdentifier; + } + getActionForFormSubmission({ submitter, formElement }) { + return getVisitAction(submitter, formElement) || "advance"; + } +} + +var PageStage; +(function (PageStage) { + PageStage[PageStage["initial"] = 0] = "initial"; + PageStage[PageStage["loading"] = 1] = "loading"; + PageStage[PageStage["interactive"] = 2] = "interactive"; + PageStage[PageStage["complete"] = 3] = "complete"; +})(PageStage || (PageStage = {})); +class PageObserver { + constructor(delegate) { + this.stage = PageStage.initial; + this.started = false; + this.interpretReadyState = () => { + const { readyState } = this; + if (readyState == "interactive") { + this.pageIsInteractive(); + } + else if (readyState == "complete") { + this.pageIsComplete(); } - else { - if (container) { - return container.getAttribute("data-turbo") == "true"; - } - else { - return false; - } + }; + this.pageWillUnload = () => { + this.delegate.pageWillUnload(); + }; + this.delegate = delegate; + } + start() { + if (!this.started) { + if (this.stage == PageStage.initial) { + this.stage = PageStage.loading; } + document.addEventListener("readystatechange", this.interpretReadyState, false); + addEventListener("pagehide", this.pageWillUnload, false); + this.started = true; } - getActionForLink(link) { - return getVisitAction(link) || "advance"; + } + stop() { + if (this.started) { + document.removeEventListener("readystatechange", this.interpretReadyState, false); + removeEventListener("pagehide", this.pageWillUnload, false); + this.started = false; } - get snapshot() { - return this.view.snapshot; + } + pageIsInteractive() { + if (this.stage == PageStage.loading) { + this.stage = PageStage.interactive; + this.delegate.pageBecameInteractive(); } } - function extendURLWithDeprecatedProperties(url) { - Object.defineProperties(url, deprecatedLocationPropertyDescriptors); + pageIsComplete() { + this.pageIsInteractive(); + if (this.stage == PageStage.interactive) { + this.stage = PageStage.complete; + this.delegate.pageLoaded(); + } } - const deprecatedLocationPropertyDescriptors = { - absoluteURL: { - get() { - return this.toString(); - }, - }, - }; + get readyState() { + return document.readyState; + } +} - class Cache { - constructor(session) { - this.session = session; +class ScrollObserver { + constructor(delegate) { + this.started = false; + this.onScroll = () => { + this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset }); + }; + this.delegate = delegate; + } + start() { + if (!this.started) { + addEventListener("scroll", this.onScroll, false); + this.onScroll(); + this.started = true; } - clear() { - this.session.clearCache(); + } + stop() { + if (this.started) { + removeEventListener("scroll", this.onScroll, false); + this.started = false; } - resetCacheControl() { - this.setCacheControl(""); + } + updatePosition(position) { + this.delegate.scrollPositionChanged(position); + } +} + +class StreamMessageRenderer { + render({ fragment }) { + Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment)); + } + enteringBardo(currentPermanentElement, newPermanentElement) { + newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true)); + } + leavingBardo() { } +} +function getPermanentElementMapForFragment(fragment) { + const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement); + const permanentElementMap = {}; + for (const permanentElementInDocument of permanentElementsInDocument) { + const { id } = permanentElementInDocument; + for (const streamElement of fragment.querySelectorAll("turbo-stream")) { + const elementInStream = getPermanentElementById(streamElement.templateElement.content, id); + if (elementInStream) { + permanentElementMap[id] = [permanentElementInDocument, elementInStream]; + } } - exemptPageFromCache() { - this.setCacheControl("no-cache"); + } + return permanentElementMap; +} + +class StreamObserver { + constructor(delegate) { + this.sources = new Set(); + this.started = false; + this.inspectFetchResponse = ((event) => { + const response = fetchResponseFromEvent(event); + if (response && fetchResponseIsStream(response)) { + event.preventDefault(); + this.receiveMessageResponse(response); + } + }); + this.receiveMessageEvent = (event) => { + if (this.started && typeof event.data == "string") { + this.receiveMessageHTML(event.data); + } + }; + this.delegate = delegate; + } + start() { + if (!this.started) { + this.started = true; + addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); } - exemptPageFromPreview() { - this.setCacheControl("no-preview"); + } + stop() { + if (this.started) { + this.started = false; + removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); } - setCacheControl(value) { - setMetaContent("turbo-cache-control", value); + } + connectStreamSource(source) { + if (!this.streamSourceIsConnected(source)) { + this.sources.add(source); + source.addEventListener("message", this.receiveMessageEvent, false); } } - - const StreamActions = { - after() { - this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); }); - }, - append() { - this.removeDuplicateTargetChildren(); - this.targetElements.forEach((e) => e.append(this.templateContent)); - }, - before() { - this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); }); - }, - prepend() { - this.removeDuplicateTargetChildren(); - this.targetElements.forEach((e) => e.prepend(this.templateContent)); - }, - remove() { - this.targetElements.forEach((e) => e.remove()); - }, - replace() { - this.targetElements.forEach((e) => e.replaceWith(this.templateContent)); - }, - update() { - this.targetElements.forEach((targetElement) => { - targetElement.innerHTML = ""; - targetElement.append(this.templateContent); - }); - }, - }; - - const session = new Session(); - const cache = new Cache(session); - const { navigator: navigator$1 } = session; - function start() { - session.start(); + disconnectStreamSource(source) { + if (this.streamSourceIsConnected(source)) { + this.sources.delete(source); + source.removeEventListener("message", this.receiveMessageEvent, false); + } + } + streamSourceIsConnected(source) { + return this.sources.has(source); } - function registerAdapter(adapter) { - session.registerAdapter(adapter); + async receiveMessageResponse(response) { + const html = await response.responseHTML; + if (html) { + this.receiveMessageHTML(html); + } } - function visit(location, options) { - session.visit(location, options); + receiveMessageHTML(html) { + this.delegate.receivedMessageFromStream(StreamMessage.wrap(html)); } - function connectStreamSource(source) { - session.connectStreamSource(source); +} +function fetchResponseFromEvent(event) { + var _a; + const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse; + if (fetchResponse instanceof FetchResponse) { + return fetchResponse; } - function disconnectStreamSource(source) { - session.disconnectStreamSource(source); +} +function fetchResponseIsStream(response) { + var _a; + const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : ""; + return contentType.startsWith(StreamMessage.contentType); +} + +class ErrorRenderer extends Renderer { + static renderElement(currentElement, newElement) { + const { documentElement, body } = document; + documentElement.replaceChild(newElement, body); } - function renderStreamMessage(message) { - session.renderStreamMessage(message); + async render() { + this.replaceHeadAndBody(); + this.activateScriptElements(); } - function clearCache() { - console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"); - session.clearCache(); + replaceHeadAndBody() { + const { documentElement, head } = document; + documentElement.replaceChild(this.newHead, head); + this.renderElement(this.currentElement, this.newElement); } - function setProgressBarDelay(delay) { - session.setProgressBarDelay(delay); + activateScriptElements() { + for (const replaceableElement of this.scriptElements) { + const parentNode = replaceableElement.parentNode; + if (parentNode) { + const element = activateScriptElement(replaceableElement); + parentNode.replaceChild(element, replaceableElement); + } + } } - function setConfirmMethod(confirmMethod) { - FormSubmission.confirmMethod = confirmMethod; + get newHead() { + return this.newSnapshot.headSnapshot.element; } - function setFormMode(mode) { - session.setFormMode(mode); + get scriptElements() { + return document.documentElement.querySelectorAll("script"); } +} - var Turbo = /*#__PURE__*/Object.freeze({ - __proto__: null, - navigator: navigator$1, - session: session, - cache: cache, - PageRenderer: PageRenderer, - PageSnapshot: PageSnapshot, - FrameRenderer: FrameRenderer, - start: start, - registerAdapter: registerAdapter, - visit: visit, - connectStreamSource: connectStreamSource, - disconnectStreamSource: disconnectStreamSource, - renderStreamMessage: renderStreamMessage, - clearCache: clearCache, - setProgressBarDelay: setProgressBarDelay, - setConfirmMethod: setConfirmMethod, - setFormMode: setFormMode, - StreamActions: StreamActions - }); - - class TurboFrameMissingError extends Error { +class PageRenderer extends Renderer { + static renderElement(currentElement, newElement) { + if (document.body && newElement instanceof HTMLBodyElement) { + document.body.replaceWith(newElement); + } + else { + document.documentElement.appendChild(newElement); + } } - - class FrameController { - constructor(element) { - this.fetchResponseLoaded = (_fetchResponse) => { }; - this.currentFetchRequest = null; - this.resolveVisitPromise = () => { }; - this.connected = false; - this.hasBeenLoaded = false; - this.ignoredAttributes = new Set(); - this.action = null; - this.visitCachedSnapshot = ({ element }) => { - const frame = element.querySelector("#" + this.element.id); - if (frame && this.previousFrameElement) { - frame.replaceChildren(...this.previousFrameElement.children); - } - delete this.previousFrameElement; + get shouldRender() { + return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical; + } + get reloadReason() { + if (!this.newSnapshot.isVisitable) { + return { + reason: "turbo_visit_control_is_reload", }; - this.element = element; - this.view = new FrameView(this, this.element); - this.appearanceObserver = new AppearanceObserver(this, this.element); - this.formLinkClickObserver = new FormLinkClickObserver(this, this.element); - this.linkInterceptor = new LinkInterceptor(this, this.element); - this.restorationIdentifier = uuid(); - this.formSubmitObserver = new FormSubmitObserver(this, this.element); - } - connect() { - if (!this.connected) { - this.connected = true; - if (this.loadingStyle == exports.FrameLoadingStyle.lazy) { - this.appearanceObserver.start(); - } - else { - this.loadSourceURL(); - } - this.formLinkClickObserver.start(); - this.linkInterceptor.start(); - this.formSubmitObserver.start(); - } } - disconnect() { - if (this.connected) { - this.connected = false; - this.appearanceObserver.stop(); - this.formLinkClickObserver.stop(); - this.linkInterceptor.stop(); - this.formSubmitObserver.stop(); - } + if (!this.trackedElementsAreIdentical) { + return { + reason: "tracked_element_mismatch", + }; } - disabledChanged() { - if (this.loadingStyle == exports.FrameLoadingStyle.eager) { - this.loadSourceURL(); - } + } + async prepareToRender() { + await this.mergeHead(); + } + async render() { + if (this.willRender) { + await this.replaceBody(); } - sourceURLChanged() { - if (this.isIgnoringChangesTo("src")) - return; - if (this.element.isConnected) { - this.complete = false; - } - if (this.loadingStyle == exports.FrameLoadingStyle.eager || this.hasBeenLoaded) { - this.loadSourceURL(); - } + } + finishRendering() { + super.finishRendering(); + if (!this.isPreview) { + this.focusFirstAutofocusableElement(); } - sourceURLReloaded() { - const { src } = this.element; - this.ignoringChangesToAttribute("complete", () => { - this.element.removeAttribute("complete"); - }); - this.element.src = null; - this.element.src = src; - return this.element.loaded; + } + get currentHeadSnapshot() { + return this.currentSnapshot.headSnapshot; + } + get newHeadSnapshot() { + return this.newSnapshot.headSnapshot; + } + get newElement() { + return this.newSnapshot.element; + } + async mergeHead() { + const mergedHeadElements = this.mergeProvisionalElements(); + const newStylesheetElements = this.copyNewHeadStylesheetElements(); + this.copyNewHeadScriptElements(); + await mergedHeadElements; + await newStylesheetElements; + } + async replaceBody() { + await this.preservingPermanentElements(async () => { + this.activateNewBody(); + await this.assignNewBody(); + }); + } + get trackedElementsAreIdentical() { + return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature; + } + async copyNewHeadStylesheetElements() { + const loadingElements = []; + for (const element of this.newHeadStylesheetElements) { + loadingElements.push(waitForLoad(element)); + document.head.appendChild(element); } - completeChanged() { - if (this.isIgnoringChangesTo("complete")) - return; - this.loadSourceURL(); + await Promise.all(loadingElements); + } + copyNewHeadScriptElements() { + for (const element of this.newHeadScriptElements) { + document.head.appendChild(activateScriptElement(element)); } - loadingStyleChanged() { - if (this.loadingStyle == exports.FrameLoadingStyle.lazy) { - this.appearanceObserver.start(); - } - else { - this.appearanceObserver.stop(); - this.loadSourceURL(); + } + async mergeProvisionalElements() { + const newHeadElements = [...this.newHeadProvisionalElements]; + for (const element of this.currentHeadProvisionalElements) { + if (!this.isCurrentElementInElementList(element, newHeadElements)) { + document.head.removeChild(element); } } - async loadSourceURL() { - if (this.enabled && this.isActive && !this.complete && this.sourceURL) { - this.element.loaded = this.visit(expandURL(this.sourceURL)); - this.appearanceObserver.stop(); - await this.element.loaded; - this.hasBeenLoaded = true; - } + for (const element of newHeadElements) { + document.head.appendChild(element); } - async loadResponse(fetchResponse) { - if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) { - this.sourceURL = fetchResponse.response.url; - } - try { - const html = await fetchResponse.responseHTML; - if (html) { - const document = parseHTMLDocument(html); - const pageSnapshot = PageSnapshot.fromDocument(document); - if (pageSnapshot.isVisitable) { - await this.loadFrameResponse(fetchResponse, document); - } - else { - await this.handleUnvisitableFrameResponse(fetchResponse); - } + } + isCurrentElementInElementList(element, elementList) { + for (const [index, newElement] of elementList.entries()) { + if (element.tagName == "TITLE") { + if (newElement.tagName != "TITLE") { + continue; + } + if (element.innerHTML == newElement.innerHTML) { + elementList.splice(index, 1); + return true; } } - finally { - this.fetchResponseLoaded = () => { }; + if (newElement.isEqualNode(element)) { + elementList.splice(index, 1); + return true; } } - elementAppearedInViewport(element) { - this.proposeVisitIfNavigatedWithAction(element, element); - this.loadSourceURL(); + return false; + } + removeCurrentHeadProvisionalElements() { + for (const element of this.currentHeadProvisionalElements) { + document.head.removeChild(element); } - willSubmitFormLinkToLocation(link) { - return this.shouldInterceptNavigation(link); + } + copyNewHeadProvisionalElements() { + for (const element of this.newHeadProvisionalElements) { + document.head.appendChild(element); } - submittedFormLinkToLocation(link, _location, form) { - const frame = this.findFrameElement(link); - if (frame) - form.setAttribute("data-turbo-frame", frame.id); + } + activateNewBody() { + document.adoptNode(this.newElement); + this.activateNewBodyScriptElements(); + } + activateNewBodyScriptElements() { + for (const inertScriptElement of this.newBodyScriptElements) { + const activatedScriptElement = activateScriptElement(inertScriptElement); + inertScriptElement.replaceWith(activatedScriptElement); } - shouldInterceptLinkClick(element, _location, _event) { - return this.shouldInterceptNavigation(element); + } + async assignNewBody() { + await this.renderElement(this.currentElement, this.newElement); + } + get newHeadStylesheetElements() { + return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot); + } + get newHeadScriptElements() { + return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot); + } + get currentHeadProvisionalElements() { + return this.currentHeadSnapshot.provisionalElements; + } + get newHeadProvisionalElements() { + return this.newHeadSnapshot.provisionalElements; + } + get newBodyScriptElements() { + return this.newElement.querySelectorAll("script"); + } +} + +class SnapshotCache { + constructor(size) { + this.keys = []; + this.snapshots = {}; + this.size = size; + } + has(location) { + return toCacheKey(location) in this.snapshots; + } + get(location) { + if (this.has(location)) { + const snapshot = this.read(location); + this.touch(location); + return snapshot; } - linkClickIntercepted(element, location) { - this.navigateFrame(element, location); + } + put(location, snapshot) { + this.write(location, snapshot); + this.touch(location); + return snapshot; + } + clear() { + this.snapshots = {}; + } + read(location) { + return this.snapshots[toCacheKey(location)]; + } + write(location, snapshot) { + this.snapshots[toCacheKey(location)] = snapshot; + } + touch(location) { + const key = toCacheKey(location); + const index = this.keys.indexOf(key); + if (index > -1) + this.keys.splice(index, 1); + this.keys.unshift(key); + this.trim(); + } + trim() { + for (const key of this.keys.splice(this.size)) { + delete this.snapshots[key]; } - willSubmitForm(element, submitter) { - return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter); + } +} + +class PageView extends View { + constructor() { + super(...arguments); + this.snapshotCache = new SnapshotCache(10); + this.lastRenderedLocation = new URL(location.href); + this.forceReloaded = false; + } + renderPage(snapshot, isPreview = false, willRender = true, visit) { + const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender); + if (!renderer.shouldRender) { + this.forceReloaded = true; } - formSubmitted(element, submitter) { - if (this.formSubmission) { - this.formSubmission.stop(); - } - this.formSubmission = new FormSubmission(this, element, submitter); - const { fetchRequest } = this.formSubmission; - this.prepareRequest(fetchRequest); - this.formSubmission.start(); + else { + visit === null || visit === void 0 ? void 0 : visit.changeHistory(); } - prepareRequest(request) { - var _a; - request.headers["Turbo-Frame"] = this.id; - if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) { - request.acceptResponseType(StreamMessage.contentType); - } + return this.render(renderer); + } + renderError(snapshot, visit) { + visit === null || visit === void 0 ? void 0 : visit.changeHistory(); + const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false); + return this.render(renderer); + } + clearSnapshotCache() { + this.snapshotCache.clear(); + } + async cacheSnapshot(snapshot = this.snapshot) { + if (snapshot.isCacheable) { + this.delegate.viewWillCacheSnapshot(); + const { lastRenderedLocation: location } = this; + await nextEventLoopTick(); + const cachedSnapshot = snapshot.clone(); + this.snapshotCache.put(location, cachedSnapshot); + return cachedSnapshot; } - requestStarted(_request) { - markAsBusy(this.element); + } + getCachedSnapshotForLocation(location) { + return this.snapshotCache.get(location); + } + get snapshot() { + return PageSnapshot.fromElement(this.element); + } +} + +class Preloader { + constructor(delegate) { + this.selector = "a[data-turbo-preload]"; + this.delegate = delegate; + } + get snapshotCache() { + return this.delegate.navigator.view.snapshotCache; + } + start() { + if (document.readyState === "loading") { + return document.addEventListener("DOMContentLoaded", () => { + this.preloadOnLoadLinksForView(document.body); + }); } - requestPreventedHandlingResponse(_request, _response) { - this.resolveVisitPromise(); + else { + this.preloadOnLoadLinksForView(document.body); } - async requestSucceededWithResponse(request, response) { - await this.loadResponse(response); - this.resolveVisitPromise(); + } + preloadOnLoadLinksForView(element) { + for (const link of element.querySelectorAll(this.selector)) { + this.preloadURL(link); } - async requestFailedWithResponse(request, response) { - await this.loadResponse(response); - this.resolveVisitPromise(); + } + async preloadURL(link) { + const location = new URL(link.href); + if (this.snapshotCache.has(location)) { + return; } - requestErrored(request, error) { - console.error(error); - this.resolveVisitPromise(); + try { + const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } }); + const responseText = await response.text(); + const snapshot = PageSnapshot.fromHTMLString(responseText); + this.snapshotCache.put(location, snapshot); } - requestFinished(_request) { - clearBusyState(this.element); + catch (_) { } - formSubmissionStarted({ formElement }) { - markAsBusy(formElement, this.findFrameElement(formElement)); + } +} + +class Session { + constructor() { + this.navigator = new Navigator(this); + this.history = new History(this); + this.preloader = new Preloader(this); + this.view = new PageView(this, document.documentElement); + this.adapter = new BrowserAdapter(this); + this.pageObserver = new PageObserver(this); + this.cacheObserver = new CacheObserver(); + this.linkClickObserver = new LinkClickObserver(this, window); + this.formSubmitObserver = new FormSubmitObserver(this, document); + this.scrollObserver = new ScrollObserver(this); + this.streamObserver = new StreamObserver(this); + this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement); + this.frameRedirector = new FrameRedirector(this, document.documentElement); + this.streamMessageRenderer = new StreamMessageRenderer(); + this.drive = true; + this.enabled = true; + this.progressBarDelay = 500; + this.started = false; + this.formMode = "on"; + } + start() { + if (!this.started) { + this.pageObserver.start(); + this.cacheObserver.start(); + this.formLinkClickObserver.start(); + this.linkClickObserver.start(); + this.formSubmitObserver.start(); + this.scrollObserver.start(); + this.streamObserver.start(); + this.frameRedirector.start(); + this.history.start(); + this.preloader.start(); + this.started = true; + this.enabled = true; } - formSubmissionSucceededWithResponse(formSubmission, response) { - const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter); - frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter); - frame.delegate.loadResponse(response); - if (!formSubmission.isSafe) { - session.clearCache(); - } + } + disable() { + this.enabled = false; + } + stop() { + if (this.started) { + this.pageObserver.stop(); + this.cacheObserver.stop(); + this.formLinkClickObserver.stop(); + this.linkClickObserver.stop(); + this.formSubmitObserver.stop(); + this.scrollObserver.stop(); + this.streamObserver.stop(); + this.frameRedirector.stop(); + this.history.stop(); + this.started = false; } - formSubmissionFailedWithResponse(formSubmission, fetchResponse) { - this.element.delegate.loadResponse(fetchResponse); - session.clearCache(); + } + registerAdapter(adapter) { + this.adapter = adapter; + } + visit(location, options = {}) { + const frameElement = options.frame ? document.getElementById(options.frame) : null; + if (frameElement instanceof FrameElement) { + frameElement.src = location.toString(); + frameElement.loaded; } - formSubmissionErrored(formSubmission, error) { - console.error(error); + else { + this.navigator.proposeVisit(expandURL(location), options); } - formSubmissionFinished({ formElement }) { - clearBusyState(formElement, this.findFrameElement(formElement)); + } + connectStreamSource(source) { + this.streamObserver.connectStreamSource(source); + } + disconnectStreamSource(source) { + this.streamObserver.disconnectStreamSource(source); + } + renderStreamMessage(message) { + this.streamMessageRenderer.render(StreamMessage.wrap(message)); + } + clearCache() { + this.view.clearSnapshotCache(); + } + setProgressBarDelay(delay) { + this.progressBarDelay = delay; + } + setFormMode(mode) { + this.formMode = mode; + } + get location() { + return this.history.location; + } + get restorationIdentifier() { + return this.history.restorationIdentifier; + } + historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) { + if (this.enabled) { + this.navigator.startVisit(location, restorationIdentifier, { + action: "restore", + historyChanged: true, + }); } - allowsImmediateRender({ element: newFrame }, options) { - const event = dispatch("turbo:before-frame-render", { - target: this.element, - detail: Object.assign({ newFrame }, options), - cancelable: true, + else { + this.adapter.pageInvalidated({ + reason: "turbo_disabled", }); - const { defaultPrevented, detail: { render }, } = event; - if (this.view.renderer && render) { - this.view.renderer.renderElement = render; - } - return !defaultPrevented; } - viewRenderedSnapshot(_snapshot, _isPreview) { } - preloadOnLoadLinksForView(element) { - session.preloadOnLoadLinksForView(element); + } + scrollPositionChanged(position) { + this.history.updateRestorationData({ scrollPosition: position }); + } + willSubmitFormLinkToLocation(link, location) { + return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation); + } + submittedFormLinkToLocation() { } + willFollowLinkToLocation(link, location, event) { + return (this.elementIsNavigatable(link) && + locationIsVisitable(location, this.snapshot.rootLocation) && + this.applicationAllowsFollowingLinkToLocation(link, location, event)); + } + followedLinkToLocation(link, location) { + const action = this.getActionForLink(link); + const acceptsStreamResponse = link.hasAttribute("data-turbo-stream"); + this.visit(location.href, { action, acceptsStreamResponse }); + } + allowsVisitingLocationWithAction(location, action) { + return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location); + } + visitProposedToLocation(location, options) { + extendURLWithDeprecatedProperties(location); + this.adapter.visitProposedToLocation(location, options); + } + visitStarted(visit) { + if (!visit.acceptsStreamResponse) { + markAsBusy(document.documentElement); + } + extendURLWithDeprecatedProperties(visit.location); + if (!visit.silent) { + this.notifyApplicationAfterVisitingLocation(visit.location, visit.action); } - viewInvalidated() { } - willRenderFrame(currentElement, _newElement) { - this.previousFrameElement = currentElement.cloneNode(true); + } + visitCompleted(visit) { + clearBusyState(document.documentElement); + this.notifyApplicationAfterPageLoad(visit.getTimingMetrics()); + } + locationWithActionIsSamePage(location, action) { + return this.navigator.locationWithActionIsSamePage(location, action); + } + visitScrolledToSamePageLocation(oldURL, newURL) { + this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL); + } + willSubmitForm(form, submitter) { + const action = getAction(form, submitter); + return (this.submissionIsNavigatable(form, submitter) && + locationIsVisitable(expandURL(action), this.snapshot.rootLocation)); + } + formSubmitted(form, submitter) { + this.navigator.submitForm(form, submitter); + } + pageBecameInteractive() { + this.view.lastRenderedLocation = this.location; + this.notifyApplicationAfterPageLoad(); + } + pageLoaded() { + this.history.assumeControlOfScrollRestoration(); + } + pageWillUnload() { + this.history.relinquishControlOfScrollRestoration(); + } + receivedMessageFromStream(message) { + this.renderStreamMessage(message); + } + viewWillCacheSnapshot() { + var _a; + if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) { + this.notifyApplicationBeforeCachingSnapshot(); } - async loadFrameResponse(fetchResponse, document) { - const newFrameElement = await this.extractForeignFrameElement(document.body); - if (newFrameElement) { - const snapshot = new Snapshot(newFrameElement); - const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false); - if (this.view.renderPromise) - await this.view.renderPromise; - this.changeHistory(); - await this.view.render(renderer); - this.complete = true; - session.frameRendered(fetchResponse, this.element); - session.frameLoaded(this.element); - this.fetchResponseLoaded(fetchResponse); - } - else if (this.willHandleFrameMissingFromResponse(fetchResponse)) { - this.handleFrameMissingFromResponse(fetchResponse); - } - } - async visit(url) { - var _a; - const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element); - (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel(); - this.currentFetchRequest = request; - return new Promise((resolve) => { - this.resolveVisitPromise = () => { - this.resolveVisitPromise = () => { }; - this.currentFetchRequest = null; - resolve(); - }; - request.perform(); - }); + } + allowsImmediateRender({ element }, options) { + const event = this.notifyApplicationBeforeRender(element, options); + const { defaultPrevented, detail: { render }, } = event; + if (this.view.renderer && render) { + this.view.renderer.renderElement = render; } - navigateFrame(element, url, submitter) { - const frame = this.findFrameElement(element, submitter); - frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter); - this.withCurrentNavigationElement(element, () => { - frame.src = url; - }); + return !defaultPrevented; + } + viewRenderedSnapshot(_snapshot, _isPreview) { + this.view.lastRenderedLocation = this.history.location; + this.notifyApplicationAfterRender(); + } + preloadOnLoadLinksForView(element) { + this.preloader.preloadOnLoadLinksForView(element); + } + viewInvalidated(reason) { + this.adapter.pageInvalidated(reason); + } + frameLoaded(frame) { + this.notifyApplicationAfterFrameLoad(frame); + } + frameRendered(fetchResponse, frame) { + this.notifyApplicationAfterFrameRender(fetchResponse, frame); + } + applicationAllowsFollowingLinkToLocation(link, location, ev) { + const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev); + return !event.defaultPrevented; + } + applicationAllowsVisitingLocation(location) { + const event = this.notifyApplicationBeforeVisitingLocation(location); + return !event.defaultPrevented; + } + notifyApplicationAfterClickingLinkToLocation(link, location, event) { + return dispatch("turbo:click", { + target: link, + detail: { url: location.href, originalEvent: event }, + cancelable: true, + }); + } + notifyApplicationBeforeVisitingLocation(location) { + return dispatch("turbo:before-visit", { + detail: { url: location.href }, + cancelable: true, + }); + } + notifyApplicationAfterVisitingLocation(location, action) { + return dispatch("turbo:visit", { detail: { url: location.href, action } }); + } + notifyApplicationBeforeCachingSnapshot() { + return dispatch("turbo:before-cache"); + } + notifyApplicationBeforeRender(newBody, options) { + return dispatch("turbo:before-render", { + detail: Object.assign({ newBody }, options), + cancelable: true, + }); + } + notifyApplicationAfterRender() { + return dispatch("turbo:render"); + } + notifyApplicationAfterPageLoad(timing = {}) { + return dispatch("turbo:load", { + detail: { url: this.location.href, timing }, + }); + } + notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) { + dispatchEvent(new HashChangeEvent("hashchange", { + oldURL: oldURL.toString(), + newURL: newURL.toString(), + })); + } + notifyApplicationAfterFrameLoad(frame) { + return dispatch("turbo:frame-load", { target: frame }); + } + notifyApplicationAfterFrameRender(fetchResponse, frame) { + return dispatch("turbo:frame-render", { + detail: { fetchResponse }, + target: frame, + cancelable: true, + }); + } + submissionIsNavigatable(form, submitter) { + if (this.formMode == "off") { + return false; } - proposeVisitIfNavigatedWithAction(frame, element, submitter) { - this.action = getVisitAction(submitter, element, frame); - if (this.action) { - const pageSnapshot = PageSnapshot.fromElement(frame).clone(); - const { visitCachedSnapshot } = frame.delegate; - frame.delegate.fetchResponseLoaded = (fetchResponse) => { - if (frame.src) { - const { statusCode, redirected } = fetchResponse; - const responseHTML = frame.ownerDocument.documentElement.outerHTML; - const response = { statusCode, redirected, responseHTML }; - const options = { - response, - visitCachedSnapshot, - willRender: false, - updateHistory: false, - restorationIdentifier: this.restorationIdentifier, - snapshot: pageSnapshot, - }; - if (this.action) - options.action = this.action; - session.visit(frame.src, options); - } - }; + else { + const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true; + if (this.formMode == "optin") { + return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null; } - } - changeHistory() { - if (this.action) { - const method = getHistoryMethodForAction(this.action); - session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier); + else { + return submitterIsNavigatable && this.elementIsNavigatable(form); } } - async handleUnvisitableFrameResponse(fetchResponse) { - console.warn(`The response (${fetchResponse.statusCode}) from is performing a full page visit due to turbo-visit-control.`); - await this.visitResponse(fetchResponse.response); - } - willHandleFrameMissingFromResponse(fetchResponse) { - this.element.setAttribute("complete", ""); - const response = fetchResponse.response; - const visit = async (url, options = {}) => { - if (url instanceof Response) { - this.visitResponse(url); - } - else { - session.visit(url, options); - } - }; - const event = dispatch("turbo:frame-missing", { - target: this.element, - detail: { response, visit }, - cancelable: true, - }); - return !event.defaultPrevented; - } - handleFrameMissingFromResponse(fetchResponse) { - this.view.missing(); - this.throwFrameMissingError(fetchResponse); - } - throwFrameMissingError(fetchResponse) { - const message = `The response (${fetchResponse.statusCode}) did not contain the expected and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`; - throw new TurboFrameMissingError(message); - } - async visitResponse(response) { - const wrapped = new FetchResponse(response); - const responseHTML = await wrapped.responseHTML; - const { location, redirected, statusCode } = wrapped; - return session.visit(location, { response: { redirected, statusCode, responseHTML } }); - } - findFrameElement(element, submitter) { - var _a; - const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target"); - return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element; - } - async extractForeignFrameElement(container) { - let element; - const id = CSS.escape(this.id); - try { - element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL); - if (element) { - return element; - } - element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL); - if (element) { - await element.loaded; - return await this.extractForeignFrameElement(element); - } + } + elementIsNavigatable(element) { + const container = findClosestRecursively(element, "[data-turbo]"); + const withinFrame = findClosestRecursively(element, "turbo-frame"); + if (this.drive || withinFrame) { + if (container) { + return container.getAttribute("data-turbo") != "false"; } - catch (error) { - console.error(error); - return new FrameElement(); + else { + return true; } - return null; - } - formActionIsVisitable(form, submitter) { - const action = getAction(form, submitter); - return locationIsVisitable(expandURL(action), this.rootLocation); } - shouldInterceptNavigation(element, submitter) { - const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target"); - if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) { - return false; + else { + if (container) { + return container.getAttribute("data-turbo") == "true"; } - if (!this.enabled || id == "_top") { + else { return false; } - if (id) { - const frameElement = getFrameElementById(id); - if (frameElement) { - return !frameElement.disabled; - } - } - if (!session.elementIsNavigatable(element)) { - return false; + } + } + getActionForLink(link) { + return getVisitAction(link) || "advance"; + } + get snapshot() { + return this.view.snapshot; + } +} +function extendURLWithDeprecatedProperties(url) { + Object.defineProperties(url, deprecatedLocationPropertyDescriptors); +} +const deprecatedLocationPropertyDescriptors = { + absoluteURL: { + get() { + return this.toString(); + }, + }, +}; + +class Cache { + constructor(session) { + this.session = session; + } + clear() { + this.session.clearCache(); + } + resetCacheControl() { + this.setCacheControl(""); + } + exemptPageFromCache() { + this.setCacheControl("no-cache"); + } + exemptPageFromPreview() { + this.setCacheControl("no-preview"); + } + setCacheControl(value) { + setMetaContent("turbo-cache-control", value); + } +} + +const StreamActions = { + after() { + this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); }); + }, + append() { + this.removeDuplicateTargetChildren(); + this.targetElements.forEach((e) => e.append(this.templateContent)); + }, + before() { + this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); }); + }, + prepend() { + this.removeDuplicateTargetChildren(); + this.targetElements.forEach((e) => e.prepend(this.templateContent)); + }, + remove() { + this.targetElements.forEach((e) => e.remove()); + }, + replace() { + this.targetElements.forEach((e) => e.replaceWith(this.templateContent)); + }, + update() { + this.targetElements.forEach((targetElement) => { + targetElement.innerHTML = ""; + targetElement.append(this.templateContent); + }); + }, +}; + +const session = new Session(); +const cache = new Cache(session); +const { navigator: navigator$1 } = session; +function start() { + session.start(); +} +function registerAdapter(adapter) { + session.registerAdapter(adapter); +} +function visit(location, options) { + session.visit(location, options); +} +function connectStreamSource(source) { + session.connectStreamSource(source); +} +function disconnectStreamSource(source) { + session.disconnectStreamSource(source); +} +function renderStreamMessage(message) { + session.renderStreamMessage(message); +} +function clearCache() { + console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"); + session.clearCache(); +} +function setProgressBarDelay(delay) { + session.setProgressBarDelay(delay); +} +function setConfirmMethod(confirmMethod) { + FormSubmission.confirmMethod = confirmMethod; +} +function setFormMode(mode) { + session.setFormMode(mode); +} + +var Turbo = /*#__PURE__*/Object.freeze({ + __proto__: null, + navigator: navigator$1, + session: session, + cache: cache, + PageRenderer: PageRenderer, + PageSnapshot: PageSnapshot, + FrameRenderer: FrameRenderer, + start: start, + registerAdapter: registerAdapter, + visit: visit, + connectStreamSource: connectStreamSource, + disconnectStreamSource: disconnectStreamSource, + renderStreamMessage: renderStreamMessage, + clearCache: clearCache, + setProgressBarDelay: setProgressBarDelay, + setConfirmMethod: setConfirmMethod, + setFormMode: setFormMode, + StreamActions: StreamActions +}); + +class TurboFrameMissingError extends Error { +} + +class FrameController { + constructor(element) { + this.fetchResponseLoaded = (_fetchResponse) => { }; + this.currentFetchRequest = null; + this.resolveVisitPromise = () => { }; + this.connected = false; + this.hasBeenLoaded = false; + this.ignoredAttributes = new Set(); + this.action = null; + this.visitCachedSnapshot = ({ element }) => { + const frame = element.querySelector("#" + this.element.id); + if (frame && this.previousFrameElement) { + frame.replaceChildren(...this.previousFrameElement.children); + } + delete this.previousFrameElement; + }; + this.element = element; + this.view = new FrameView(this, this.element); + this.appearanceObserver = new AppearanceObserver(this, this.element); + this.formLinkClickObserver = new FormLinkClickObserver(this, this.element); + this.linkInterceptor = new LinkInterceptor(this, this.element); + this.restorationIdentifier = uuid(); + this.formSubmitObserver = new FormSubmitObserver(this, this.element); + } + connect() { + if (!this.connected) { + this.connected = true; + if (this.loadingStyle == FrameLoadingStyle.lazy) { + this.appearanceObserver.start(); } - if (submitter && !session.elementIsNavigatable(submitter)) { - return false; + else { + this.loadSourceURL(); } - return true; + this.formLinkClickObserver.start(); + this.linkInterceptor.start(); + this.formSubmitObserver.start(); } - get id() { - return this.element.id; + } + disconnect() { + if (this.connected) { + this.connected = false; + this.appearanceObserver.stop(); + this.formLinkClickObserver.stop(); + this.linkInterceptor.stop(); + this.formSubmitObserver.stop(); } - get enabled() { - return !this.element.disabled; + } + disabledChanged() { + if (this.loadingStyle == FrameLoadingStyle.eager) { + this.loadSourceURL(); } - get sourceURL() { - if (this.element.src) { - return this.element.src; - } + } + sourceURLChanged() { + if (this.isIgnoringChangesTo("src")) + return; + if (this.element.isConnected) { + this.complete = false; } - set sourceURL(sourceURL) { - this.ignoringChangesToAttribute("src", () => { - this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null; - }); + if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) { + this.loadSourceURL(); + } + } + sourceURLReloaded() { + const { src } = this.element; + this.ignoringChangesToAttribute("complete", () => { + this.element.removeAttribute("complete"); + }); + this.element.src = null; + this.element.src = src; + return this.element.loaded; + } + completeChanged() { + if (this.isIgnoringChangesTo("complete")) + return; + this.loadSourceURL(); + } + loadingStyleChanged() { + if (this.loadingStyle == FrameLoadingStyle.lazy) { + this.appearanceObserver.start(); } - get loadingStyle() { - return this.element.loading; + else { + this.appearanceObserver.stop(); + this.loadSourceURL(); } - get isLoading() { - return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined; + } + async loadSourceURL() { + if (this.enabled && this.isActive && !this.complete && this.sourceURL) { + this.element.loaded = this.visit(expandURL(this.sourceURL)); + this.appearanceObserver.stop(); + await this.element.loaded; + this.hasBeenLoaded = true; } - get complete() { - return this.element.hasAttribute("complete"); + } + async loadResponse(fetchResponse) { + if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) { + this.sourceURL = fetchResponse.response.url; } - set complete(value) { - this.ignoringChangesToAttribute("complete", () => { - if (value) { - this.element.setAttribute("complete", ""); + try { + const html = await fetchResponse.responseHTML; + if (html) { + const document = parseHTMLDocument(html); + const pageSnapshot = PageSnapshot.fromDocument(document); + if (pageSnapshot.isVisitable) { + await this.loadFrameResponse(fetchResponse, document); } else { - this.element.removeAttribute("complete"); + await this.handleUnvisitableFrameResponse(fetchResponse); } - }); + } } - get isActive() { - return this.element.isActive && this.connected; + finally { + this.fetchResponseLoaded = () => { }; } - get rootLocation() { - var _a; - const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); - const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/"; - return expandURL(root); + } + elementAppearedInViewport(element) { + this.proposeVisitIfNavigatedWithAction(element, element); + this.loadSourceURL(); + } + willSubmitFormLinkToLocation(link) { + return this.shouldInterceptNavigation(link); + } + submittedFormLinkToLocation(link, _location, form) { + const frame = this.findFrameElement(link); + if (frame) + form.setAttribute("data-turbo-frame", frame.id); + } + shouldInterceptLinkClick(element, _location, _event) { + return this.shouldInterceptNavigation(element); + } + linkClickIntercepted(element, location) { + this.navigateFrame(element, location); + } + willSubmitForm(element, submitter) { + return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter); + } + formSubmitted(element, submitter) { + if (this.formSubmission) { + this.formSubmission.stop(); } - isIgnoringChangesTo(attributeName) { - return this.ignoredAttributes.has(attributeName); + this.formSubmission = new FormSubmission(this, element, submitter); + const { fetchRequest } = this.formSubmission; + this.prepareRequest(fetchRequest); + this.formSubmission.start(); + } + prepareRequest(request) { + var _a; + request.headers["Turbo-Frame"] = this.id; + if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) { + request.acceptResponseType(StreamMessage.contentType); } - ignoringChangesToAttribute(attributeName, callback) { - this.ignoredAttributes.add(attributeName); - callback(); - this.ignoredAttributes.delete(attributeName); + } + requestStarted(_request) { + markAsBusy(this.element); + } + requestPreventedHandlingResponse(_request, _response) { + this.resolveVisitPromise(); + } + async requestSucceededWithResponse(request, response) { + await this.loadResponse(response); + this.resolveVisitPromise(); + } + async requestFailedWithResponse(request, response) { + await this.loadResponse(response); + this.resolveVisitPromise(); + } + requestErrored(request, error) { + console.error(error); + this.resolveVisitPromise(); + } + requestFinished(_request) { + clearBusyState(this.element); + } + formSubmissionStarted({ formElement }) { + markAsBusy(formElement, this.findFrameElement(formElement)); + } + formSubmissionSucceededWithResponse(formSubmission, response) { + const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter); + frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter); + frame.delegate.loadResponse(response); + if (!formSubmission.isSafe) { + session.clearCache(); } - withCurrentNavigationElement(element, callback) { - this.currentNavigationElement = element; - callback(); - delete this.currentNavigationElement; + } + formSubmissionFailedWithResponse(formSubmission, fetchResponse) { + this.element.delegate.loadResponse(fetchResponse); + session.clearCache(); + } + formSubmissionErrored(formSubmission, error) { + console.error(error); + } + formSubmissionFinished({ formElement }) { + clearBusyState(formElement, this.findFrameElement(formElement)); + } + allowsImmediateRender({ element: newFrame }, options) { + const event = dispatch("turbo:before-frame-render", { + target: this.element, + detail: Object.assign({ newFrame }, options), + cancelable: true, + }); + const { defaultPrevented, detail: { render }, } = event; + if (this.view.renderer && render) { + this.view.renderer.renderElement = render; + } + return !defaultPrevented; + } + viewRenderedSnapshot(_snapshot, _isPreview) { } + preloadOnLoadLinksForView(element) { + session.preloadOnLoadLinksForView(element); + } + viewInvalidated() { } + willRenderFrame(currentElement, _newElement) { + this.previousFrameElement = currentElement.cloneNode(true); + } + async loadFrameResponse(fetchResponse, document) { + const newFrameElement = await this.extractForeignFrameElement(document.body); + if (newFrameElement) { + const snapshot = new Snapshot(newFrameElement); + const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false); + if (this.view.renderPromise) + await this.view.renderPromise; + this.changeHistory(); + await this.view.render(renderer); + this.complete = true; + session.frameRendered(fetchResponse, this.element); + session.frameLoaded(this.element); + this.fetchResponseLoaded(fetchResponse); + } + else if (this.willHandleFrameMissingFromResponse(fetchResponse)) { + this.handleFrameMissingFromResponse(fetchResponse); + } + } + async visit(url) { + var _a; + const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element); + (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel(); + this.currentFetchRequest = request; + return new Promise((resolve) => { + this.resolveVisitPromise = () => { + this.resolveVisitPromise = () => { }; + this.currentFetchRequest = null; + resolve(); + }; + request.perform(); + }); + } + navigateFrame(element, url, submitter) { + const frame = this.findFrameElement(element, submitter); + frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter); + this.withCurrentNavigationElement(element, () => { + frame.src = url; + }); + } + proposeVisitIfNavigatedWithAction(frame, element, submitter) { + this.action = getVisitAction(submitter, element, frame); + if (this.action) { + const pageSnapshot = PageSnapshot.fromElement(frame).clone(); + const { visitCachedSnapshot } = frame.delegate; + frame.delegate.fetchResponseLoaded = (fetchResponse) => { + if (frame.src) { + const { statusCode, redirected } = fetchResponse; + const responseHTML = frame.ownerDocument.documentElement.outerHTML; + const response = { statusCode, redirected, responseHTML }; + const options = { + response, + visitCachedSnapshot, + willRender: false, + updateHistory: false, + restorationIdentifier: this.restorationIdentifier, + snapshot: pageSnapshot, + }; + if (this.action) + options.action = this.action; + session.visit(frame.src, options); + } + }; } } - function getFrameElementById(id) { - if (id != null) { - const element = document.getElementById(id); - if (element instanceof FrameElement) { - return element; - } + changeHistory() { + if (this.action) { + const method = getHistoryMethodForAction(this.action); + session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier); } } - function activateElement(element, currentURL) { - if (element) { - const src = element.getAttribute("src"); - if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) { - throw new Error(`Matching element has a source URL which references itself`); + async handleUnvisitableFrameResponse(fetchResponse) { + console.warn(`The response (${fetchResponse.statusCode}) from is performing a full page visit due to turbo-visit-control.`); + await this.visitResponse(fetchResponse.response); + } + willHandleFrameMissingFromResponse(fetchResponse) { + this.element.setAttribute("complete", ""); + const response = fetchResponse.response; + const visit = async (url, options = {}) => { + if (url instanceof Response) { + this.visitResponse(url); } - if (element.ownerDocument !== document) { - element = document.importNode(element, true); + else { + session.visit(url, options); } - if (element instanceof FrameElement) { - element.connectedCallback(); - element.disconnectedCallback(); + }; + const event = dispatch("turbo:frame-missing", { + target: this.element, + detail: { response, visit }, + cancelable: true, + }); + return !event.defaultPrevented; + } + handleFrameMissingFromResponse(fetchResponse) { + this.view.missing(); + this.throwFrameMissingError(fetchResponse); + } + throwFrameMissingError(fetchResponse) { + const message = `The response (${fetchResponse.statusCode}) did not contain the expected and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`; + throw new TurboFrameMissingError(message); + } + async visitResponse(response) { + const wrapped = new FetchResponse(response); + const responseHTML = await wrapped.responseHTML; + const { location, redirected, statusCode } = wrapped; + return session.visit(location, { response: { redirected, statusCode, responseHTML } }); + } + findFrameElement(element, submitter) { + var _a; + const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target"); + return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element; + } + async extractForeignFrameElement(container) { + let element; + const id = CSS.escape(this.id); + try { + element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL); + if (element) { return element; } + element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL); + if (element) { + await element.loaded; + return await this.extractForeignFrameElement(element); + } } - } - - class StreamElement extends HTMLElement { - static async renderElement(newElement) { - await newElement.performAction(); + catch (error) { + console.error(error); + return new FrameElement(); } - async connectedCallback() { - try { - await this.render(); - } - catch (error) { - console.error(error); - } - finally { - this.disconnect(); - } + return null; + } + formActionIsVisitable(form, submitter) { + const action = getAction(form, submitter); + return locationIsVisitable(expandURL(action), this.rootLocation); + } + shouldInterceptNavigation(element, submitter) { + const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target"); + if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) { + return false; } - async render() { - var _a; - return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => { - const event = this.beforeRenderEvent; - if (this.dispatchEvent(event)) { - await nextAnimationFrame(); - await event.detail.render(this); - } - })())); + if (!this.enabled || id == "_top") { + return false; } - disconnect() { - try { - this.remove(); + if (id) { + const frameElement = getFrameElementById(id); + if (frameElement) { + return !frameElement.disabled; } - catch (_a) { } } - removeDuplicateTargetChildren() { - this.duplicateChildren.forEach((c) => c.remove()); + if (!session.elementIsNavigatable(element)) { + return false; } - get duplicateChildren() { - var _a; - const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id); - const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id); - return existingChildren.filter((c) => newChildrenIds.includes(c.id)); + if (submitter && !session.elementIsNavigatable(submitter)) { + return false; } - get performAction() { - if (this.action) { - const actionFunction = StreamActions[this.action]; - if (actionFunction) { - return actionFunction; - } - this.raise("unknown action"); - } - this.raise("action attribute is missing"); + return true; + } + get id() { + return this.element.id; + } + get enabled() { + return !this.element.disabled; + } + get sourceURL() { + if (this.element.src) { + return this.element.src; } - get targetElements() { - if (this.target) { - return this.targetElementsById; - } - else if (this.targets) { - return this.targetElementsByQuery; + } + set sourceURL(sourceURL) { + this.ignoringChangesToAttribute("src", () => { + this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null; + }); + } + get loadingStyle() { + return this.element.loading; + } + get isLoading() { + return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined; + } + get complete() { + return this.element.hasAttribute("complete"); + } + set complete(value) { + this.ignoringChangesToAttribute("complete", () => { + if (value) { + this.element.setAttribute("complete", ""); } else { - this.raise("target or targets attribute is missing"); - } - } - get templateContent() { - return this.templateElement.content.cloneNode(true); - } - get templateElement() { - if (this.firstElementChild === null) { - const template = this.ownerDocument.createElement("template"); - this.appendChild(template); - return template; - } - else if (this.firstElementChild instanceof HTMLTemplateElement) { - return this.firstElementChild; + this.element.removeAttribute("complete"); } - this.raise("first child element must be a