From 860ba1f2e4ff0105dd151b497b134f6fda7fafc4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 29 Oct 2025 08:35:27 +0100 Subject: [PATCH] Upgrade turbo and remove our patched version We upstreamed the changes from https://github.com/basecamp/fizzy/pull/1327 --- Gemfile.lock | 2 +- config/importmap.rb | 2 +- vendor/javascript/turbo.patched.js | 6050 ---------------------------- 3 files changed, 2 insertions(+), 6052 deletions(-) delete mode 100644 vendor/javascript/turbo.patched.js diff --git a/Gemfile.lock b/Gemfile.lock index 137d07264..57a5571a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -565,7 +565,7 @@ GEM tiktoken_ruby (0.0.12-x86_64-linux-musl) timeout (0.4.3) tsort (0.2.0) - turbo-rails (2.0.17) + turbo-rails (2.0.20) actionpack (>= 7.1.0) railties (>= 7.1.0) tzinfo (2.0.6) diff --git a/config/importmap.rb b/config/importmap.rb index c1445db74..f1da52737 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,7 +1,7 @@ # Pin npm packages by running ./bin/importmap pin "application" -pin "@hotwired/turbo-rails", to: "turbo.patched.js" +pin "@hotwired/turbo-rails", to: "turbo.min.js" pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin "@rails/request.js", to: "@rails--request.js" # @0.0.11 diff --git a/vendor/javascript/turbo.patched.js b/vendor/javascript/turbo.patched.js deleted file mode 100644 index bcc9fcdcf..000000000 --- a/vendor/javascript/turbo.patched.js +++ /dev/null @@ -1,6050 +0,0 @@ -/*! -Turbo 8.0.17 -Copyright © 2025 37signals LLC - */ -(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); - } - }; - 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); - -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?.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) { - const prototypeOfSubmitEvent = window.SubmitEvent.prototype; - if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) { - prototype = prototypeOfSubmitEvent; - } else { - return; - } - } - addEventListener("click", clickCaptured, true); - Object.defineProperty(prototype, "submitter", { - get() { - if (this.type == "submit" && this.target instanceof HTMLFormElement) { - return submittersByForm.get(this.target); - } - } - }); -})(); - -const FrameLoadingStyle = { - eager: "eager", - lazy: "lazy" -}; - -class FrameElement extends HTMLElement { - static delegateConstructor=undefined; - loaded=Promise.resolve(); - static get observedAttributes() { - return [ "disabled", "loading", "src" ]; - } - constructor() { - super(); - 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 == "src") { - this.delegate.sourceURLChanged(); - } else if (name == "disabled") { - this.delegate.disabledChanged(); - } - } - get src() { - return this.getAttribute("src"); - } - set src(value) { - if (value) { - this.setAttribute("src", value); - } else { - this.removeAttribute("src"); - } - } - get refresh() { - return this.getAttribute("refresh"); - } - set refresh(value) { - if (value) { - this.setAttribute("refresh", value); - } else { - this.removeAttribute("refresh"); - } - } - get shouldReloadWithMorph() { - return this.src && this.refresh === "morph"; - } - get loading() { - return frameLoadingStyleFromString(this.getAttribute("loading") || ""); - } - set loading(value) { - if (value) { - this.setAttribute("loading", value); - } else { - this.removeAttribute("loading"); - } - } - get disabled() { - return this.hasAttribute("disabled"); - } - set disabled(value) { - if (value) { - this.setAttribute("disabled", ""); - } else { - this.removeAttribute("disabled"); - } - } - get autoscroll() { - return this.hasAttribute("autoscroll"); - } - set autoscroll(value) { - if (value) { - this.setAttribute("autoscroll", ""); - } else { - this.removeAttribute("autoscroll"); - } - } - get complete() { - return !this.delegate.isLoading; - } - get isActive() { - return this.ownerDocument === document && !this.isPreview; - } - get isPreview() { - return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview"); - } -} - -function frameLoadingStyleFromString(style) { - switch (style.toLowerCase()) { - case "lazy": - return FrameLoadingStyle.lazy; - - default: - return FrameLoadingStyle.eager; - } -} - -const drive = { - enabled: true, - progressBarDelay: 500, - unvisitableExtensions: new Set([ ".7z", ".aac", ".apk", ".avi", ".bmp", ".bz2", ".css", ".csv", ".deb", ".dmg", ".doc", ".docx", ".exe", ".gif", ".gz", ".heic", ".heif", ".ico", ".iso", ".jpeg", ".jpg", ".js", ".json", ".m4a", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi", ".ogg", ".ogv", ".pdf", ".pkg", ".png", ".ppt", ".pptx", ".rar", ".rtf", ".svg", ".tar", ".tif", ".tiff", ".txt", ".wav", ".webm", ".webp", ".wma", ".wmv", ".xls", ".xlsx", ".xml", ".zip" ]) -}; - -function activateScriptElement(element) { - if (element.getAttribute("data-turbo-eval") == "false") { - return element; - } else { - const createdScriptElement = document.createElement("script"); - const cspNonce = getCspNonce(); - if (cspNonce) { - createdScriptElement.nonce = cspNonce; - } - createdScriptElement.textContent = element.textContent; - createdScriptElement.async = false; - copyElementAttributes(createdScriptElement, element); - return createdScriptElement; - } -} - -function copyElementAttributes(destinationElement, sourceElement) { - for (const {name: name, value: 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: target, cancelable: cancelable, detail: detail} = {}) { - const event = new CustomEvent(eventName, { - cancelable: cancelable, - bubbles: true, - composed: true, - detail: detail - }); - if (target && target.isConnected) { - target.dispatchEvent(event); - } else { - document.documentElement.dispatchEvent(event); - } - return event; -} - -function cancelEvent(event) { - event.preventDefault(); - event.stopImmediatePropagation(); -} - -function nextRepaint() { - if (document.visibilityState === "hidden") { - return nextEventLoopTick(); - } else { - return nextAnimationFrame(); - } -} - -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?.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 = 2e3) { - 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 getCspNonce() { - const element = getMetaElement("csp-nonce"); - if (element) { - const {nonce: nonce, content: content} = element; - return nonce == "" ? content : nonce; - } -} - -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) { - if (element instanceof Element) { - return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector); - } -} - -function elementIsFocusable(element) { - const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])"; - return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function"; -} - -function queryAutofocusableElement(elementOrDocumentFragment) { - return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable); -} - -async function around(callback, reader) { - const before = reader(); - callback(); - await nextAnimationFrame(); - const after = reader(); - return [ before, after ]; -} - -function doesNotTargetIFrame(name) { - if (name === "_blank") { - return false; - } else if (name) { - for (const element of document.getElementsByName(name)) { - if (element instanceof HTMLIFrameElement) return false; - } - return true; - } else { - return true; - } -} - -function findLinkFromClickTarget(target) { - const link = findClosestRecursively(target, "a[href], a[xlink\\:href]"); - if (!link) return null; - if (link.hasAttribute("download")) return null; - if (link.hasAttribute("target") && link.target !== "_self") return null; - return link; -} - -function getLocationForLink(link) { - return expandURL(link.getAttribute("href") || ""); -} - -function debounce(fn, delay) { - let timeoutId = null; - return (...args) => { - const callback = () => fn.apply(this, args); - clearTimeout(timeoutId); - timeoutId = setTimeout(callback, delay); - }; -} - -const submitter = { - "aria-disabled": { - beforeSubmit: submitter => { - submitter.setAttribute("aria-disabled", "true"); - submitter.addEventListener("click", cancelEvent); - }, - afterSubmit: submitter => { - submitter.removeAttribute("aria-disabled"); - submitter.removeEventListener("click", cancelEvent); - } - }, - disabled: { - beforeSubmit: submitter => submitter.disabled = true, - afterSubmit: submitter => submitter.disabled = false - } -}; - -class Config { - #submitter=null; - constructor(config) { - Object.assign(this, config); - } - get submitter() { - return this.#submitter; - } - set submitter(value) { - this.#submitter = submitter[value] || value; - } -} - -const forms = new Config({ - mode: "on", - submitter: "disabled" -}); - -const config = { - drive: drive, - forms: forms -}; - -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$1(form, submitter) { - const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action; - return expandURL(action); -} - -function getExtension(url) { - return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || ""; -} - -function isPrefixedBy(baseURL, url) { - const prefix = addTrailingSlash(url.origin + url.pathname); - return addTrailingSlash(baseURL.href) === prefix || baseURL.href.startsWith(prefix); -} - -function locationIsVisitable(location, rootLocation) { - return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(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 addTrailingSlash(value) { - return value.endsWith("/") ? value : value + "/"; -} - -class FetchResponse { - constructor(response) { - this.response = response; - } - get succeeded() { - return this.response.ok; - } - get failed() { - return !this.succeeded; - } - get clientError() { - return this.statusCode >= 400 && this.statusCode <= 499; - } - get serverError() { - return this.statusCode >= 500 && this.statusCode <= 599; - } - get redirected() { - return this.response.redirected; - } - get location() { - return expandURL(this.response.url); - } - get isHTML() { - return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/); - } - get statusCode() { - return this.response.status; - } - get contentType() { - return this.header("Content-Type"); - } - get responseText() { - return this.response.clone().text(); - } - get responseHTML() { - if (this.isHTML) { - return this.response.clone().text(); - } else { - return Promise.resolve(undefined); - } - } - header(name) { - return this.response.headers.get(name); - } -} - -class LimitedSet extends Set { - constructor(maxSize) { - super(); - this.maxSize = maxSize; - } - add(value) { - if (this.size >= this.maxSize) { - const iterator = this.values(); - const oldestValue = iterator.next().value; - this.delete(oldestValue); - } - super.add(value); - } -} - -const recentRequests = new LimitedSet(20); - -function fetchWithTurboHeaders(url, options = {}) { - const modifiedHeaders = new Headers(options.headers || {}); - const requestUID = uuid(); - recentRequests.add(requestUID); - modifiedHeaders.append("X-Turbo-Request-Id", requestUID); - return window.fetch(url, { - ...options, - headers: modifiedHeaders - }); -} - -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; - } -} - -const FetchMethod = { - get: "get", - post: "post", - put: "put", - patch: "patch", - delete: "delete" -}; - -function fetchEnctypeFromString(encoding) { - switch (encoding.toLowerCase()) { - case FetchEnctype.multipart: - return FetchEnctype.multipart; - - case FetchEnctype.plain: - return FetchEnctype.plain; - - default: - return FetchEnctype.urlEncoded; - } -} - -const FetchEnctype = { - urlEncoded: "application/x-www-form-urlencoded", - multipart: "multipart/form-data", - plain: "text/plain" -}; - -class FetchRequest { - abortController=new AbortController; - #resolveRequestPromise=_value => {}; - constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) { - const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype); - this.delegate = delegate; - this.url = url; - this.target = target; - this.fetchOptions = { - credentials: "same-origin", - redirect: "follow", - method: method.toUpperCase(), - headers: { - ...this.defaultHeaders - }, - body: body, - signal: this.abortSignal, - referrer: this.delegate.referrer?.href - }; - this.enctype = enctype; - } - get method() { - return this.fetchOptions.method; - } - set method(value) { - const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData; - const fetchMethod = fetchMethodFromString(value) || FetchMethod.get; - this.url.search = ""; - const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype); - this.url = url; - this.fetchOptions.body = body; - this.fetchOptions.method = fetchMethod.toUpperCase(); - } - get headers() { - return this.fetchOptions.headers; - } - set headers(value) { - this.fetchOptions.headers = value; - } - get body() { - if (this.isSafe) { - return this.url.searchParams; - } else { - return this.fetchOptions.body; - } - } - set body(value) { - this.fetchOptions.body = value; - } - 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: fetchOptions} = this; - this.delegate.prepareRequest(this); - const event = await this.#allowRequestToBeIntercepted(fetchOptions); - try { - this.delegate.requestStarted(this); - if (event.detail.fetchRequest) { - this.response = event.detail.fetchRequest.response; - } else { - this.response = fetchWithTurboHeaders(this.url.href, fetchOptions); - } - const response = await this.response; - 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: fetchResponse - }, - target: this.target - }); - if (event.defaultPrevented) { - this.delegate.requestPreventedHandlingResponse(this, fetchResponse); - } else if (fetchResponse.succeeded) { - this.delegate.requestSucceededWithResponse(this, fetchResponse); - } else { - this.delegate.requestFailedWithResponse(this, fetchResponse); - } - return fetchResponse; - } - get defaultHeaders() { - return { - Accept: "text/html, application/xhtml+xml" - }; - } - get isSafe() { - return isSafe(this.method); - } - 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: fetchOptions, - url: this.url, - resume: this.#resolveRequestPromise - }, - target: this.target - }); - this.url = event.detail.url; - if (event.defaultPrevented) await requestInterception; - return event; - } - #willDelegateErrorHandling(error) { - const event = dispatch("turbo:fetch-request-error", { - target: this.target, - cancelable: true, - detail: { - request: this, - error: error - } - }); - return !event.defaultPrevented; - } -} - -function isSafe(fetchMethod) { - return fetchMethodFromString(fetchMethod) == FetchMethod.get; -} - -function buildResourceAndBody(resource, method, requestBody, enctype) { - const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams; - if (isSafe(method)) { - return [ mergeIntoURLSearchParams(resource, searchParams), null ]; - } else if (enctype == FetchEnctype.urlEncoded) { - return [ resource, searchParams ]; - } else { - return [ resource, requestBody ]; - } -} - -function entriesExcludingFiles(requestBody) { - const entries = []; - for (const [name, value] of requestBody) { - if (value instanceof File) continue; else entries.push([ name, value ]); - } - return entries; -} - -function mergeIntoURLSearchParams(url, requestBody) { - const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody)); - url.search = searchParams.toString(); - return url; -} - -class AppearanceObserver { - started=false; - constructor(delegate, 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); - } - } - intersect=entries => { - const lastEntry = entries.slice(-1)[0]; - if (lastEntry?.isIntersecting) { - this.delegate.elementAppearedInViewport(this.element); - } - }; -} - -class StreamMessage { - static contentType="text/vnd.turbo-stream.html"; - static wrap(message) { - if (typeof message == "string") { - return new this(createDocumentFragment(message)); - } else { - return message; - } - } - constructor(fragment) { - this.fragment = importStreamElements(fragment); - } -} - -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)); - } - element.replaceWith(streamElement); - } - return fragment; -} - -const PREFETCH_DELAY = 100; - -class PrefetchCache { - #prefetchTimeout=null; - #prefetched=null; - get(url) { - if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) { - return this.#prefetched.request; - } - } - setLater(url, request, ttl) { - this.clear(); - this.#prefetchTimeout = setTimeout((() => { - request.perform(); - this.set(url, request, ttl); - this.#prefetchTimeout = null; - }), PREFETCH_DELAY); - } - set(url, request, ttl) { - this.#prefetched = { - url: url, - request: request, - expire: new Date((new Date).getTime() + ttl) - }; - } - clear() { - if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout); - this.#prefetched = null; - } -} - -const cacheTtl = 10 * 1e3; - -const prefetchCache = new PrefetchCache; - -const FormSubmissionState = { - initialized: "initialized", - requesting: "requesting", - waiting: "waiting", - receiving: "receiving", - stopping: "stopping", - stopped: "stopped" -}; - -class FormSubmission { - state=FormSubmissionState.initialized; - static confirmMethod(message) { - return Promise.resolve(confirm(message)); - } - constructor(delegate, formElement, submitter, mustRedirect = false) { - const method = getMethod(formElement, submitter); - const action = getAction(getFormAction(formElement, submitter), method); - const body = buildFormData(formElement, submitter); - const enctype = getEnctype(formElement, submitter); - this.delegate = delegate; - this.formElement = formElement; - this.submitter = submitter; - this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype); - this.mustRedirect = mustRedirect; - } - get method() { - return this.fetchRequest.method; - } - set method(value) { - this.fetchRequest.method = value; - } - get action() { - return this.fetchRequest.url.toString(); - } - set action(value) { - this.fetchRequest.url = expandURL(value); - } - get body() { - return this.fetchRequest.body; - } - get enctype() { - return this.fetchRequest.enctype; - } - get isSafe() { - return this.fetchRequest.isSafe; - } - get location() { - return this.fetchRequest.url; - } - async start() { - const {initialized: initialized, requesting: requesting} = FormSubmissionState; - const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement); - if (typeof confirmationMessage === "string") { - const confirmMethod = typeof config.forms.confirm === "function" ? config.forms.confirm : FormSubmission.confirmMethod; - const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter); - if (!answer) { - return; - } - } - if (this.state == initialized) { - this.state = requesting; - return this.fetchRequest.perform(); - } - } - stop() { - const {stopping: stopping, stopped: stopped} = FormSubmissionState; - if (this.state != stopping && this.state != stopped) { - this.state = stopping; - this.fetchRequest.cancel(); - 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) { - this.state = FormSubmissionState.waiting; - if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter); - this.setSubmitsWith(); - markAsBusy(this.formElement); - dispatch("turbo:submit-start", { - target: this.formElement, - detail: { - formSubmission: this - } - }); - this.delegate.formSubmissionStarted(this); - } - requestPreventedHandlingResponse(request, response) { - prefetchCache.clear(); - this.result = { - success: response.succeeded, - fetchResponse: response - }; - } - requestSucceededWithResponse(request, response) { - if (response.clientError || response.serverError) { - this.delegate.formSubmissionFailedWithResponse(this, response); - return; - } - prefetchCache.clear(); - 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: error - }; - this.delegate.formSubmissionErrored(this, error); - } - requestFinished(_request) { - this.state = FormSubmissionState.stopped; - if (this.submitter) config.forms.submitter.afterSubmit(this.submitter); - this.resetSubmitterText(); - clearBusyState(this.formElement); - dispatch("turbo:submit-end", { - target: this.formElement, - detail: { - 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() { - return this.submitter?.getAttribute("data-turbo-submits-with"); - } -} - -function buildFormData(formElement, submitter) { - const formData = new FormData(formElement); - const name = submitter?.getAttribute("name"); - const value = 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 getFormAction(formElement, submitter) { - const formElementAction = typeof formElement.action === "string" ? formElement.action : null; - if (submitter?.hasAttribute("formaction")) { - return submitter.getAttribute("formaction") || ""; - } else { - return formElement.getAttribute("action") || formElementAction || ""; - } -} - -function getAction(formAction, fetchMethod) { - const action = expandURL(formAction); - if (isSafe(fetchMethod)) { - action.search = ""; - } - return action; -} - -function getMethod(formElement, submitter) { - const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || ""; - return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get; -} - -function getEnctype(formElement, submitter) { - return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype); -} - -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() { - return queryAutofocusableElement(this.element); - } - get permanentElements() { - return queryPermanentElementsAll(this.element); - } - getPermanentElementById(id) { - return getPermanentElementById(this.element, id); - } - getPermanentElementMapForSnapshot(snapshot) { - const permanentElementMap = {}; - for (const currentPermanentElement of this.permanentElements) { - const {id: id} = currentPermanentElement; - const newPermanentElement = snapshot.getPermanentElementById(id); - if (newPermanentElement) { - permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ]; - } - } - return permanentElementMap; - } -} - -function getPermanentElementById(node, id) { - return node.querySelector(`#${id}[data-turbo-permanent]`); -} - -function queryPermanentElementsAll(node) { - return node.querySelectorAll("[id][data-turbo-permanent]"); -} - -class FormSubmitObserver { - started=false; - constructor(delegate, eventTarget) { - this.delegate = delegate; - this.eventTarget = eventTarget; - } - start() { - if (!this.started) { - this.eventTarget.addEventListener("submit", this.submitCaptured, true); - this.started = true; - } - } - stop() { - if (this.started) { - this.eventTarget.removeEventListener("submit", this.submitCaptured, true); - this.started = false; - } - } - submitCaptured=() => { - this.eventTarget.removeEventListener("submit", this.submitBubbled, false); - this.eventTarget.addEventListener("submit", this.submitBubbled, false); - }; - 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); - } - } - }; -} - -function submissionDoesNotDismissDialog(form, submitter) { - const method = submitter?.getAttribute("formmethod") || form.getAttribute("method"); - return method != "dialog"; -} - -function submissionDoesNotTargetIFrame(form, submitter) { - const target = submitter?.getAttribute("formtarget") || form.getAttribute("target"); - return doesNotTargetIFrame(target); -} - -class View { - #resolveRenderPromise=_value => {}; - #resolveInterceptionPromise=_value => {}; - constructor(delegate, element) { - this.delegate = delegate; - this.element = element; - } - scrollToAnchor(anchor) { - const element = this.snapshot.getElementForAnchor(anchor); - if (element) { - this.focusElement(element); - this.scrollToElement(element); - } else { - this.scrollToPosition({ - x: 0, - y: 0 - }); - } - } - scrollToAnchorFromLocation(location) { - this.scrollToAnchor(getAnchor(location)); - } - scrollToElement(element) { - element.scrollIntoView(); - } - focusElement(element) { - if (element instanceof HTMLElement) { - if (element.hasAttribute("tabindex")) { - element.focus(); - } else { - element.setAttribute("tabindex", "-1"); - element.focus(); - element.removeAttribute("tabindex"); - } - } - } - scrollToPosition({x: x, y: y}) { - this.scrollRoot.scrollTo(x, y); - } - scrollToTop() { - this.scrollToPosition({ - x: 0, - y: 0 - }); - } - get scrollRoot() { - return window; - } - async render(renderer) { - const {isPreview: isPreview, shouldRender: shouldRender, willRender: willRender, newSnapshot: snapshot} = renderer; - const shouldInvalidate = willRender; - 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, - renderMethod: this.renderer.renderMethod - }; - const immediateRender = this.delegate.allowsImmediateRender(snapshot, options); - if (!immediateRender) await renderInterception; - await this.renderSnapshot(renderer); - this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod); - this.delegate.preloadOnLoadLinksForView(this.element); - this.finishRenderingSnapshot(renderer); - } finally { - delete this.renderer; - this.#resolveRenderPromise(undefined); - delete this.renderPromise; - } - } else if (shouldInvalidate) { - this.invalidate(renderer.reloadReason); - } - } - 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", ""); - } else { - this.element.removeAttribute("data-turbo-preview"); - } - } - markVisitDirection(direction) { - this.element.setAttribute("data-turbo-visit-direction", direction); - } - unmarkVisitDirection() { - this.element.removeAttribute("data-turbo-visit-direction"); - } - async renderSnapshot(renderer) { - await renderer.render(); - } - finishRenderingSnapshot(renderer) { - renderer.finishRendering(); - } -} - -class FrameView extends View { - missing() { - this.element.innerHTML = `Content missing`; - } - get snapshot() { - return new Snapshot(this.element); - } -} - -class LinkInterceptor { - constructor(delegate, element) { - 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); - } - clickBubbled=event => { - if (this.clickEventIsSignificant(event)) { - this.clickEvent = event; - } else { - delete this.clickEvent; - } - }; - linkClicked=event => { - if (this.clickEvent && this.clickEventIsSignificant(event)) { - 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); - } - } - delete this.clickEvent; - }; - willVisit=_event => { - delete this.clickEvent; - }; - clickEventIsSignificant(event) { - const target = event.composed ? event.target?.parentElement : event.target; - const element = findLinkFromClickTarget(target) || target; - return element instanceof Element && element.closest("turbo-frame, html") == this.element; - } -} - -class LinkClickObserver { - started=false; - constructor(delegate, eventTarget) { - this.delegate = delegate; - this.eventTarget = eventTarget; - } - start() { - if (!this.started) { - this.eventTarget.addEventListener("click", this.clickCaptured, true); - this.started = true; - } - } - stop() { - if (this.started) { - this.eventTarget.removeEventListener("click", this.clickCaptured, true); - this.started = false; - } - } - clickCaptured=() => { - this.eventTarget.removeEventListener("click", this.clickBubbled, false); - this.eventTarget.addEventListener("click", this.clickBubbled, false); - }; - clickBubbled=event => { - if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) { - const target = event.composedPath && event.composedPath()[0] || event.target; - const link = findLinkFromClickTarget(target); - if (link && doesNotTargetIFrame(link.target)) { - const location = getLocationForLink(link); - if (this.delegate.willFollowLinkToLocation(link, location, event)) { - event.preventDefault(); - this.delegate.followedLinkToLocation(link, location); - } - } - } - }; - clickEventIsSignificant(event) { - return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); - } -} - -class FormLinkClickObserver { - constructor(delegate, element) { - this.delegate = delegate; - this.linkInterceptor = new LinkClickObserver(this, element); - } - start() { - this.linkInterceptor.start(); - } - stop() { - this.linkInterceptor.stop(); - } - canPrefetchRequestToLocation(link, location) { - return false; - } - prefetchAndCacheRequestToLocation(link, location) { - return; - } - willFollowLinkToLocation(link, location, originalEvent) { - return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream")); - } - 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: type, - name: name, - value: 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?.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 { - #activeElement=null; - static renderElement(currentElement, newElement) {} - constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) { - this.currentSnapshot = currentSnapshot; - this.newSnapshot = newSnapshot; - this.isPreview = isPreview; - this.willRender = willRender; - this.renderElement = this.constructor.renderElement; - this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = { - resolve: resolve, - reject: reject - })); - } - get shouldRender() { - return true; - } - get shouldAutofocus() { - return true; - } - get reloadReason() { - return; - } - prepareToRender() { - return; - } - render() {} - finishRendering() { - if (this.resolvingFunctions) { - this.resolvingFunctions.resolve(); - delete this.resolvingFunctions; - } - } - async preservingPermanentElements(callback) { - await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback); - } - focusFirstAutofocusableElement() { - if (this.shouldAutofocus) { - const element = this.connectedSnapshot.firstAutofocusableElement; - if (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); - } - get renderMethod() { - return "replace"; - } -} - -class FrameRenderer extends Renderer { - static renderElement(currentElement, newElement) { - const destinationRange = document.createRange(); - destinationRange.selectNodeContents(currentElement); - destinationRange.deleteContents(); - const frameElement = newElement; - const sourceRange = frameElement.ownerDocument?.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 nextRepaint(); - this.preservingPermanentElements((() => { - this.loadFrameElement(); - })); - this.scrollFrameIntoView(); - await nextRepaint(); - this.focusFirstAutofocusableElement(); - await nextRepaint(); - 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: block, - behavior: 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"); - } -} - -function readScrollLogicalPosition(value, defaultValue) { - if (value == "end" || value == "start" || value == "center" || value == "nearest") { - return value; - } else { - return defaultValue; - } -} - -function readScrollBehavior(value, defaultValue) { - if (value == "auto" || value == "smooth") { - return value; - } else { - return defaultValue; - } -} - -var Idiomorph = function() { - const noOp = () => {}; - const defaults = { - morphStyle: "outerHTML", - callbacks: { - beforeNodeAdded: noOp, - afterNodeAdded: noOp, - beforeNodeMorphed: noOp, - afterNodeMorphed: noOp, - beforeNodeRemoved: noOp, - afterNodeRemoved: noOp, - beforeAttributeUpdated: noOp - }, - head: { - style: "merge", - shouldPreserve: elt => elt.getAttribute("im-preserve") === "true", - shouldReAppend: elt => elt.getAttribute("im-re-append") === "true", - shouldRemove: noOp, - afterHeadMorphed: noOp - }, - restoreFocus: true - }; - function morph(oldNode, newContent, config = {}) { - oldNode = normalizeElement(oldNode); - const newNode = normalizeParent(newContent); - const ctx = createMorphContext(oldNode, newNode, config); - const morphedNodes = saveAndRestoreFocus(ctx, (() => withHeadBlocking(ctx, oldNode, newNode, (ctx => { - if (ctx.morphStyle === "innerHTML") { - morphChildren(ctx, oldNode, newNode); - return Array.from(oldNode.childNodes); - } else { - return morphOuterHTML(ctx, oldNode, newNode); - } - })))); - ctx.pantry.remove(); - return morphedNodes; - } - function morphOuterHTML(ctx, oldNode, newNode) { - const oldParent = normalizeParent(oldNode); - let childNodes = Array.from(oldParent.childNodes); - const index = childNodes.indexOf(oldNode); - const rightMargin = childNodes.length - (index + 1); - morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling); - childNodes = Array.from(oldParent.childNodes); - return childNodes.slice(index, childNodes.length - rightMargin); - } - function saveAndRestoreFocus(ctx, fn) { - if (!ctx.config.restoreFocus) return fn(); - let activeElement = document.activeElement; - if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) { - return fn(); - } - const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement; - const results = fn(); - if (activeElementId && activeElementId !== document.activeElement?.id) { - activeElement = ctx.target.querySelector(`#${activeElementId}`); - activeElement?.focus(); - } - if (activeElement && !activeElement.selectionEnd && selectionEnd) { - activeElement.setSelectionRange(selectionStart, selectionEnd); - } - return results; - } - const morphChildren = function() { - function morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) { - if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) { - oldParent = oldParent.content; - newParent = newParent.content; - } - insertionPoint ||= oldParent.firstChild; - for (const newChild of newParent.childNodes) { - if (insertionPoint && insertionPoint != endPoint) { - const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint); - if (bestMatch) { - if (bestMatch !== insertionPoint) { - removeNodesBetween(ctx, insertionPoint, bestMatch); - } - morphNode(bestMatch, newChild, ctx); - insertionPoint = bestMatch.nextSibling; - continue; - } - } - if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) { - const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx); - morphNode(movedChild, newChild, ctx); - insertionPoint = movedChild.nextSibling; - continue; - } - const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx); - if (insertedNode) { - insertionPoint = insertedNode.nextSibling; - } - } - while (insertionPoint && insertionPoint != endPoint) { - const tempNode = insertionPoint; - insertionPoint = insertionPoint.nextSibling; - removeNode(ctx, tempNode); - } - } - function createNode(oldParent, newChild, insertionPoint, ctx) { - if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null; - if (ctx.idMap.has(newChild)) { - const newEmptyChild = document.createElement(newChild.tagName); - oldParent.insertBefore(newEmptyChild, insertionPoint); - morphNode(newEmptyChild, newChild, ctx); - ctx.callbacks.afterNodeAdded(newEmptyChild); - return newEmptyChild; - } else { - const newClonedChild = document.importNode(newChild, true); - oldParent.insertBefore(newClonedChild, insertionPoint); - ctx.callbacks.afterNodeAdded(newClonedChild); - return newClonedChild; - } - } - const findBestMatch = function() { - function findBestMatch(ctx, node, startPoint, endPoint) { - let softMatch = null; - let nextSibling = node.nextSibling; - let siblingSoftMatchCount = 0; - let cursor = startPoint; - while (cursor && cursor != endPoint) { - if (isSoftMatch(cursor, node)) { - if (isIdSetMatch(ctx, cursor, node)) { - return cursor; - } - if (softMatch === null) { - if (!ctx.idMap.has(cursor)) { - softMatch = cursor; - } - } - } - if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) { - siblingSoftMatchCount++; - nextSibling = nextSibling.nextSibling; - if (siblingSoftMatchCount >= 2) { - softMatch = undefined; - } - } - if (cursor.contains(document.activeElement)) break; - cursor = cursor.nextSibling; - } - return softMatch || null; - } - function isIdSetMatch(ctx, oldNode, newNode) { - let oldSet = ctx.idMap.get(oldNode); - let newSet = ctx.idMap.get(newNode); - if (!newSet || !oldSet) return false; - for (const id of oldSet) { - if (newSet.has(id)) { - return true; - } - } - return false; - } - function isSoftMatch(oldNode, newNode) { - const oldElt = oldNode; - const newElt = newNode; - return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id); - } - return findBestMatch; - }(); - function removeNode(ctx, node) { - if (ctx.idMap.has(node)) { - if (ctx.callbacks.beforeNodeRemoved(node) === false) return; - moveBefore(ctx.pantry, node, null); - } else { - if (ctx.callbacks.beforeNodeRemoved(node) === false) return; - node.parentNode?.removeChild(node); - ctx.callbacks.afterNodeRemoved(node); - } - } - function removeNodesBetween(ctx, startInclusive, endExclusive) { - let cursor = startInclusive; - while (cursor && cursor !== endExclusive) { - let tempNode = cursor; - cursor = cursor.nextSibling; - removeNode(ctx, tempNode); - } - return cursor; - } - function moveBeforeById(parentNode, id, after, ctx) { - const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`); - removeElementFromAncestorsIdMaps(target, ctx); - moveBefore(parentNode, target, after); - return target; - } - function removeElementFromAncestorsIdMaps(element, ctx) { - const id = element.id; - while (element = element.parentNode) { - let idSet = ctx.idMap.get(element); - if (idSet) { - idSet.delete(id); - if (!idSet.size) { - ctx.idMap.delete(element); - } - } - } - } - function moveBefore(parentNode, element, after) { - if (parentNode.moveBefore) { - try { - parentNode.moveBefore(element, after); - } catch (e) { - parentNode.insertBefore(element, after); - } - } else { - parentNode.insertBefore(element, after); - } - } - return morphChildren; - }(); - const morphNode = function() { - function morphNode(oldNode, newContent, ctx) { - if (ctx.ignoreActive && oldNode === document.activeElement) { - return null; - } - if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) { - return oldNode; - } - if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") { - handleHeadElement(oldNode, newContent, ctx); - } else { - morphAttributes(oldNode, newContent, ctx); - if (!ignoreValueOfActiveElement(oldNode, ctx)) { - morphChildren(ctx, oldNode, newContent); - } - } - ctx.callbacks.afterNodeMorphed(oldNode, newContent); - return oldNode; - } - function morphAttributes(oldNode, newNode, ctx) { - let type = newNode.nodeType; - if (type === 1) { - const oldElt = oldNode; - const newElt = newNode; - const oldAttributes = oldElt.attributes; - const newAttributes = newElt.attributes; - for (const newAttribute of newAttributes) { - if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) { - continue; - } - if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) { - oldElt.setAttribute(newAttribute.name, newAttribute.value); - } - } - for (let i = oldAttributes.length - 1; 0 <= i; i--) { - const oldAttribute = oldAttributes[i]; - if (!oldAttribute) continue; - if (!newElt.hasAttribute(oldAttribute.name)) { - if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) { - continue; - } - oldElt.removeAttribute(oldAttribute.name); - } - } - if (!ignoreValueOfActiveElement(oldElt, ctx)) { - syncInputValue(oldElt, newElt, ctx); - } - } - if (type === 8 || type === 3) { - if (oldNode.nodeValue !== newNode.nodeValue) { - oldNode.nodeValue = newNode.nodeValue; - } - } - } - function syncInputValue(oldElement, newElement, ctx) { - if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") { - let newValue = newElement.value; - let oldValue = oldElement.value; - syncBooleanAttribute(oldElement, newElement, "checked", ctx); - syncBooleanAttribute(oldElement, newElement, "disabled", ctx); - if (!newElement.hasAttribute("value")) { - if (!ignoreAttribute("value", oldElement, "remove", ctx)) { - oldElement.value = ""; - oldElement.removeAttribute("value"); - } - } else if (oldValue !== newValue) { - if (!ignoreAttribute("value", oldElement, "update", ctx)) { - oldElement.setAttribute("value", newValue); - oldElement.value = newValue; - } - } - } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) { - syncBooleanAttribute(oldElement, newElement, "selected", ctx); - } else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) { - let newValue = newElement.value; - let oldValue = oldElement.value; - if (ignoreAttribute("value", oldElement, "update", ctx)) { - return; - } - if (newValue !== oldValue) { - oldElement.value = newValue; - } - if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) { - oldElement.firstChild.nodeValue = newValue; - } - } - } - function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) { - const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName]; - if (newLiveValue !== oldLiveValue) { - const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx); - if (!ignoreUpdate) { - oldElement[attributeName] = newElement[attributeName]; - } - if (newLiveValue) { - if (!ignoreUpdate) { - oldElement.setAttribute(attributeName, ""); - } - } else { - if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) { - oldElement.removeAttribute(attributeName); - } - } - } - } - function ignoreAttribute(attr, element, updateType, ctx) { - if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) { - return true; - } - return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false; - } - function ignoreValueOfActiveElement(possibleActiveElement, ctx) { - return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body; - } - return morphNode; - }(); - function withHeadBlocking(ctx, oldNode, newNode, callback) { - if (ctx.head.block) { - const oldHead = oldNode.querySelector("head"); - const newHead = newNode.querySelector("head"); - if (oldHead && newHead) { - const promises = handleHeadElement(oldHead, newHead, ctx); - return Promise.all(promises).then((() => { - const newCtx = Object.assign(ctx, { - head: { - block: false, - ignore: true - } - }); - return callback(newCtx); - })); - } - } - return callback(ctx); - } - function handleHeadElement(oldHead, newHead, ctx) { - let added = []; - let removed = []; - let preserved = []; - let nodesToAppend = []; - let srcToNewHeadNodes = new Map; - for (const newHeadChild of newHead.children) { - srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild); - } - for (const currentHeadElt of oldHead.children) { - let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML); - let isReAppended = ctx.head.shouldReAppend(currentHeadElt); - let isPreserved = ctx.head.shouldPreserve(currentHeadElt); - if (inNewContent || isPreserved) { - if (isReAppended) { - removed.push(currentHeadElt); - } else { - srcToNewHeadNodes.delete(currentHeadElt.outerHTML); - preserved.push(currentHeadElt); - } - } else { - if (ctx.head.style === "append") { - if (isReAppended) { - removed.push(currentHeadElt); - nodesToAppend.push(currentHeadElt); - } - } else { - if (ctx.head.shouldRemove(currentHeadElt) !== false) { - removed.push(currentHeadElt); - } - } - } - } - nodesToAppend.push(...srcToNewHeadNodes.values()); - let promises = []; - for (const newNode of nodesToAppend) { - let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild; - if (ctx.callbacks.beforeNodeAdded(newElt) !== false) { - if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) { - let resolve; - let promise = new Promise((function(_resolve) { - resolve = _resolve; - })); - newElt.addEventListener("load", (function() { - resolve(); - })); - promises.push(promise); - } - oldHead.appendChild(newElt); - ctx.callbacks.afterNodeAdded(newElt); - added.push(newElt); - } - } - for (const removedElement of removed) { - if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) { - oldHead.removeChild(removedElement); - ctx.callbacks.afterNodeRemoved(removedElement); - } - } - ctx.head.afterHeadMorphed(oldHead, { - added: added, - kept: preserved, - removed: removed - }); - return promises; - } - const createMorphContext = function() { - function createMorphContext(oldNode, newContent, config) { - const {persistentIds: persistentIds, idMap: idMap} = createIdMaps(oldNode, newContent); - const mergedConfig = mergeDefaults(config); - const morphStyle = mergedConfig.morphStyle || "outerHTML"; - if (![ "innerHTML", "outerHTML" ].includes(morphStyle)) { - throw `Do not understand how to morph style ${morphStyle}`; - } - return { - target: oldNode, - newContent: newContent, - config: mergedConfig, - morphStyle: morphStyle, - ignoreActive: mergedConfig.ignoreActive, - ignoreActiveValue: mergedConfig.ignoreActiveValue, - restoreFocus: mergedConfig.restoreFocus, - idMap: idMap, - persistentIds: persistentIds, - pantry: createPantry(), - callbacks: mergedConfig.callbacks, - head: mergedConfig.head - }; - } - function mergeDefaults(config) { - let finalConfig = Object.assign({}, defaults); - Object.assign(finalConfig, config); - finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks); - finalConfig.head = Object.assign({}, defaults.head, config.head); - return finalConfig; - } - function createPantry() { - const pantry = document.createElement("div"); - pantry.hidden = true; - document.body.insertAdjacentElement("afterend", pantry); - return pantry; - } - function findIdElements(root) { - let elements = Array.from(root.querySelectorAll("[id]")); - if (root.id) { - elements.push(root); - } - return elements; - } - function populateIdMapWithTree(idMap, persistentIds, root, elements) { - for (const elt of elements) { - if (persistentIds.has(elt.id)) { - let current = elt; - while (current) { - let idSet = idMap.get(current); - if (idSet == null) { - idSet = new Set; - idMap.set(current, idSet); - } - idSet.add(elt.id); - if (current === root) break; - current = current.parentElement; - } - } - } - } - function createIdMaps(oldContent, newContent) { - const oldIdElements = findIdElements(oldContent); - const newIdElements = findIdElements(newContent); - const persistentIds = createPersistentIds(oldIdElements, newIdElements); - let idMap = new Map; - populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements); - const newRoot = newContent.__idiomorphRoot || newContent; - populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements); - return { - persistentIds: persistentIds, - idMap: idMap - }; - } - function createPersistentIds(oldIdElements, newIdElements) { - let duplicateIds = new Set; - let oldIdTagNameMap = new Map; - for (const {id: id, tagName: tagName} of oldIdElements) { - if (oldIdTagNameMap.has(id)) { - duplicateIds.add(id); - } else { - oldIdTagNameMap.set(id, tagName); - } - } - let persistentIds = new Set; - for (const {id: id, tagName: tagName} of newIdElements) { - if (persistentIds.has(id)) { - duplicateIds.add(id); - } else if (oldIdTagNameMap.get(id) === tagName) { - persistentIds.add(id); - } - } - for (const id of duplicateIds) { - persistentIds.delete(id); - } - return persistentIds; - } - return createMorphContext; - }(); - const {normalizeElement: normalizeElement, normalizeParent: normalizeParent} = function() { - const generatedByIdiomorph = new WeakSet; - function normalizeElement(content) { - if (content instanceof Document) { - return content.documentElement; - } else { - return content; - } - } - function normalizeParent(newContent) { - if (newContent == null) { - return document.createElement("div"); - } else if (typeof newContent === "string") { - return normalizeParent(parseContent(newContent)); - } else if (generatedByIdiomorph.has(newContent)) { - return newContent; - } else if (newContent instanceof Node) { - if (newContent.parentNode) { - return createDuckTypedParent(newContent); - } else { - const dummyParent = document.createElement("div"); - dummyParent.append(newContent); - return dummyParent; - } - } else { - const dummyParent = document.createElement("div"); - for (const elt of [ ...newContent ]) { - dummyParent.append(elt); - } - return dummyParent; - } - } - function createDuckTypedParent(newContent) { - return { - childNodes: [ newContent ], - querySelectorAll: s => { - const elements = newContent.querySelectorAll(s); - return newContent.matches(s) ? [ newContent, ...elements ] : elements; - }, - insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r), - moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r), - get __idiomorphRoot() { - return newContent; - } - }; - } - function parseContent(newContent) { - let parser = new DOMParser; - let contentWithSvgsRemoved = newContent.replace(/]*>|>)([\s\S]*?)<\/svg>/gim, ""); - if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) { - let content = parser.parseFromString(newContent, "text/html"); - if (contentWithSvgsRemoved.match(/<\/html>/)) { - generatedByIdiomorph.add(content); - return content; - } else { - let htmlElement = content.firstChild; - if (htmlElement) { - generatedByIdiomorph.add(htmlElement); - } - return htmlElement; - } - } else { - let responseDoc = parser.parseFromString("", "text/html"); - let content = responseDoc.body.querySelector("template").content; - generatedByIdiomorph.add(content); - return content; - } - } - return { - normalizeElement: normalizeElement, - normalizeParent: normalizeParent - }; - }(); - return { - morph: morph, - defaults: defaults - }; -}(); - -function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) { - Idiomorph.morph(currentElement, newElement, { - ...options, - callbacks: new DefaultIdiomorphCallbacks(callbacks) - }); -} - -function morphChildren(currentElement, newElement, options = {}) { - morphElements(currentElement, newElement.childNodes, { - ...options, - morphStyle: "innerHTML" - }); -} - -class DefaultIdiomorphCallbacks { - #beforeNodeMorphed; - constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) { - this.#beforeNodeMorphed = beforeNodeMorphed || (() => true); - } - beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id)); - beforeNodeMorphed=(currentElement, newElement) => { - if (currentElement instanceof Element) { - if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) { - const event = dispatch("turbo:before-morph-element", { - cancelable: true, - target: currentElement, - detail: { - currentElement: currentElement, - newElement: newElement - } - }); - return !event.defaultPrevented; - } else { - return false; - } - } - }; - beforeAttributeUpdated=(attributeName, target, mutationType) => { - const event = dispatch("turbo:before-morph-attribute", { - cancelable: true, - target: target, - detail: { - attributeName: attributeName, - mutationType: mutationType - } - }); - return !event.defaultPrevented; - }; - beforeNodeRemoved=node => this.beforeNodeMorphed(node); - afterNodeMorphed=(currentElement, newElement) => { - if (currentElement instanceof Element) { - dispatch("turbo:morph-element", { - target: currentElement, - detail: { - currentElement: currentElement, - newElement: newElement - } - }); - } - }; -} - -class MorphingFrameRenderer extends FrameRenderer { - static renderElement(currentElement, newElement) { - dispatch("turbo:before-frame-morph", { - target: currentElement, - detail: { - currentElement: currentElement, - newElement: newElement - } - }); - morphChildren(currentElement, newElement); - } - async preservingPermanentElements(callback) { - return await callback(); - } -} - -class ProgressBar { - static animationDuration=300; - 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); - } - `; - } - hiding=false; - value=0; - visible=false; - constructor() { - 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; - })); - } - } - 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; - } - trickle=() => { - this.setValue(this.value + Math.random() / 100); - }; - refresh() { - requestAnimationFrame((() => { - this.progressElement.style.width = `${10 + this.value * 90}%`; - })); - } - createStylesheetElement() { - const element = document.createElement("style"); - element.type = "text/css"; - element.textContent = ProgressBar.defaultCSS; - const cspNonce = getCspNonce(); - if (cspNonce) { - element.nonce = cspNonce; - } - return element; - } - createProgressElement() { - const element = document.createElement("div"); - element.className = "turbo-progress-bar"; - return element; - } -} - -class HeadSnapshot extends Snapshot { - detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => { - const {outerHTML: outerHTML} = element; - const details = outerHTML in result ? result[outerHTML] : { - type: elementType(element), - tracked: elementIsTracked(element), - elements: [] - }; - return { - ...result, - [outerHTML]: { - ...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}) => type == matchedType)).map((({elements: [element]}) => element)); - } - get provisionalElements() { - return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => { - const {type: type, tracked: tracked, elements: 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 | 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)); - } - static fromElement(element) { - return this.fromDocument(element.ownerDocument); - } - static fromDocument({documentElement: documentElement, body: body, head: head}) { - return new this(documentElement, body, new HeadSnapshot(head)); - } - constructor(documentElement, body, headSnapshot) { - super(body); - this.documentElement = documentElement; - 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; - } - for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) { - clonedPasswordInput.value = ""; - } - return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot); - } - get lang() { - return this.documentElement.getAttribute("lang"); - } - get headElement() { - return this.headSnapshot.element; - } - get rootLocation() { - const root = this.getSetting("root") ?? "/"; - 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"; - } - get prefersViewTransitions() { - const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin"; - return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches; - } - get shouldMorphPage() { - return this.getSetting("refresh-method") === "morph"; - } - get shouldPreserveScrollPosition() { - return this.getSetting("refresh-scroll") === "preserve"; - } - getSetting(name) { - return this.headSnapshot.getMetaValue(`turbo-${name}`); - } -} - -class ViewTransitioner { - #viewTransitionStarted=false; - #lastOperation=Promise.resolve(); - renderChange(useViewTransition, render) { - if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) { - this.#viewTransitionStarted = true; - this.#lastOperation = this.#lastOperation.then((async () => { - await document.startViewTransition(render).finished; - })); - } else { - this.#lastOperation = this.#lastOperation.then(render); - } - return this.#lastOperation; - } - get viewTransitionsAvailable() { - return document.startViewTransition; - } -} - -const defaultOptions = { - action: "advance", - historyChanged: false, - visitCachedSnapshot: () => {}, - willRender: true, - updateHistory: true, - shouldCacheSnapshot: true, - acceptsStreamResponse: false -}; - -const TimingMetric = { - visitStart: "visitStart", - requestStart: "requestStart", - requestEnd: "requestEnd", - visitEnd: "visitEnd" -}; - -const VisitState = { - initialized: "initialized", - started: "started", - canceled: "canceled", - failed: "failed", - completed: "completed" -}; - -const SystemStatusCode = { - networkFailure: 0, - timeoutFailure: -1, - contentTypeMismatch: -2 -}; - -const Direction = { - advance: "forward", - restore: "back", - replace: "none" -}; - -class Visit { - identifier=uuid(); - timingMetrics={}; - followedRedirect=false; - historyChanged=false; - scrolled=false; - shouldCacheSnapshot=true; - acceptsStreamResponse=false; - snapshotCached=false; - state=VisitState.initialized; - viewTransitioner=new ViewTransitioner; - constructor(delegate, location, restorationIdentifier, options = {}) { - this.delegate = delegate; - this.location = location; - this.restorationIdentifier = restorationIdentifier || uuid(); - const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = { - ...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.isPageRefresh = this.view.isPageRefresh(this); - this.visitCachedSnapshot = visitCachedSnapshot; - this.willRender = willRender; - this.updateHistory = updateHistory; - this.scrolled = !willRender; - this.shouldCacheSnapshot = shouldCacheSnapshot; - this.acceptsStreamResponse = acceptsStreamResponse; - this.direction = direction || Direction[action]; - } - 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.adapter.visitCompleted(this); - this.state = VisitState.completed; - this.followRedirect(); - if (!this.followedRedirect) { - this.delegate.visitCompleted(this); - } - } - } - fail() { - if (this.state == VisitState.started) { - this.state = VisitState.failed; - this.adapter.visitFailed(this); - this.delegate.visitCompleted(this); - } - } - changeHistory() { - if (!this.historyChanged && this.updateHistory) { - const actionForHistory = this.location.href === this.referrer?.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: 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: statusCode, responseHTML: responseHTML} = this.response; - this.render((async () => { - if (this.shouldCacheSnapshot) this.cacheSnapshot(); - if (this.view.renderPromise) await this.view.renderPromise; - if (isSuccessful(statusCode) && responseHTML != null) { - const snapshot = PageSnapshot.fromHTMLString(responseHTML); - await this.renderPageSnapshot(snapshot, false); - 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.isPageRefresh) { - this.adapter.visitRendered(this); - } else { - if (this.view.renderPromise) await this.view.renderPromise; - await this.renderPageSnapshot(snapshot, isPreview); - this.adapter.visitRendered(this); - if (!isPreview) { - this.complete(); - } - } - })); - } - } - followRedirect() { - if (this.redirectedToLocation && !this.followedRedirect && this.response?.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: redirected, statusCode: statusCode} = response; - if (responseHTML == undefined) { - this.recordResponse({ - statusCode: SystemStatusCode.contentTypeMismatch, - redirected: redirected - }); - } else { - this.redirectedToLocation = response.redirected ? response.location : undefined; - this.recordResponse({ - statusCode: statusCode, - responseHTML: responseHTML, - redirected: redirected - }); - } - } - async requestFailedWithResponse(request, response) { - const responseHTML = await response.responseHTML; - const {redirected: redirected, statusCode: statusCode} = response; - if (responseHTML == undefined) { - this.recordResponse({ - statusCode: SystemStatusCode.contentTypeMismatch, - redirected: redirected - }); - } else { - this.recordResponse({ - statusCode: statusCode, - responseHTML: responseHTML, - redirected: redirected - }); - } - } - requestErrored(_request, _error) { - this.recordResponse({ - statusCode: SystemStatusCode.networkFailure, - redirected: false - }); - } - requestFinished() { - this.finishRequest(); - } - performScroll() { - if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) { - 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: 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 { - ...this.timingMetrics - }; - } - 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 = document.visibilityState === "hidden" ? setTimeout((() => resolve()), 0) : requestAnimationFrame((() => resolve())); - })); - await callback(); - delete this.frame; - } - async renderPageSnapshot(snapshot, isPreview) { - await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => { - await this.view.renderPage(snapshot, isPreview, this.willRender, this); - this.performScroll(); - })); - } - cancelRender() { - if (this.frame) { - cancelAnimationFrame(this.frame); - delete this.frame; - } - } -} - -function isSuccessful(statusCode) { - return statusCode >= 200 && statusCode < 300; -} - -class BrowserAdapter { - progressBar=new ProgressBar; - constructor(session) { - this.session = session; - } - visitProposedToLocation(location, options) { - if (locationIsVisitable(location, this.navigator.rootLocation)) { - this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options); - } else { - window.location.href = location.toString(); - } - } - visitStarted(visit) { - this.location = visit.location; - this.redirectedToLocation = null; - 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(); - if (visit.response.redirected) { - this.redirectedToLocation = visit.redirectedToLocation; - } - } - visitRequestFailedWithStatusCode(visit, statusCode) { - switch (statusCode) { - case SystemStatusCode.networkFailure: - case SystemStatusCode.timeoutFailure: - case SystemStatusCode.contentTypeMismatch: - return this.reload({ - reason: "request_failed", - context: { - statusCode: statusCode - } - }); - - default: - return visit.loadResponse(); - } - } - visitRequestFinished(_visit) {} - visitCompleted(_visit) { - this.progressBar.setValue(1); - this.hideVisitProgressBar(); - } - pageInvalidated(reason) { - this.reload(reason); - } - visitFailed(_visit) { - this.progressBar.setValue(1); - this.hideVisitProgressBar(); - } - visitRendered(_visit) {} - linkPrefetchingIsEnabledForLocation(location) { - return true; - } - formSubmissionStarted(_formSubmission) { - this.progressBar.setValue(0); - this.showFormProgressBarAfterDelay(); - } - formSubmissionFinished(_formSubmission) { - this.progressBar.setValue(1); - this.hideFormProgressBar(); - } - showVisitProgressBarAfterDelay() { - this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); - } - hideVisitProgressBar() { - this.progressBar.hide(); - if (this.visitProgressBarTimeout != null) { - window.clearTimeout(this.visitProgressBarTimeout); - delete this.visitProgressBarTimeout; - } - } - showFormProgressBarAfterDelay() { - if (this.formProgressBarTimeout == null) { - this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); - } - } - hideFormProgressBar() { - this.progressBar.hide(); - if (this.formProgressBarTimeout != null) { - window.clearTimeout(this.formProgressBarTimeout); - delete this.formProgressBarTimeout; - } - } - showProgressBar=() => { - this.progressBar.show(); - }; - reload(reason) { - dispatch("turbo:reload", { - detail: reason - }); - window.location.href = (this.redirectedToLocation || this.location)?.toString() || window.location.href; - } - get navigator() { - return this.session.navigator; - } -} - -class CacheObserver { - selector="[data-turbo-temporary]"; - deprecatedSelector="[data-turbo-cache=false]"; - started=false; - 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); - } - } - removeTemporaryElements=_event => { - for (const element of this.temporaryElements) { - element.remove(); - } - }; - 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) { - const action = getAction$1(form, submitter); - const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); - const rootLocation = expandURL(meta?.content ?? "/"); - 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?.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 { - location; - restorationIdentifier=uuid(); - restorationData={}; - started=false; - pageLoaded=false; - currentIndex=0; - constructor(delegate) { - this.delegate = delegate; - } - start() { - if (!this.started) { - addEventListener("popstate", this.onPopState, false); - addEventListener("load", this.onPageLoad, false); - this.currentIndex = history.state?.turbo?.restorationIndex || 0; - 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()) { - if (method === history.pushState) ++this.currentIndex; - const state = { - turbo: { - restorationIdentifier: restorationIdentifier, - restorationIndex: this.currentIndex - } - }; - method.call(history, state, "", location.href); - this.location = location; - this.restorationIdentifier = restorationIdentifier; - } - getRestorationDataForIdentifier(restorationIdentifier) { - return this.restorationData[restorationIdentifier] || {}; - } - updateRestorationData(additionalData) { - const {restorationIdentifier: restorationIdentifier} = this; - const restorationData = this.restorationData[restorationIdentifier]; - this.restorationData[restorationIdentifier] = { - ...restorationData, - ...additionalData - }; - } - assumeControlOfScrollRestoration() { - if (!this.previousScrollRestoration) { - this.previousScrollRestoration = history.scrollRestoration ?? "auto"; - history.scrollRestoration = "manual"; - } - } - relinquishControlOfScrollRestoration() { - if (this.previousScrollRestoration) { - history.scrollRestoration = this.previousScrollRestoration; - delete this.previousScrollRestoration; - } - } - onPopState=event => { - if (this.shouldHandlePopState()) { - const {turbo: turbo} = event.state || {}; - if (turbo) { - this.location = new URL(window.location.href); - const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo; - this.restorationIdentifier = restorationIdentifier; - const direction = restorationIndex > this.currentIndex ? "forward" : "back"; - this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction); - this.currentIndex = restorationIndex; - } - } - }; - onPageLoad=async _event => { - await nextMicrotask(); - this.pageLoaded = true; - }; - shouldHandlePopState() { - return this.pageIsLoaded(); - } - pageIsLoaded() { - return this.pageLoaded || document.readyState == "complete"; - } -} - -class LinkPrefetchObserver { - started=false; - #prefetchedLink=null; - constructor(delegate, eventTarget) { - this.delegate = delegate; - this.eventTarget = eventTarget; - } - start() { - if (this.started) return; - if (this.eventTarget.readyState === "loading") { - this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, { - once: true - }); - } else { - this.#enable(); - } - } - stop() { - if (!this.started) return; - this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, { - capture: true, - passive: true - }); - this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, { - capture: true, - passive: true - }); - this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true); - this.started = false; - } - #enable=() => { - this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, { - capture: true, - passive: true - }); - this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, { - capture: true, - passive: true - }); - this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true); - this.started = true; - }; - #tryToPrefetchRequest=event => { - if (getMetaContent("turbo-prefetch") === "false") return; - const target = event.target; - const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])"); - if (isLink && this.#isPrefetchable(target)) { - const link = target; - const location = getLocationForLink(link); - if (this.delegate.canPrefetchRequestToLocation(link, location)) { - this.#prefetchedLink = link; - const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target); - fetchRequest.fetchOptions.priority = "low"; - prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl); - } - } - }; - #cancelRequestIfObsolete=event => { - if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest(); - }; - #cancelPrefetchRequest=() => { - prefetchCache.clear(); - this.#prefetchedLink = null; - }; - #tryToUsePrefetchedRequest=event => { - if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") { - const cached = prefetchCache.get(event.detail.url.toString()); - if (cached) { - event.detail.fetchRequest = cached; - } - prefetchCache.clear(); - } - }; - prepareRequest(request) { - const link = request.target; - request.headers["X-Sec-Purpose"] = "prefetch"; - const turboFrame = link.closest("turbo-frame"); - const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id; - if (turboFrameTarget && turboFrameTarget !== "_top") { - request.headers["Turbo-Frame"] = turboFrameTarget; - } - } - requestSucceededWithResponse() {} - requestStarted(fetchRequest) {} - requestErrored(fetchRequest) {} - requestFinished(fetchRequest) {} - requestPreventedHandlingResponse(fetchRequest, fetchResponse) {} - requestFailedWithResponse(fetchRequest, fetchResponse) {} - get #cacheTtl() { - return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl; - } - #isPrefetchable(link) { - const href = link.getAttribute("href"); - if (!href) return false; - if (unfetchableLink(link)) return false; - if (linkToTheSamePage(link)) return false; - if (linkOptsOut(link)) return false; - if (nonSafeLink(link)) return false; - if (eventPrevented(link)) return false; - return true; - } -} - -const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target"); - -const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#"); - -const linkOptsOut = link => { - if (link.getAttribute("data-turbo-prefetch") === "false") return true; - if (link.getAttribute("data-turbo") === "false") return true; - const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]"); - if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true; - return false; -}; - -const nonSafeLink = link => { - const turboMethod = link.getAttribute("data-turbo-method"); - if (turboMethod && turboMethod.toLowerCase() !== "get") return true; - if (isUJS(link)) return true; - if (link.hasAttribute("data-turbo-confirm")) return true; - if (link.hasAttribute("data-turbo-stream")) return true; - return false; -}; - -const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method"); - -const eventPrevented = link => { - const event = dispatch("turbo:before-prefetch", { - target: link, - cancelable: true - }); - return event.defaultPrevented; -}; - -class Navigator { - constructor(delegate) { - this.delegate = delegate; - } - proposeVisit(location, options = {}) { - if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) { - this.delegate.visitProposedToLocation(location, options); - } - } - startVisit(locatable, restorationIdentifier, options = {}) { - this.stop(); - this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, { - 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 rootLocation() { - return this.view.snapshot.rootLocation; - } - 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: statusCode, redirected: redirected} = fetchResponse; - const action = this.#getActionForFormSubmission(formSubmission, fetchResponse); - const visitOptions = { - action: action, - shouldCacheSnapshot: shouldCacheSnapshot, - response: { - statusCode: statusCode, - responseHTML: responseHTML, - redirected: 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); - } - if (!snapshot.shouldPreserveScrollPosition) { - this.view.scrollToTop(); - } - this.view.clearSnapshotCache(); - } - } - formSubmissionErrored(formSubmission, error) { - console.error(error); - } - formSubmissionFinished(formSubmission) { - if (typeof this.adapter.formSubmissionFinished === "function") { - this.adapter.formSubmissionFinished(formSubmission); - } - } - linkPrefetchingIsEnabledForLocation(location) { - if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") { - return this.adapter.linkPrefetchingIsEnabledForLocation(location); - } - return true; - } - visitStarted(visit) { - this.delegate.visitStarted(visit); - } - visitCompleted(visit) { - this.delegate.visitCompleted(visit); - delete this.currentVisit; - } - 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(formSubmission, fetchResponse) { - const {submitter: submitter, formElement: formElement} = formSubmission; - return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse); - } - #getDefaultAction(fetchResponse) { - const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href; - return sameLocationRedirect ? "replace" : "advance"; - } -} - -const PageStage = { - initial: 0, - loading: 1, - interactive: 2, - complete: 3 -}; - -class PageObserver { - stage=PageStage.initial; - started=false; - constructor(delegate) { - 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; - } - } - interpretReadyState=() => { - const {readyState: readyState} = this; - if (readyState == "interactive") { - this.pageIsInteractive(); - } else if (readyState == "complete") { - this.pageIsComplete(); - } - }; - 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(); - } - } - pageWillUnload=() => { - this.delegate.pageWillUnload(); - }; - get readyState() { - return document.readyState; - } -} - -class ScrollObserver { - started=false; - constructor(delegate) { - 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; - } - } - onScroll=() => { - this.updatePosition({ - x: window.pageXOffset, - y: window.pageYOffset - }); - }; - updatePosition(position) { - this.delegate.scrollPositionChanged(position); - } -} - -class StreamMessageRenderer { - render({fragment: fragment}) { - Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => { - withAutofocusFromFragment(fragment, (() => { - withPreservedFocus((() => { - 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: 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; -} - -async function withAutofocusFromFragment(fragment, callback) { - const generatedID = `turbo-stream-autofocus-${uuid()}`; - const turboStreams = fragment.querySelectorAll("turbo-stream"); - const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams); - let willAutofocusId = null; - if (elementWithAutofocus) { - if (elementWithAutofocus.id) { - willAutofocusId = elementWithAutofocus.id; - } else { - willAutofocusId = generatedID; - } - elementWithAutofocus.id = willAutofocusId; - } - callback(); - await nextRepaint(); - const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body; - if (hasNoActiveElement && willAutofocusId) { - const elementToAutofocus = document.getElementById(willAutofocusId); - if (elementIsFocusable(elementToAutofocus)) { - elementToAutofocus.focus(); - } - if (elementToAutofocus && elementToAutofocus.id == generatedID) { - elementToAutofocus.removeAttribute("id"); - } - } -} - -async function withPreservedFocus(callback) { - const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement)); - const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id; - if (restoreFocusTo) { - const elementToFocus = document.getElementById(restoreFocusTo); - if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) { - elementToFocus.focus(); - } - } -} - -function firstAutofocusableElementInStreams(nodeListOfStreamElements) { - for (const streamElement of nodeListOfStreamElements) { - const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content); - if (elementWithAutofocus) return elementWithAutofocus; - } - return null; -} - -class StreamObserver { - sources=new Set; - #started=false; - constructor(delegate) { - 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); - } - inspectFetchResponse=event => { - const response = fetchResponseFromEvent(event); - if (response && fetchResponseIsStream(response)) { - event.preventDefault(); - this.receiveMessageResponse(response); - } - }; - receiveMessageEvent=event => { - if (this.#started && typeof event.data == "string") { - this.receiveMessageHTML(event.data); - } - }; - async receiveMessageResponse(response) { - const html = await response.responseHTML; - if (html) { - this.receiveMessageHTML(html); - } - } - receiveMessageHTML(html) { - this.delegate.receivedMessageFromStream(StreamMessage.wrap(html)); - } -} - -function fetchResponseFromEvent(event) { - const fetchResponse = event.detail?.fetchResponse; - if (fetchResponse instanceof FetchResponse) { - return fetchResponse; - } -} - -function fetchResponseIsStream(response) { - const contentType = response.contentType ?? ""; - return contentType.startsWith(StreamMessage.contentType); -} - -class ErrorRenderer extends Renderer { - static renderElement(currentElement, newElement) { - const {documentElement: documentElement, body: body} = document; - documentElement.replaceChild(newElement, body); - } - async render() { - this.replaceHeadAndBody(); - this.activateScriptElements(); - } - replaceHeadAndBody() { - const {documentElement: documentElement, head: 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() { - this.#setLanguage(); - 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; - } - #setLanguage() { - const {documentElement: documentElement} = this.currentSnapshot; - const {lang: lang} = this.newSnapshot; - if (lang) { - documentElement.setAttribute("lang", lang); - } else { - documentElement.removeAttribute("lang"); - } - } - async mergeHead() { - const mergedHeadElements = this.mergeProvisionalElements(); - const newStylesheetElements = this.copyNewHeadStylesheetElements(); - this.copyNewHeadScriptElements(); - await mergedHeadElements; - await newStylesheetElements; - if (this.willRender) { - this.removeUnusedDynamicStylesheetElements(); - } - } - 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)); - } - } - removeUnusedDynamicStylesheetElements() { - for (const element of this.unusedDynamicStylesheetElements) { - document.head.removeChild(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 unusedDynamicStylesheetElements() { - return this.oldHeadStylesheetElements.filter((element => element.getAttribute("data-turbo-track") === "dynamic")); - } - get oldHeadStylesheetElements() { - return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot); - } - 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"); - } -} - -export class MorphingPageRenderer extends PageRenderer { - static renderElement(currentElement, newElement) { - morphElements(currentElement, newElement, { - callbacks: { - beforeNodeMorphed: element => !canRefreshFrame(element) - } - }) - - for (const frame of currentElement.querySelectorAll("turbo-frame")) { - if (canRefreshFrame(frame)) frame.reload() - } - - dispatch("turbo:morph", { detail: { currentElement, newElement } }) - } - - async preservingPermanentElements(callback) { - return await callback() - } - - get renderMethod() { - return "morph" - } - - get shouldAutofocus() { - return false - } -} - -function canRefreshFrame(frame) { - return frame instanceof FrameElement && - frame.src && - frame.refresh === "morph" && - !frame.closest("[data-turbo-permanent]") -} - -class SnapshotCache { - keys=[]; - snapshots={}; - constructor(size) { - 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]; - } - } -} - -class PageView extends View { - snapshotCache=new SnapshotCache(10); - lastRenderedLocation=new URL(location.href); - forceReloaded=false; - shouldTransitionTo(newSnapshot) { - return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions; - } - renderPage(snapshot, isPreview = false, willRender = true, visit) { - const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage; - const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer; - const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender); - if (!renderer.shouldRender) { - this.forceReloaded = true; - } else { - visit?.changeHistory(); - } - return this.render(renderer); - } - renderError(snapshot, visit) { - visit?.changeHistory(); - const renderer = new ErrorRenderer(this.snapshot, snapshot, 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); - } - isPageRefresh(visit) { - return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace"; - } - shouldPreserveScrollPosition(visit) { - return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition; - } - get snapshot() { - return PageSnapshot.fromElement(this.element); - } -} - -class Preloader { - selector="a[data-turbo-preload]"; - constructor(delegate, snapshotCache) { - this.delegate = delegate; - this.snapshotCache = snapshotCache; - } - start() { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", this.#preloadAll); - } else { - this.preloadOnLoadLinksForView(document.body); - } - } - stop() { - document.removeEventListener("DOMContentLoaded", this.#preloadAll); - } - preloadOnLoadLinksForView(element) { - for (const link of element.querySelectorAll(this.selector)) { - if (this.delegate.shouldPreloadLink(link)) { - this.preloadURL(link); - } - } - } - async preloadURL(link) { - const location = new URL(link.href); - if (this.snapshotCache.has(location)) { - return; - } - const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link); - await fetchRequest.perform(); - } - prepareRequest(fetchRequest) { - fetchRequest.headers["X-Sec-Purpose"] = "prefetch"; - } - async requestSucceededWithResponse(fetchRequest, fetchResponse) { - try { - const responseHTML = await fetchResponse.responseHTML; - const snapshot = PageSnapshot.fromHTMLString(responseHTML); - this.snapshotCache.put(fetchRequest.url, snapshot); - } catch (_) {} - } - requestStarted(fetchRequest) {} - requestErrored(fetchRequest) {} - requestFinished(fetchRequest) {} - requestPreventedHandlingResponse(fetchRequest, fetchResponse) {} - requestFailedWithResponse(fetchRequest, fetchResponse) {} - #preloadAll=() => { - this.preloadOnLoadLinksForView(document.body); - }; -} - -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); - } -} - -class Session { - navigator=new Navigator(this); - history=new History(this); - view=new PageView(this, document.documentElement); - adapter=new BrowserAdapter(this); - pageObserver=new PageObserver(this); - cacheObserver=new CacheObserver; - linkPrefetchObserver=new LinkPrefetchObserver(this, document); - linkClickObserver=new LinkClickObserver(this, window); - formSubmitObserver=new FormSubmitObserver(this, document); - scrollObserver=new ScrollObserver(this); - streamObserver=new StreamObserver(this); - formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement); - frameRedirector=new FrameRedirector(this, document.documentElement); - streamMessageRenderer=new StreamMessageRenderer; - cache=new Cache(this); - enabled=true; - started=false; - #pageRefreshDebouncePeriod=150; - constructor(recentRequests) { - this.recentRequests = recentRequests; - this.preloader = new Preloader(this, this.view.snapshotCache); - this.debouncedRefresh = this.refresh; - this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod; - } - start() { - if (!this.started) { - this.pageObserver.start(); - this.cacheObserver.start(); - this.linkPrefetchObserver.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.linkPrefetchObserver.stop(); - this.formLinkClickObserver.stop(); - this.linkClickObserver.stop(); - this.formSubmitObserver.stop(); - this.scrollObserver.stop(); - this.streamObserver.stop(); - this.frameRedirector.stop(); - this.history.stop(); - this.preloader.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) { - const action = options.action || getVisitAction(frameElement); - frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action); - frameElement.src = location.toString(); - } else { - this.navigator.proposeVisit(expandURL(location), options); - } - } - refresh(url, requestId) { - const isRecentRequest = requestId && this.recentRequests.has(requestId); - const isCurrentUrl = url === document.baseURI; - if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) { - this.visit(url, { - action: "replace", - shouldCacheSnapshot: false - }); - } - } - 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) { - console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`"); - this.progressBarDelay = delay; - } - set progressBarDelay(delay) { - config.drive.progressBarDelay = delay; - } - get progressBarDelay() { - return config.drive.progressBarDelay; - } - set drive(value) { - config.drive.enabled = value; - } - get drive() { - return config.drive.enabled; - } - set formMode(value) { - config.forms.mode = value; - } - get formMode() { - return config.forms.mode; - } - get location() { - return this.history.location; - } - get restorationIdentifier() { - return this.history.restorationIdentifier; - } - get pageRefreshDebouncePeriod() { - return this.#pageRefreshDebouncePeriod; - } - set pageRefreshDebouncePeriod(value) { - this.refresh = debounce(this.debouncedRefresh.bind(this), value); - this.#pageRefreshDebouncePeriod = value; - } - shouldPreloadLink(element) { - const isUnsafe = element.hasAttribute("data-turbo-method"); - const isStream = element.hasAttribute("data-turbo-stream"); - const frameTarget = element.getAttribute("data-turbo-frame"); - const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])"); - if (isUnsafe || isStream || frame instanceof FrameElement) { - return false; - } else { - const location = new URL(element.href); - return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation); - } - } - historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) { - if (this.enabled) { - this.navigator.startVisit(location, restorationIdentifier, { - action: "restore", - historyChanged: true, - direction: direction - }); - } 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() {} - canPrefetchRequestToLocation(link, location) { - return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location); - } - 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: action, - acceptsStreamResponse: 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); - this.view.markVisitDirection(visit.direction); - } - extendURLWithDeprecatedProperties(visit.location); - if (!visit.silent) { - this.notifyApplicationAfterVisitingLocation(visit.location, visit.action); - } - } - visitCompleted(visit) { - this.view.unmarkVisitDirection(); - 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$1(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() { - if (!this.navigator.currentVisit?.silent) { - this.notifyApplicationBeforeCachingSnapshot(); - } - } - allowsImmediateRender({element: element}, options) { - const event = this.notifyApplicationBeforeRender(element, options); - const {defaultPrevented: defaultPrevented, detail: {render: render}} = event; - if (this.view.renderer && render) { - this.view.renderer.renderElement = render; - } - return !defaultPrevented; - } - viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) { - this.view.lastRenderedLocation = this.history.location; - this.notifyApplicationAfterRender(renderMethod); - } - 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: action - } - }); - } - notifyApplicationBeforeCachingSnapshot() { - return dispatch("turbo:before-cache"); - } - notifyApplicationBeforeRender(newBody, options) { - return dispatch("turbo:before-render", { - detail: { - newBody: newBody, - ...options - }, - cancelable: true - }); - } - notifyApplicationAfterRender(renderMethod) { - return dispatch("turbo:render", { - detail: { - renderMethod: renderMethod - } - }); - } - notifyApplicationAfterPageLoad(timing = {}) { - return dispatch("turbo:load", { - detail: { - url: this.location.href, - timing: 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: fetchResponse - }, - target: frame, - cancelable: true - }); - } - submissionIsNavigatable(form, submitter) { - if (config.forms.mode == "off") { - return false; - } else { - const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true; - if (config.forms.mode == "optin") { - return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null; - } else { - return submitterIsNavigatable && this.elementIsNavigatable(form); - } - } - } - elementIsNavigatable(element) { - const container = findClosestRecursively(element, "[data-turbo]"); - const withinFrame = findClosestRecursively(element, "turbo-frame"); - if (config.drive.enabled || withinFrame) { - if (container) { - return container.getAttribute("data-turbo") != "false"; - } else { - return true; - } - } else { - if (container) { - return container.getAttribute("data-turbo") == "true"; - } else { - 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(); - } - } -}; - -const session = new Session(recentRequests); - -const {cache: cache, 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) { - console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`"); - config.drive.progressBarDelay = delay; -} - -function setConfirmMethod(confirmMethod) { - console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`"); - config.forms.confirm = confirmMethod; -} - -function setFormMode(mode) { - console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`"); - config.forms.mode = mode; -} - -function morphBodyElements(currentBody, newBody) { - MorphingPageRenderer.renderElement(currentBody, newBody); -} - -function morphTurboFrameElements(currentFrame, newFrame) { - MorphingFrameRenderer.renderElement(currentFrame, newFrame); -} - -var Turbo = Object.freeze({ - __proto__: null, - navigator: navigator$1, - session: session, - cache: cache, - PageRenderer: PageRenderer, - PageSnapshot: PageSnapshot, - FrameRenderer: FrameRenderer, - fetch: fetchWithTurboHeaders, - config: config, - start: start, - registerAdapter: registerAdapter, - visit: visit, - connectStreamSource: connectStreamSource, - disconnectStreamSource: disconnectStreamSource, - renderStreamMessage: renderStreamMessage, - clearCache: clearCache, - setProgressBarDelay: setProgressBarDelay, - setConfirmMethod: setConfirmMethod, - setFormMode: setFormMode, - morphBodyElements: morphBodyElements, - morphTurboFrameElements: morphTurboFrameElements, - morphChildren: morphChildren, - morphElements: morphElements -}); - -class TurboFrameMissingError extends Error {} - -class FrameController { - fetchResponseLoaded=_fetchResponse => Promise.resolve(); - #currentFetchRequest=null; - #resolveVisitPromise=() => {}; - #connected=false; - #hasBeenLoaded=false; - #ignoredAttributes=new Set; - #shouldMorphFrame=false; - action=null; - constructor(element) { - 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(); - } 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(); - } - } - disabledChanged() { - if (this.loadingStyle == FrameLoadingStyle.eager) { - this.#loadSourceURL(); - } - } - sourceURLChanged() { - if (this.#isIgnoringChangesTo("src")) return; - if (this.element.isConnected) { - this.complete = false; - } - if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) { - this.#loadSourceURL(); - } - } - sourceURLReloaded() { - const {refresh: refresh, src: src} = this.element; - this.#shouldMorphFrame = src && refresh === "morph"; - this.element.removeAttribute("complete"); - this.element.src = null; - this.element.src = src; - return this.element.loaded; - } - loadingStyleChanged() { - if (this.loadingStyle == FrameLoadingStyle.lazy) { - this.appearanceObserver.start(); - } else { - this.appearanceObserver.stop(); - this.#loadSourceURL(); - } - } - 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; - } - } - 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); - } - } - } finally { - this.#shouldMorphFrame = false; - this.fetchResponseLoaded = () => Promise.resolve(); - } - } - elementAppearedInViewport(element) { - this.proposeVisitIfNavigatedWithAction(element, getVisitAction(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(); - } - this.formSubmission = new FormSubmission(this, element, submitter); - const {fetchRequest: fetchRequest} = this.formSubmission; - this.prepareRequest(fetchRequest); - this.formSubmission.start(); - } - prepareRequest(request) { - request.headers["Turbo-Frame"] = this.id; - if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) { - request.acceptResponseType(StreamMessage.contentType); - } - } - 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: formElement}) { - markAsBusy(formElement, this.#findFrameElement(formElement)); - } - formSubmissionSucceededWithResponse(formSubmission, response) { - const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter); - frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame)); - frame.delegate.loadResponse(response); - if (!formSubmission.isSafe) { - session.clearCache(); - } - } - formSubmissionFailedWithResponse(formSubmission, fetchResponse) { - this.element.delegate.loadResponse(fetchResponse); - session.clearCache(); - } - formSubmissionErrored(formSubmission, error) { - console.error(error); - } - formSubmissionFinished({formElement: formElement}) { - clearBusyState(formElement, this.#findFrameElement(formElement)); - } - allowsImmediateRender({element: newFrame}, options) { - const event = dispatch("turbo:before-frame-render", { - target: this.element, - detail: { - newFrame: newFrame, - ...options - }, - cancelable: true - }); - const {defaultPrevented: defaultPrevented, detail: {render: render}} = event; - if (this.view.renderer && render) { - this.view.renderer.renderElement = render; - } - return !defaultPrevented; - } - viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {} - preloadOnLoadLinksForView(element) { - session.preloadOnLoadLinksForView(element); - } - viewInvalidated() {} - willRenderFrame(currentElement, _newElement) { - this.previousFrameElement = currentElement.cloneNode(true); - } - visitCachedSnapshot=({element: element}) => { - const frame = element.querySelector("#" + this.element.id); - if (frame && this.previousFrameElement) { - frame.replaceChildren(...this.previousFrameElement.children); - } - delete this.previousFrameElement; - }; - async #loadFrameResponse(fetchResponse, document) { - const newFrameElement = await this.extractForeignFrameElement(document.body); - const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer; - if (newFrameElement) { - const snapshot = new Snapshot(newFrameElement); - const renderer = new rendererClass(this, this.view.snapshot, snapshot, 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); - await this.fetchResponseLoaded(fetchResponse); - } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) { - this.#handleFrameMissingFromResponse(fetchResponse); - } - } - async #visit(url) { - const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element); - this.#currentFetchRequest?.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, getVisitAction(submitter, element, frame)); - this.#withCurrentNavigationElement(element, (() => { - frame.src = url; - })); - } - proposeVisitIfNavigatedWithAction(frame, action = null) { - this.action = action; - if (this.action) { - const pageSnapshot = PageSnapshot.fromElement(frame).clone(); - const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate; - frame.delegate.fetchResponseLoaded = async fetchResponse => { - if (frame.src) { - const {statusCode: statusCode, redirected: redirected} = fetchResponse; - const responseHTML = await fetchResponse.responseHTML; - const response = { - statusCode: statusCode, - redirected: redirected, - responseHTML: responseHTML - }; - const options = { - response: response, - visitCachedSnapshot: visitCachedSnapshot, - willRender: false, - updateHistory: false, - restorationIdentifier: this.restorationIdentifier, - snapshot: pageSnapshot - }; - if (this.action) options.action = this.action; - session.visit(frame.src, options); - } - }; - } - } - changeHistory() { - if (this.action) { - const method = getHistoryMethodForAction(this.action); - session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier); - } - } - 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: response, - visit: 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: location, redirected: redirected, statusCode: statusCode} = wrapped; - return session.visit(location, { - response: { - redirected: redirected, - statusCode: statusCode, - responseHTML: responseHTML - } - }); - } - #findFrameElement(element, submitter) { - const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target"); - return getFrameElementById(id) ?? 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); - } - } catch (error) { - console.error(error); - return new FrameElement; - } - return null; - } - #formActionIsVisitable(form, submitter) { - const action = getAction$1(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; - } - if (!this.enabled || id == "_top") { - return false; - } - if (id) { - const frameElement = getFrameElementById(id); - if (frameElement) { - return !frameElement.disabled; - } - } - if (!session.elementIsNavigatable(element)) { - return false; - } - if (submitter && !session.elementIsNavigatable(submitter)) { - return false; - } - return true; - } - get id() { - return this.element.id; - } - get enabled() { - return !this.element.disabled; - } - get sourceURL() { - if (this.element.src) { - return this.element.src; - } - } - set sourceURL(sourceURL) { - this.#ignoringChangesToAttribute("src", (() => { - this.element.src = 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) { - if (value) { - this.element.setAttribute("complete", ""); - } else { - this.element.removeAttribute("complete"); - } - } - get isActive() { - return this.element.isActive && this.#connected; - } - get rootLocation() { - const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); - const root = meta?.content ?? "/"; - return expandURL(root); - } - #isIgnoringChangesTo(attributeName) { - return this.#ignoredAttributes.has(attributeName); - } - #ignoringChangesToAttribute(attributeName, callback) { - this.#ignoredAttributes.add(attributeName); - callback(); - this.#ignoredAttributes.delete(attributeName); - } - #withCurrentNavigationElement(element, callback) { - this.currentNavigationElement = element; - callback(); - delete this.currentNavigationElement; - } -} - -function getFrameElementById(id) { - if (id != null) { - const element = document.getElementById(id); - if (element instanceof FrameElement) { - return element; - } - } -} - -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`); - } - if (element.ownerDocument !== document) { - element = document.importNode(element, true); - } - if (element instanceof FrameElement) { - element.connectedCallback(); - element.disconnectedCallback(); - return element; - } - } -} - -const StreamActions = { - after() { - this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling))); - }, - append() { - this.removeDuplicateTargetChildren(); - this.targetElements.forEach((e => e.append(this.templateContent))); - }, - before() { - this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e))); - }, - prepend() { - this.removeDuplicateTargetChildren(); - this.targetElements.forEach((e => e.prepend(this.templateContent))); - }, - remove() { - this.targetElements.forEach((e => e.remove())); - }, - replace() { - const method = this.getAttribute("method"); - this.targetElements.forEach((targetElement => { - if (method === "morph") { - morphElements(targetElement, this.templateContent); - } else { - targetElement.replaceWith(this.templateContent); - } - })); - }, - update() { - const method = this.getAttribute("method"); - this.targetElements.forEach((targetElement => { - if (method === "morph") { - morphChildren(targetElement, this.templateContent); - } else { - targetElement.innerHTML = ""; - targetElement.append(this.templateContent); - } - })); - }, - refresh() { - session.refresh(this.baseURI, this.requestId); - } -}; - -class StreamElement extends HTMLElement { - static async renderElement(newElement) { - await newElement.performAction(); - } - async connectedCallback() { - try { - await this.render(); - } catch (error) { - console.error(error); - } finally { - this.disconnect(); - } - } - async render() { - return this.renderPromise ??= (async () => { - const event = this.beforeRenderEvent; - if (this.dispatchEvent(event)) { - await nextRepaint(); - await event.detail.render(this); - } - })(); - } - disconnect() { - try { - this.remove(); - } catch {} - } - removeDuplicateTargetChildren() { - this.duplicateChildren.forEach((c => c.remove())); - } - get duplicateChildren() { - const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.getAttribute("id"))); - const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id"))); - return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id")))); - } - get performAction() { - if (this.action) { - const actionFunction = StreamActions[this.action]; - if (actionFunction) { - return actionFunction; - } - this.#raise("unknown action"); - } - this.#raise("action attribute is missing"); - } - get targetElements() { - if (this.target) { - return this.targetElementsById; - } else if (this.targets) { - return this.targetElementsByQuery; - } 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.#raise("first child element must be a