@hotwired/turbo
Version:
The speed of a single-page web application without having to write any JavaScript
1,506 lines (1,478 loc) • 90.5 kB
JavaScript
/*
Turbo 7.0.0-beta.4
Copyright © 2021 Basecamp, LLC
*/
(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);
})();
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 ("SubmitEvent" in window)
return;
addEventListener("click", clickCaptured, true);
Object.defineProperty(Event.prototype, "submitter", {
get() {
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
return submittersByForm.get(this.target);
}
}
});
})();
var FrameLoadingStyle;
(function (FrameLoadingStyle) {
FrameLoadingStyle["eager"] = "eager";
FrameLoadingStyle["lazy"] = "lazy";
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
class FrameElement extends HTMLElement {
constructor() {
super();
this.loaded = Promise.resolve();
this.delegate = new FrameElement.delegateConstructor(this);
}
static get observedAttributes() {
return ["loading", "src"];
}
connectedCallback() {
this.delegate.connect();
}
disconnectedCallback() {
this.delegate.disconnect();
}
attributeChangedCallback(name) {
if (name == "loading") {
this.delegate.loadingStyleChanged();
}
else if (name == "src") {
this.delegate.sourceURLChanged();
}
}
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 FrameLoadingStyle.lazy;
default: return FrameLoadingStyle.eager;
}
}
function expandURL(locatable) {
const anchor = document.createElement("a");
anchor.href = locatable.toString();
return new URL(anchor.href);
}
function getAnchor(url) {
let anchorMatch;
if (url.hash) {
return url.hash.slice(1);
}
else if (anchorMatch = url.href.match(/#(.*)$/)) {
return anchorMatch[1];
}
else {
return "";
}
}
function getExtension(url) {
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
}
function isHTML(url) {
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
}
function isPrefixedBy(baseURL, url) {
const prefix = getPrefix(url);
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
}
function toCacheKey(url) {
const anchorLength = url.hash.length;
if (anchorLength < 2) {
return url.href;
}
else {
return url.href.slice(0, -anchorLength);
}
}
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.text();
}
get responseHTML() {
if (this.isHTML) {
return this.response.text();
}
else {
return Promise.resolve(undefined);
}
}
header(name) {
return this.response.headers.get(name);
}
}
function dispatch(eventName, { target, cancelable, detail } = {}) {
const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
void (target || 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.apply(null, { 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("");
}
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) {
this.abortController = new AbortController;
this.delegate = delegate;
this.method = method;
if (this.isIdempotent) {
this.url = mergeFormDataEntries(location, [...body.entries()]);
}
else {
this.body = body;
this.url = location;
}
}
get location() {
return this.url;
}
get params() {
return this.url.searchParams;
}
get entries() {
return this.body ? Array.from(this.body.entries()) : [];
}
cancel() {
this.abortController.abort();
}
async perform() {
const { fetchOptions } = this;
dispatch("turbo:before-fetch-request", { detail: { fetchOptions } });
try {
this.delegate.requestStarted(this);
const response = await fetch(this.url.href, fetchOptions);
return await this.receive(response);
}
catch (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 } });
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() {
return {
method: FetchMethod[this.method].toUpperCase(),
credentials: "same-origin",
headers: this.headers,
redirect: "follow",
body: this.body,
signal: this.abortSignal
};
}
get isIdempotent() {
return this.method == FetchMethod.get;
}
get headers() {
const headers = Object.assign({}, this.defaultHeaders);
if (typeof this.delegate.prepareHeadersForRequest == "function") {
this.delegate.prepareHeadersForRequest(headers, this);
}
return headers;
}
get abortSignal() {
return this.abortController.signal;
}
get defaultHeaders() {
return {
"Accept": "text/html, application/xhtml+xml"
};
}
}
function mergeFormDataEntries(url, entries) {
const currentSearchParams = new URLSearchParams(url.search);
for (const [name, value] of entries) {
if (value instanceof File)
continue;
if (currentSearchParams.has(name)) {
currentSearchParams.delete(name);
url.searchParams.set(name, value);
}
else {
url.searchParams.append(name, value);
}
}
return url;
}
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(html) {
this.templateElement = document.createElement("template");
this.templateElement.innerHTML = html;
}
static wrap(message) {
if (typeof message == "string") {
return new this(message);
}
else {
return message;
}
}
get fragment() {
const fragment = document.createDocumentFragment();
for (const element of this.foreignElements) {
fragment.appendChild(document.importNode(element, true));
}
return fragment;
}
get foreignElements() {
return this.templateChildren.reduce((streamElements, child) => {
if (child.tagName.toLowerCase() == "turbo-stream") {
return [...streamElements, child];
}
else {
return streamElements;
}
}, []);
}
get templateChildren() {
return Array.from(this.templateElement.content.children);
}
}
StreamMessage.contentType = "text/vnd.turbo-stream.html";
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.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
this.mustRedirect = mustRedirect;
}
get method() {
var _a;
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
}
get action() {
var _a;
return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
}
get location() {
return expandURL(this.action);
}
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 stringFormData() {
return [...this.formData].reduce((entries, [name, value]) => {
return entries.concat(typeof value == "string" ? [[name, value]] : []);
}, []);
}
async start() {
const { initialized, requesting } = FormSubmissionState;
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;
}
headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
}
}
requestStarted(request) {
this.state = FormSubmissionState.waiting;
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) {
this.state = FormSubmissionState.stopped;
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;
}
}
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.get(name) != value) {
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 getMetaContent(name) {
const element = document.querySelector(`meta[name="${name}"]`);
return element && element.content;
}
function responseSucceededWithoutRedirect(response) {
return response.statusCode == 200 && !response.redirected;
}
class Snapshot {
constructor(element) {
this.element = element;
}
get children() {
return [...this.element.children];
}
hasAnchor(anchor) {
return this.getElementForAnchor(anchor) != null;
}
getElementForAnchor(anchor) {
try {
return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
}
catch (_a) {
return null;
}
}
get firstAutofocusableElement() {
return this.element.querySelector("[autofocus]");
}
get permanentElements() {
return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
}
getPermanentElementById(id) {
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
}
getPermanentElementsPresentInSnapshot(snapshot) {
return this.permanentElements.filter(({ id }) => snapshot.getPermanentElementById(id));
}
}
class FormInterceptor {
constructor(delegate, element) {
this.submitBubbled = ((event) => {
if (event.target instanceof HTMLFormElement) {
const form = event.target;
const submitter = event.submitter || undefined;
if (this.delegate.shouldInterceptFormSubmission(form, submitter)) {
event.preventDefault();
event.stopImmediatePropagation();
this.delegate.formSubmissionIntercepted(form, submitter);
}
}
});
this.delegate = delegate;
this.element = element;
}
start() {
this.element.addEventListener("submit", this.submitBubbled);
}
stop() {
this.element.removeEventListener("submit", this.submitBubbled);
}
}
class View {
constructor(delegate, element) {
this.delegate = delegate;
this.element = element;
}
scrollToAnchor(anchor) {
const element = this.snapshot.getElementForAnchor(anchor);
if (element) {
this.scrollToElement(element);
}
else {
this.scrollToPosition({ x: 0, y: 0 });
}
}
scrollToElement(element) {
element.scrollIntoView();
}
scrollToPosition({ x, y }) {
this.scrollRoot.scrollTo(x, y);
}
get scrollRoot() {
return window;
}
async render(renderer) {
if (this.renderer) {
throw new Error("rendering is already in progress");
}
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
if (shouldRender) {
try {
this.renderer = renderer;
this.prepareToRenderSnapshot(renderer);
this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
await this.renderSnapshot(renderer);
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
this.finishRenderingSnapshot(renderer);
}
finally {
delete this.renderer;
}
}
else {
this.invalidate();
}
}
invalidate() {
this.delegate.viewInvalidated();
}
prepareToRenderSnapshot(renderer) {
this.markAsPreview(renderer.isPreview);
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)) {
this.clickEvent.preventDefault();
event.preventDefault();
this.delegate.linkClickIntercepted(event.target, event.detail.url);
}
}
delete this.clickEvent;
});
this.willVisit = () => {
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 Renderer {
constructor(currentSnapshot, newSnapshot, isPreview) {
this.currentSnapshot = currentSnapshot;
this.newSnapshot = newSnapshot;
this.isPreview = isPreview;
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
}
get shouldRender() {
return true;
}
prepareToRender() {
return;
}
finishRendering() {
if (this.resolvingFunctions) {
this.resolvingFunctions.resolve();
delete this.resolvingFunctions;
}
}
createScriptElement(element) {
if (element.getAttribute("data-turbo-eval") == "false") {
return element;
}
else {
const createdScriptElement = document.createElement("script");
createdScriptElement.textContent = element.textContent;
createdScriptElement.async = false;
copyElementAttributes(createdScriptElement, element);
return createdScriptElement;
}
}
preservingPermanentElements(callback) {
const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
callback();
replacePlaceholderElementsWithClonedPermanentElements(placeholders);
}
focusFirstAutofocusableElement() {
const element = this.newSnapshot.firstAutofocusableElement;
if (elementIsFocusable(element)) {
element.focus();
}
}
get currentElement() {
return this.currentSnapshot.element;
}
get newElement() {
return this.newSnapshot.element;
}
}
function replaceElementWithElement(fromElement, toElement) {
const parentElement = fromElement.parentElement;
if (parentElement) {
return parentElement.replaceChild(toElement, fromElement);
}
}
function copyElementAttributes(destinationElement, sourceElement) {
for (const { name, value } of [...sourceElement.attributes]) {
destinationElement.setAttribute(name, value);
}
}
function createPlaceholderForPermanentElement(permanentElement) {
const element = document.createElement("meta");
element.setAttribute("name", "turbo-permanent-placeholder");
element.setAttribute("content", permanentElement.id);
return { element, permanentElement };
}
function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
for (const { element, permanentElement } of placeholders) {
const clonedElement = permanentElement.cloneNode(true);
replaceElementWithElement(element, clonedElement);
}
}
function relocatePermanentElements(currentSnapshot, newSnapshot) {
return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce((placeholders, permanentElement) => {
const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
if (newElement) {
const placeholder = createPlaceholderForPermanentElement(permanentElement);
replaceElementWithElement(permanentElement, placeholder.element);
replaceElementWithElement(newElement, permanentElement);
return [...placeholders, placeholder];
}
else {
return placeholders;
}
}, []);
}
function elementIsFocusable(element) {
return element && typeof element.focus == "function";
}
class FrameRenderer extends Renderer {
get shouldRender() {
return true;
}
async render() {
await nextAnimationFrame();
this.preservingPermanentElements(() => {
this.loadFrameElement();
});
this.scrollFrameIntoView();
await nextAnimationFrame();
this.focusFirstAutofocusableElement();
}
loadFrameElement() {
var _a;
const destinationRange = document.createRange();
destinationRange.selectNodeContents(this.currentElement);
destinationRange.deleteContents();
const frameElement = this.newElement;
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
if (sourceRange) {
sourceRange.selectNodeContents(frameElement);
this.currentElement.appendChild(sourceRange.extractContents());
}
}
scrollFrameIntoView() {
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
const element = this.currentElement.firstElementChild;
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
if (element) {
element.scrollIntoView({ block });
return true;
}
}
return false;
}
}
function readScrollLogicalPosition(value, defaultValue) {
if (value == "end" || value == "start" || value == "center" || value == "nearest") {
return value;
}
else {
return defaultValue;
}
}
class FrameController {
constructor(element) {
this.resolveVisitPromise = () => { };
this.element = element;
this.view = new FrameView(this, this.element);
this.appearanceObserver = new AppearanceObserver(this, this.element);
this.linkInterceptor = new LinkInterceptor(this, this.element);
this.formInterceptor = new FormInterceptor(this, this.element);
}
connect() {
if (this.loadingStyle == FrameLoadingStyle.lazy) {
this.appearanceObserver.start();
}
this.linkInterceptor.start();
this.formInterceptor.start();
}
disconnect() {
this.appearanceObserver.stop();
this.linkInterceptor.stop();
this.formInterceptor.stop();
}
sourceURLChanged() {
if (this.loadingStyle == FrameLoadingStyle.eager) {
this.loadSourceURL();
}
}
loadingStyleChanged() {
if (this.loadingStyle == FrameLoadingStyle.lazy) {
this.appearanceObserver.start();
}
else {
this.appearanceObserver.stop();
this.loadSourceURL();
}
}
async loadSourceURL() {
if (this.isActive && this.sourceURL && this.sourceURL != this.loadingURL) {
try {
this.loadingURL = this.sourceURL;
this.element.loaded = this.visit(this.sourceURL);
this.appearanceObserver.stop();
await this.element.loaded;
}
finally {
delete this.loadingURL;
}
}
}
async loadResponse(response) {
try {
const html = await response.responseHTML;
if (html) {
const { body } = parseHTMLDocument(html);
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
await this.view.render(renderer);
}
}
catch (error) {
console.error(error);
this.view.invalidate();
}
}
elementAppearedInViewport(element) {
this.loadSourceURL();
}
shouldInterceptLinkClick(element, url) {
return this.shouldInterceptNavigation(element);
}
linkClickIntercepted(element, url) {
this.navigateFrame(element, url);
}
shouldInterceptFormSubmission(element) {
return this.shouldInterceptNavigation(element);
}
formSubmissionIntercepted(element, submitter) {
if (this.formSubmission) {
this.formSubmission.stop();
}
this.formSubmission = new FormSubmission(this, element, submitter);
if (this.formSubmission.fetchRequest.isIdempotent) {
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
}
else {
this.formSubmission.start();
}
}
prepareHeadersForRequest(headers, request) {
headers["Turbo-Frame"] = this.id;
}
requestStarted(request) {
this.element.setAttribute("busy", "");
}
requestPreventedHandlingResponse(request, response) {
this.resolveVisitPromise();
}
async requestSucceededWithResponse(request, response) {
await this.loadResponse(response);
this.resolveVisitPromise();
}
requestFailedWithResponse(request, response) {
console.error(response);
this.resolveVisitPromise();
}
requestErrored(request, error) {
console.error(error);
this.resolveVisitPromise();
}
requestFinished(request) {
this.element.removeAttribute("busy");
}
formSubmissionStarted(formSubmission) {
}
formSubmissionSucceededWithResponse(formSubmission, response) {
const frame = this.findFrameElement(formSubmission.formElement);
frame.delegate.loadResponse(response);
}
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
this.element.delegate.loadResponse(fetchResponse);
}
formSubmissionErrored(formSubmission, error) {
}
formSubmissionFinished(formSubmission) {
}
viewWillRenderSnapshot(snapshot, isPreview) {
}
viewRenderedSnapshot(snapshot, isPreview) {
}
viewInvalidated() {
}
async visit(url) {
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
return new Promise(resolve => {
this.resolveVisitPromise = () => {
this.resolveVisitPromise = () => { };
resolve();
};
request.perform();
});
}
navigateFrame(element, url) {
const frame = this.findFrameElement(element);
frame.src = url;
}
findFrameElement(element) {
var _a;
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
}
async extractForeignFrameElement(container) {
let element;
const id = CSS.escape(this.id);
if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
return element;
}
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
await element.loaded;
return await this.extractForeignFrameElement(element);
}
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
return new FrameElement();
}
shouldInterceptNavigation(element) {
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
if (!this.enabled || id == "_top") {
return false;
}
if (id) {
const frameElement = getFrameElementById(id);
if (frameElement) {
return !frameElement.disabled;
}
}
return true;
}
get id() {
return this.element.id;
}
get enabled() {
return !this.element.disabled;
}
get sourceURL() {
return this.element.src;
}
get loadingStyle() {
return this.element.loading;
}
get isLoading() {
return this.formSubmission !== undefined || this.loadingURL !== undefined;
}
get isActive() {
return this.element.isActive;
}
}
function getFrameElementById(id) {
if (id != null) {
const element = document.getElementById(id);
if (element instanceof FrameElement) {
return element;
}
}
}
function activateElement(element) {
if (element && element.ownerDocument !== document) {
element = document.importNode(element, true);
}
if (element instanceof FrameElement) {
return element;
}
}
const StreamActions = {
append() {
var _a;
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
},
prepend() {
var _a;
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
},
remove() {
var _a;
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
},
replace() {
var _a;
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
},
update() {
if (this.targetElement) {
this.targetElement.innerHTML = "";
this.targetElement.append(this.templateContent);
}
}
};
class StreamElement extends HTMLElement {
async connectedCallback() {
try {
await this.render();
}
catch (error) {
console.error(error);
}
finally {
this.disconnect();
}
}
async render() {
var _a;
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
if (this.dispatchEvent(this.beforeRenderEvent)) {
await nextAnimationFrame();
this.performAction();
}
})());
}
disconnect() {
try {
this.remove();
}
catch (_a) { }
}
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 targetElement() {
var _a;
if (this.target) {
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
}
this.raise("target attribute is missing");
}
get templateContent() {
return this.templateElement.content;
}
get templateElement() {
if (this.firstElementChild instanceof HTMLTemplateElement) {
return this.firstElementChild;
}
this.raise("first child element must be a <template> element");
}
get action() {
return this.getAttribute("action");
}
get target() {
return this.getAttribute("target");
}
raise(message) {
throw new Error(`${this.description}: ${message}`);
}
get description() {
var _a, _b;
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
}
get beforeRenderEvent() {
return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
}
}
FrameElement.delegateConstructor = FrameController;
customElements.define("turbo-frame", FrameElement);
customElements.define("turbo-stream", StreamElement);
(() => {
let element = document.currentScript;
if (!element)
return;
if (element.hasAttribute("data-turbo-suppress-warning"))
return;
while (element = element.parentElement) {
if (element == document.body) {
return console.warn(unindent `
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
——
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
`, element.outerHTML);
}
}
})();
class ProgressBar {
constructor() {
this.hiding = false;
this.value = 0;
this.visible = false;
this.trickle = () => {
this.setValue(this.value + Math.random() / 100);
};
this.stylesheetElement = this.createStylesheetElement();
this.progressElement = this.createProgressElement();
this.installStylesheetElement();
this.setValue(0);
}
static get defaultCSS() {
return unindent `
.turbo-progress-bar {
position: fixed;
display: block;
top: 0;
left: 0;
height: 3px;
background: #0076ff;
z-index: 9999;
transition:
width ${ProgressBar.animationDuration}ms ease-out,
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
transform: translate3d(0, 0, 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;
}
refresh() {
requestAnimationFrame(() => {
this.progressElement.style.width = `${10 + (this.value * 90)}%`;
});
}
createStylesheetElement() {
const element = document.createElement("style");
element.type = "text/css";
element.textContent = ProgressBar.defaultCSS;
return element;
}
createProgressElement() {
const element = document.createElement("div");
element.className = "turbo-progress-bar";
return element;
}
}
ProgressBar.animationDuration = 300;
class HeadSnapshot extends Snapshot {
constructor() {
super(...arguments);
this.detailsByOuterHTML = this.children.reduce((result, element) => {
const { outerHTML } = element;
const details = outerHTML in result
? result[outerHTML]
: {
type: elementType(element),
tracked: elementIsTracked(element),
elements: []
};
return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
}, {});
}
get trackedElementSignature() {
return Object.keys(this.detailsByOuterHTML)
.filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
.join("");
}
getScriptElementsNotInSnapshot(snapshot) {
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
}
getStylesheetElementsNotInSnapshot(snapshot) {
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
}
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
return Object.keys(this.detailsByOuterHTML)
.filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
.map(outerHTML => this.detailsByOuterHTML[outerHTML])
.filter(({ type }) => type == matchedType)
.map(({ elements: [element] }) => element);
}
get provisionalElements() {
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];
if (type == null && !tracked) {
return [...result, ...elements];
}
else if (elements.length > 1) {
return [...result, ...elements.slice(1)];
}
else {
return result;
}
}, []);
}
getMetaValue(name) {
const element = this.findMetaElementByName(name);
return element
? element.getAttribute("content")
: null;
}
findMetaElementByName(name) {
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
return elementIsMetaElementWithName(element, name) ? element : result;
}, undefined);
}
}
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.tagName.toLowerCase();
return tagName == "script";
}
function elementIsStylesheet(element) {
const tagName = element.tagName.toLowerCase();
return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
}
function elementIsMetaElementWithName(element, name) {
const tagName = element.tagName.toLowerCase();
return tagName == "meta" && element.getAttribute("name") == name;
}
class PageSnapshot extends Snapshot {
constructor(element, headSnapshot) {
super(element);
this.headSnapshot = headSnapshot;
}
static fromHTMLString(html = "") {
return this.fromDocument(parseHTMLDocument(html));
}
static fromElement(element) {
return this.fromDocument(element.ownerDocument);
}
static fromDocument({ head, body }) {
return new this(body, new HeadSnapshot(head));
}
clone() {
return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
}
get headElement() {
return this.headSnapshot.element;
}
get rootLocation() {
var _a;
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
return expandURL(root);
}
get cacheControlValue() {
return this.getSetting("cache-control");
}
get isPreviewable() {
return this.cacheControlValue != "no-preview";
}
get isCacheable() {
return this.cacheControlValue != "no-cache";
}
get isVisitable() {
return this.getSetting("visit-control") != "reload";
}
getSetting(name) {
return this.headSnapshot.getMetaValue(`turbo-${name}`);
}
}
var TimingMetric;
(function (TimingMetric) {
TimingMetric["visitStart"] = "visitStart";
TimingMetric["requestStart"] = "requestStart";
TimingMetric["requestEnd"] = "requestEnd";
TimingMetric["visitEnd"] = "visitEnd";
})(TimingMetric || (TimingMetric = {}));
var VisitState;
(function (VisitState) {
VisitState["initialized"] = "initialized";
VisitState["started"] = "started";
VisitState["canceled"] = "canceled";
VisitState["failed"] = "failed";
VisitState["completed"] = "completed";
})(VisitState || (VisitState = {}));
const defaultOptions = {
action: "advance",
historyChanged: false
};
var SystemStatusCode;
(function (SystemStatusCode) {
SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2]