@hotwired/turbo
Version:
The speed of a single-page web application without having to write any JavaScript
1,271 lines (1,246 loc) • 152 kB
JavaScript
/*
Turbo 7.2.4
Copyright © 2022 37signals LLC
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Turbo = {}));
})(this, (function (exports) { 'use strict';
(function () {
if (window.Reflect === undefined ||
window.customElements === undefined ||
window.customElements.polyfillWrapFlushCallback) {
return;
}
const BuiltInHTMLElement = HTMLElement;
const wrapperForTheName = {
HTMLElement: function HTMLElement() {
return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
},
};
window.HTMLElement = wrapperForTheName["HTMLElement"];
HTMLElement.prototype = BuiltInHTMLElement.prototype;
HTMLElement.prototype.constructor = HTMLElement;
Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
})();
/**
* The MIT License (MIT)
*
* Copyright (c) 2019 Javan Makhmali
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function(prototype) {
if (typeof prototype.requestSubmit == "function") return
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 === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null;
}
function clickCaptured(event) {
const submitter = findSubmitterFromClickTarget(event.target);
if (submitter && submitter.form) {
submittersByForm.set(submitter.form, submitter);
}
}
(function () {
if ("submitter" in Event.prototype)
return;
let prototype;
if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
prototype = window.SubmitEvent.prototype;
}
else if ("SubmitEvent" in window) {
return;
}
else {
prototype = window.Event.prototype;
}
addEventListener("click", clickCaptured, true);
Object.defineProperty(prototype, "submitter", {
get() {
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
return submittersByForm.get(this.target);
}
},
});
})();
exports.FrameLoadingStyle = void 0;
(function (FrameLoadingStyle) {
FrameLoadingStyle["eager"] = "eager";
FrameLoadingStyle["lazy"] = "lazy";
})(exports.FrameLoadingStyle || (exports.FrameLoadingStyle = {}));
class FrameElement extends HTMLElement {
constructor() {
super();
this.loaded = Promise.resolve();
this.delegate = new FrameElement.delegateConstructor(this);
}
static get observedAttributes() {
return ["disabled", "complete", "loading", "src"];
}
connectedCallback() {
this.delegate.connect();
}
disconnectedCallback() {
this.delegate.disconnect();
}
reload() {
return this.delegate.sourceURLReloaded();
}
attributeChangedCallback(name) {
if (name == "loading") {
this.delegate.loadingStyleChanged();
}
else if (name == "complete") {
this.delegate.completeChanged();
}
else if (name == "src") {
this.delegate.sourceURLChanged();
}
else {
this.delegate.disabledChanged();
}
}
get src() {
return this.getAttribute("src");
}
set src(value) {
if (value) {
this.setAttribute("src", value);
}
else {
this.removeAttribute("src");
}
}
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() {
var _a, _b;
return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
}
}
function frameLoadingStyleFromString(style) {
switch (style.toLowerCase()) {
case "lazy":
return exports.FrameLoadingStyle.lazy;
default:
return exports.FrameLoadingStyle.eager;
}
}
function expandURL(locatable) {
return new URL(locatable.toString(), document.baseURI);
}
function getAnchor(url) {
let anchorMatch;
if (url.hash) {
return url.hash.slice(1);
}
else if ((anchorMatch = url.href.match(/#(.*)$/))) {
return anchorMatch[1];
}
}
function getAction(form, submitter) {
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
return expandURL(action);
}
function getExtension(url) {
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
}
function isHTML(url) {
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
}
function isPrefixedBy(baseURL, url) {
const prefix = getPrefix(url);
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
}
function locationIsVisitable(location, rootLocation) {
return isPrefixedBy(location, rootLocation) && isHTML(location);
}
function getRequestURL(url) {
const anchor = getAnchor(url);
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
}
function toCacheKey(url) {
return getRequestURL(url);
}
function urlsAreEqual(left, right) {
return expandURL(left).href == expandURL(right).href;
}
function getPathComponents(url) {
return url.pathname.split("/").slice(1);
}
function getLastPathComponent(url) {
return getPathComponents(url).slice(-1)[0];
}
function getPrefix(url) {
return addTrailingSlash(url.origin + url.pathname);
}
function addTrailingSlash(value) {
return value.endsWith("/") ? value : value + "/";
}
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);
}
}
function isAction(action) {
return action == "advance" || action == "replace" || action == "restore";
}
function activateScriptElement(element) {
if (element.getAttribute("data-turbo-eval") == "false") {
return element;
}
else {
const createdScriptElement = document.createElement("script");
const cspNonce = getMetaContent("csp-nonce");
if (cspNonce) {
createdScriptElement.nonce = cspNonce;
}
createdScriptElement.textContent = element.textContent;
createdScriptElement.async = false;
copyElementAttributes(createdScriptElement, element);
return createdScriptElement;
}
}
function copyElementAttributes(destinationElement, sourceElement) {
for (const { name, value } of sourceElement.attributes) {
destinationElement.setAttribute(name, value);
}
}
function createDocumentFragment(html) {
const template = document.createElement("template");
template.innerHTML = html;
return template.content;
}
function dispatch(eventName, { target, cancelable, detail } = {}) {
const event = new CustomEvent(eventName, {
cancelable,
bubbles: true,
detail,
});
if (target && target.isConnected) {
target.dispatchEvent(event);
}
else {
document.documentElement.dispatchEvent(event);
}
return event;
}
function nextAnimationFrame() {
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
}
function nextEventLoopTick() {
return new Promise((resolve) => setTimeout(() => resolve(), 0));
}
function nextMicrotask() {
return Promise.resolve();
}
function parseHTMLDocument(html = "") {
return new DOMParser().parseFromString(html, "text/html");
}
function unindent(strings, ...values) {
const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
const match = lines[0].match(/^\s+/);
const indent = match ? match[0].length : 0;
return lines.map((line) => line.slice(indent)).join("\n");
}
function interpolate(strings, values) {
return strings.reduce((result, string, i) => {
const value = values[i] == undefined ? "" : values[i];
return result + string + value;
}, "");
}
function uuid() {
return Array.from({ length: 36 })
.map((_, i) => {
if (i == 8 || i == 13 || i == 18 || i == 23) {
return "-";
}
else if (i == 14) {
return "4";
}
else if (i == 19) {
return (Math.floor(Math.random() * 4) + 8).toString(16);
}
else {
return Math.floor(Math.random() * 15).toString(16);
}
})
.join("");
}
function getAttribute(attributeName, ...elements) {
for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
if (typeof value == "string")
return value;
}
return null;
}
function hasAttribute(attributeName, ...elements) {
return elements.some((element) => element && element.hasAttribute(attributeName));
}
function markAsBusy(...elements) {
for (const element of elements) {
if (element.localName == "turbo-frame") {
element.setAttribute("busy", "");
}
element.setAttribute("aria-busy", "true");
}
}
function clearBusyState(...elements) {
for (const element of elements) {
if (element.localName == "turbo-frame") {
element.removeAttribute("busy");
}
element.removeAttribute("aria-busy");
}
}
function waitForLoad(element, timeoutInMilliseconds = 2000) {
return new Promise((resolve) => {
const onComplete = () => {
element.removeEventListener("error", onComplete);
element.removeEventListener("load", onComplete);
resolve();
};
element.addEventListener("load", onComplete, { once: true });
element.addEventListener("error", onComplete, { once: true });
setTimeout(resolve, timeoutInMilliseconds);
});
}
function getHistoryMethodForAction(action) {
switch (action) {
case "replace":
return history.replaceState;
case "advance":
case "restore":
return history.pushState;
}
}
function getVisitAction(...elements) {
const action = getAttribute("data-turbo-action", ...elements);
return isAction(action) ? action : null;
}
function getMetaElement(name) {
return document.querySelector(`meta[name="${name}"]`);
}
function getMetaContent(name) {
const element = getMetaElement(name);
return element && element.content;
}
function setMetaContent(name, content) {
let element = getMetaElement(name);
if (!element) {
element = document.createElement("meta");
element.setAttribute("name", name);
document.head.appendChild(element);
}
element.setAttribute("content", content);
return element;
}
var FetchMethod;
(function (FetchMethod) {
FetchMethod[FetchMethod["get"] = 0] = "get";
FetchMethod[FetchMethod["post"] = 1] = "post";
FetchMethod[FetchMethod["put"] = 2] = "put";
FetchMethod[FetchMethod["patch"] = 3] = "patch";
FetchMethod[FetchMethod["delete"] = 4] = "delete";
})(FetchMethod || (FetchMethod = {}));
function fetchMethodFromString(method) {
switch (method.toLowerCase()) {
case "get":
return FetchMethod.get;
case "post":
return FetchMethod.post;
case "put":
return FetchMethod.put;
case "patch":
return FetchMethod.patch;
case "delete":
return FetchMethod.delete;
}
}
class FetchRequest {
constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
this.abortController = new AbortController();
this.resolveRequestPromise = (_value) => { };
this.delegate = delegate;
this.method = method;
this.headers = this.defaultHeaders;
this.body = body;
this.url = location;
this.target = target;
}
get location() {
return this.url;
}
get params() {
return this.url.searchParams;
}
get entries() {
return this.body ? Array.from(this.body.entries()) : [];
}
cancel() {
this.abortController.abort();
}
async perform() {
var _a, _b;
const { fetchOptions } = this;
(_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
await this.allowRequestToBeIntercepted(fetchOptions);
try {
this.delegate.requestStarted(this);
const response = await fetch(this.url.href, fetchOptions);
return await this.receive(response);
}
catch (error) {
if (error.name !== "AbortError") {
if (this.willDelegateErrorHandling(error)) {
this.delegate.requestErrored(this, error);
}
throw error;
}
}
finally {
this.delegate.requestFinished(this);
}
}
async receive(response) {
const fetchResponse = new FetchResponse(response);
const event = dispatch("turbo:before-fetch-response", {
cancelable: true,
detail: { fetchResponse },
target: this.target,
});
if (event.defaultPrevented) {
this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
}
else if (fetchResponse.succeeded) {
this.delegate.requestSucceededWithResponse(this, fetchResponse);
}
else {
this.delegate.requestFailedWithResponse(this, fetchResponse);
}
return fetchResponse;
}
get fetchOptions() {
var _a;
return {
method: FetchMethod[this.method].toUpperCase(),
credentials: "same-origin",
headers: this.headers,
redirect: "follow",
body: this.isIdempotent ? null : this.body,
signal: this.abortSignal,
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
};
}
get defaultHeaders() {
return {
Accept: "text/html, application/xhtml+xml",
};
}
get isIdempotent() {
return this.method == FetchMethod.get;
}
get abortSignal() {
return this.abortController.signal;
}
acceptResponseType(mimeType) {
this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
}
async allowRequestToBeIntercepted(fetchOptions) {
const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
const event = dispatch("turbo:before-fetch-request", {
cancelable: true,
detail: {
fetchOptions,
url: this.url,
resume: this.resolveRequestPromise,
},
target: this.target,
});
if (event.defaultPrevented)
await requestInterception;
}
willDelegateErrorHandling(error) {
const event = dispatch("turbo:fetch-request-error", {
target: this.target,
cancelable: true,
detail: { request: this, error: error },
});
return !event.defaultPrevented;
}
}
class AppearanceObserver {
constructor(delegate, element) {
this.started = false;
this.intersect = (entries) => {
const lastEntry = entries.slice(-1)[0];
if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
this.delegate.elementAppearedInViewport(this.element);
}
};
this.delegate = delegate;
this.element = element;
this.intersectionObserver = new IntersectionObserver(this.intersect);
}
start() {
if (!this.started) {
this.started = true;
this.intersectionObserver.observe(this.element);
}
}
stop() {
if (this.started) {
this.started = false;
this.intersectionObserver.unobserve(this.element);
}
}
}
class StreamMessage {
constructor(fragment) {
this.fragment = importStreamElements(fragment);
}
static wrap(message) {
if (typeof message == "string") {
return new this(createDocumentFragment(message));
}
else {
return message;
}
}
}
StreamMessage.contentType = "text/vnd.turbo-stream.html";
function importStreamElements(fragment) {
for (const element of fragment.querySelectorAll("turbo-stream")) {
const streamElement = document.importNode(element, true);
for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
}
element.replaceWith(streamElement);
}
return fragment;
}
var FormSubmissionState;
(function (FormSubmissionState) {
FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
})(FormSubmissionState || (FormSubmissionState = {}));
var FormEnctype;
(function (FormEnctype) {
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
FormEnctype["multipart"] = "multipart/form-data";
FormEnctype["plain"] = "text/plain";
})(FormEnctype || (FormEnctype = {}));
function formEnctypeFromString(encoding) {
switch (encoding.toLowerCase()) {
case FormEnctype.multipart:
return FormEnctype.multipart;
case FormEnctype.plain:
return FormEnctype.plain;
default:
return FormEnctype.urlEncoded;
}
}
class FormSubmission {
constructor(delegate, formElement, submitter, mustRedirect = false) {
this.state = FormSubmissionState.initialized;
this.delegate = delegate;
this.formElement = formElement;
this.submitter = submitter;
this.formData = buildFormData(formElement, submitter);
this.location = expandURL(this.action);
if (this.method == FetchMethod.get) {
mergeFormDataEntries(this.location, [...this.body.entries()]);
}
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
this.mustRedirect = mustRedirect;
}
static confirmMethod(message, _element, _submitter) {
return Promise.resolve(confirm(message));
}
get method() {
var _a;
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
}
get action() {
var _a;
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
return this.submitter.getAttribute("formaction") || "";
}
else {
return this.formElement.getAttribute("action") || formElementAction || "";
}
}
get body() {
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
return new URLSearchParams(this.stringFormData);
}
else {
return this.formData;
}
}
get enctype() {
var _a;
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
}
get isIdempotent() {
return this.fetchRequest.isIdempotent;
}
get stringFormData() {
return [...this.formData].reduce((entries, [name, value]) => {
return entries.concat(typeof value == "string" ? [[name, value]] : []);
}, []);
}
async start() {
const { initialized, requesting } = FormSubmissionState;
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
if (typeof confirmationMessage === "string") {
const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
if (!answer) {
return;
}
}
if (this.state == initialized) {
this.state = requesting;
return this.fetchRequest.perform();
}
}
stop() {
const { stopping, stopped } = FormSubmissionState;
if (this.state != stopping && this.state != stopped) {
this.state = stopping;
this.fetchRequest.cancel();
return true;
}
}
prepareHeadersForRequest(headers, request) {
if (!request.isIdempotent) {
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
if (token) {
headers["X-CSRF-Token"] = token;
}
}
if (this.requestAcceptsTurboStreamResponse(request)) {
request.acceptResponseType(StreamMessage.contentType);
}
}
requestStarted(_request) {
var _a;
this.state = FormSubmissionState.waiting;
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
dispatch("turbo:submit-start", {
target: this.formElement,
detail: { formSubmission: this },
});
this.delegate.formSubmissionStarted(this);
}
requestPreventedHandlingResponse(request, response) {
this.result = { success: response.succeeded, fetchResponse: response };
}
requestSucceededWithResponse(request, response) {
if (response.clientError || response.serverError) {
this.delegate.formSubmissionFailedWithResponse(this, response);
}
else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
const error = new Error("Form responses must redirect to another location");
this.delegate.formSubmissionErrored(this, error);
}
else {
this.state = FormSubmissionState.receiving;
this.result = { success: true, fetchResponse: response };
this.delegate.formSubmissionSucceededWithResponse(this, response);
}
}
requestFailedWithResponse(request, response) {
this.result = { success: false, fetchResponse: response };
this.delegate.formSubmissionFailedWithResponse(this, response);
}
requestErrored(request, error) {
this.result = { success: false, error };
this.delegate.formSubmissionErrored(this, error);
}
requestFinished(_request) {
var _a;
this.state = FormSubmissionState.stopped;
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
dispatch("turbo:submit-end", {
target: this.formElement,
detail: Object.assign({ formSubmission: this }, this.result),
});
this.delegate.formSubmissionFinished(this);
}
requestMustRedirect(request) {
return !request.isIdempotent && this.mustRedirect;
}
requestAcceptsTurboStreamResponse(request) {
return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
}
}
function buildFormData(formElement, submitter) {
const formData = new FormData(formElement);
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
if (name) {
formData.append(name, value || "");
}
return formData;
}
function getCookieValue(cookieName) {
if (cookieName != null) {
const cookies = document.cookie ? document.cookie.split("; ") : [];
const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));
if (cookie) {
const value = cookie.split("=").slice(1).join("=");
return value ? decodeURIComponent(value) : undefined;
}
}
}
function responseSucceededWithoutRedirect(response) {
return response.statusCode == 200 && !response.redirected;
}
function mergeFormDataEntries(url, entries) {
const searchParams = new URLSearchParams();
for (const [name, value] of entries) {
if (value instanceof File)
continue;
searchParams.append(name, value);
}
url.search = searchParams.toString();
return url;
}
class Snapshot {
constructor(element) {
this.element = element;
}
get activeElement() {
return this.element.ownerDocument.activeElement;
}
get children() {
return [...this.element.children];
}
hasAnchor(anchor) {
return this.getElementForAnchor(anchor) != null;
}
getElementForAnchor(anchor) {
return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;
}
get isConnected() {
return this.element.isConnected;
}
get firstAutofocusableElement() {
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
for (const element of this.element.querySelectorAll("[autofocus]")) {
if (element.closest(inertDisabledOrHidden) == null)
return element;
else
continue;
}
return null;
}
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 } = 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 {
constructor(delegate, eventTarget) {
this.started = false;
this.submitCaptured = () => {
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
};
this.submitBubbled = ((event) => {
if (!event.defaultPrevented) {
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
const submitter = event.submitter || undefined;
if (form &&
submissionDoesNotDismissDialog(form, submitter) &&
submissionDoesNotTargetIFrame(form, submitter) &&
this.delegate.willSubmitForm(form, submitter)) {
event.preventDefault();
event.stopImmediatePropagation();
this.delegate.formSubmitted(form, submitter);
}
}
});
this.delegate = delegate;
this.eventTarget = eventTarget;
}
start() {
if (!this.started) {
this.eventTarget.addEventListener("submit", this.submitCaptured, true);
this.started = true;
}
}
stop() {
if (this.started) {
this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
this.started = false;
}
}
}
function submissionDoesNotDismissDialog(form, submitter) {
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
return method != "dialog";
}
function submissionDoesNotTargetIFrame(form, submitter) {
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
for (const element of document.getElementsByName(target)) {
if (element instanceof HTMLIFrameElement)
return false;
}
return true;
}
class View {
constructor(delegate, element) {
this.resolveRenderPromise = (_value) => { };
this.resolveInterceptionPromise = (_value) => { };
this.delegate = delegate;
this.element = element;
}
scrollToAnchor(anchor) {
const element = this.snapshot.getElementForAnchor(anchor);
if (element) {
this.scrollToElement(element);
this.focusElement(element);
}
else {
this.scrollToPosition({ x: 0, y: 0 });
}
}
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, y }) {
this.scrollRoot.scrollTo(x, y);
}
scrollToTop() {
this.scrollToPosition({ x: 0, y: 0 });
}
get scrollRoot() {
return window;
}
async render(renderer) {
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
if (shouldRender) {
try {
this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
this.renderer = renderer;
await this.prepareToRenderSnapshot(renderer);
const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
if (!immediateRender)
await renderInterception;
await this.renderSnapshot(renderer);
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
this.delegate.preloadOnLoadLinksForView(this.element);
this.finishRenderingSnapshot(renderer);
}
finally {
delete this.renderer;
this.resolveRenderPromise(undefined);
delete this.renderPromise;
}
}
else {
this.invalidate(renderer.reloadReason);
}
}
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");
}
}
async renderSnapshot(renderer) {
await renderer.render();
}
finishRenderingSnapshot(renderer) {
renderer.finishRendering();
}
}
class FrameView extends View {
invalidate() {
this.element.innerHTML = "";
}
get snapshot() {
return new Snapshot(this.element);
}
}
class LinkInterceptor {
constructor(delegate, element) {
this.clickBubbled = (event) => {
if (this.respondsToEventTarget(event.target)) {
this.clickEvent = event;
}
else {
delete this.clickEvent;
}
};
this.linkClicked = ((event) => {
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
this.clickEvent.preventDefault();
event.preventDefault();
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
}
}
delete this.clickEvent;
});
this.willVisit = ((_event) => {
delete this.clickEvent;
});
this.delegate = delegate;
this.element = element;
}
start() {
this.element.addEventListener("click", this.clickBubbled);
document.addEventListener("turbo:click", this.linkClicked);
document.addEventListener("turbo:before-visit", this.willVisit);
}
stop() {
this.element.removeEventListener("click", this.clickBubbled);
document.removeEventListener("turbo:click", this.linkClicked);
document.removeEventListener("turbo:before-visit", this.willVisit);
}
respondsToEventTarget(target) {
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
return element && element.closest("turbo-frame, html") == this.element;
}
}
class LinkClickObserver {
constructor(delegate, eventTarget) {
this.started = false;
this.clickCaptured = () => {
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
this.eventTarget.addEventListener("click", this.clickBubbled, false);
};
this.clickBubbled = (event) => {
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
const target = (event.composedPath && event.composedPath()[0]) || event.target;
const link = this.findLinkFromClickTarget(target);
if (link && doesNotTargetIFrame(link)) {
const location = this.getLocationForLink(link);
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
event.preventDefault();
this.delegate.followedLinkToLocation(link, location);
}
}
}
};
this.delegate = delegate;
this.eventTarget = eventTarget;
}
start() {
if (!this.started) {
this.eventTarget.addEventListener("click", this.clickCaptured, true);
this.started = true;
}
}
stop() {
if (this.started) {
this.eventTarget.removeEventListener("click", this.clickCaptured, true);
this.started = false;
}
}
clickEventIsSignificant(event) {
return !((event.target && event.target.isContentEditable) ||
event.defaultPrevented ||
event.which > 1 ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey);
}
findLinkFromClickTarget(target) {
if (target instanceof Element) {
return target.closest("a[href]:not([target^=_]):not([download])");
}
}
getLocationForLink(link) {
return expandURL(link.getAttribute("href") || "");
}
}
function doesNotTargetIFrame(anchor) {
for (const element of document.getElementsByName(anchor.target)) {
if (element instanceof HTMLIFrameElement)
return false;
}
return true;
}
class FormLinkClickObserver {
constructor(delegate, element) {
this.delegate = delegate;
this.linkInterceptor = new LinkClickObserver(this, element);
}
start() {
this.linkInterceptor.start();
}
stop() {
this.linkInterceptor.stop();
}
willFollowLinkToLocation(link, location, originalEvent) {
return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
link.hasAttribute("data-turbo-method"));
}
followedLinkToLocation(link, location) {
const action = location.href;
const form = document.createElement("form");
form.setAttribute("data-turbo", "true");
form.setAttribute("action", action);
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 = link.getAttribute("data-turbo-action");
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 {
constructor(delegate, permanentElementMap) {
this.delegate = delegate;
this.permanentElementMap = permanentElementMap;
}
static preservingPermanentElements(delegate, permanentElementMap, callback) {
const bardo = new this(delegate, permanentElementMap);
bardo.enter();
callback();
bardo.leave();
}
enter() {
for (const id in this.permanentElementMap) {
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
}
}
leave() {
for (const id in this.permanentElementMap) {
const [currentPermanentElement] = this.permanentElementMap[id];
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
this.delegate.leavingBardo(currentPermanentElement);
}
}
replaceNewPermanentElementWithPlaceholder(permanentElement) {
const placeholder = createPlaceholderForPermanentElement(permanentElement);
permanentElement.replaceWith(placeholder);
}
replaceCurrentPermanentElementWithClone(permanentElement) {
const clone = permanentElement.cloneNode(true);
permanentElement.replaceWith(clone);
}
replacePlaceholderWithPermanentElement(permanentElement) {
const placeholder = this.getPlaceholderById(permanentElement.id);
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
}
getPlaceholderById(id) {
return this.placeholders.find((element) => element.content == id);
}
get placeholders() {
return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
}
}
function createPlaceholderForPermanentElement(permanentElement) {
const element = document.createElement("meta");
element.setAttribute("name", "turbo-permanent-placeholder");
element.setAttribute("content", permanentElement.id);
return element;
}
class Renderer {
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
this.activeElement = null;
this.currentSnapshot = currentSnapshot;
this.newSnapshot = newSnapshot;
this.isPreview = isPreview;
this.willRender = willRender;
this.renderElement = renderElement;
this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
}
get shouldRender() {
return true;
}
get