UNPKG

@hotwired/turbo

Version:

The speed of a single-page web application without having to write any JavaScript

1,459 lines (1,438 loc) 108 kB
/* Turbo 7.0.0 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 ("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); } } }); })(); 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 ["disabled", "loading", "src"]; } connectedCallback() { this.delegate.connect(); } disconnectedCallback() { this.delegate.disconnect(); } reload() { const { src } = this; this.src = null; this.src = src; } attributeChangedCallback(name) { if (name == "loading") { this.delegate.loadingStyleChanged(); } 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 FrameLoadingStyle.lazy; default: return FrameLoadingStyle.eager; } } function expandURL(locatable) { return new URL(locatable.toString(), document.baseURI); } function getAnchor(url) { let anchorMatch; if (url.hash) { return url.hash.slice(1); } else if (anchorMatch = url.href.match(/#(.*)$/)) { return anchorMatch[1]; } } function 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 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 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.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, target = null) { this.abortController = new AbortController; this.resolveRequestPromise = (value) => { }; this.delegate = delegate; this.method = method; this.headers = this.defaultHeaders; if (this.isIdempotent) { this.url = mergeFormDataEntries(location, [...body.entries()]); } else { 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') { 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.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; } 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.href, resume: this.resolveRequestPromise }, target: this.target }); if (event.defaultPrevented) await requestInterception; } } 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.formElement); this.mustRedirect = mustRedirect; } get method() { var _a; const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || ""; return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get; } get action() { var _a; const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null; return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || ""; } 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 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; 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 && value != null && 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) { return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null; } get isConnected() { return this.element.isConnected; } 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]`); } 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; } } class FormInterceptor { constructor(delegate, element) { this.submitBubbled = ((event) => { const form = event.target; if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) { 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.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; this.prepareToRenderSnapshot(renderer); const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve); const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise); if (!immediateRender) await renderInterception; await this.renderSnapshot(renderer); this.delegate.viewRenderedSnapshot(snapshot, isPreview); this.finishRenderingSnapshot(renderer); } finally { delete this.renderer; this.resolveRenderPromise(undefined); delete this.renderPromise; } } 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 Bardo { constructor(permanentElementMap) { this.permanentElementMap = permanentElementMap; } static preservingPermanentElements(permanentElementMap, callback) { const bardo = new this(permanentElementMap); bardo.enter(); callback(); bardo.leave(); } enter() { for (const id in this.permanentElementMap) { const [, newPermanentElement] = this.permanentElementMap[id]; this.replaceNewPermanentElementWithPlaceholder(newPermanentElement); } } leave() { for (const id in this.permanentElementMap) { const [currentPermanentElement] = this.permanentElementMap[id]; this.replaceCurrentPermanentElementWithClone(currentPermanentElement); this.replacePlaceholderWithPermanentElement(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, 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"); if (this.cspNonce) { createdScriptElement.nonce = this.cspNonce; } createdScriptElement.textContent = element.textContent; createdScriptElement.async = false; copyElementAttributes(createdScriptElement, element); return createdScriptElement; } } preservingPermanentElements(callback) { Bardo.preservingPermanentElements(this.permanentElementMap, callback); } focusFirstAutofocusableElement() { const element = this.connectedSnapshot.firstAutofocusableElement; if (elementIsFocusable(element)) { element.focus(); } } 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 cspNonce() { var _a; return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content"); } } function copyElementAttributes(destinationElement, sourceElement) { for (const { name, value } of [...sourceElement.attributes]) { destinationElement.setAttribute(name, value); } } 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(); await nextAnimationFrame(); this.activateScriptElements(); } 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; } activateScriptElements() { for (const inertScriptElement of this.newScriptElements) { const activatedScriptElement = this.createScriptElement(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; } } 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 .filter((element) => !elementIsNoscript(element)) .map((element) => elementWithoutNonce(element)) .reduce((result, element) => { const { outerHTML } = element; const details = outerHTML in result ? result[outerHTML] : { type: elementType(element), tracked: elementIsTracked(element), elements: [] }; return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) }); }, {}); } get trackedElementSignature() { return Object.keys(this.detailsByOuterHTML) .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked) .join(""); } getScriptElementsNotInSnapshot(snapshot) { return this.getElementsMatchingTypeNotInSnapshot("script", snapshot); } getStylesheetElementsNotInSnapshot(snapshot) { return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot); } getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) { return Object.keys(this.detailsByOuterHTML) .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML)) .map(outerHTML => this.detailsByOuterHTML[outerHTML]) .filter(({ type }) => type == matchedType) .map(({ elements: [element] }) => element); } get provisionalElements() { return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML]; if (type == null && !tracked) { return [...result, ...elements]; } else if (elements.length > 1) { return [...result, ...elements.slice(1)]; } else { return result; } }, []); } getMetaValue(name) { const element = this.findMetaElementByName(name); return element ? element.getAttribute("content") : null; } findMetaElementByName(name) { return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { const { elements: [element] } = this.detailsByOuterHTML[outerHTML]; return elementIsMetaElementWithName(element, name) ? element : result; }, undefined); } } 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 elementIsNoscript(element) { const tagName = element.tagName.toLowerCase(); return tagName == "noscript"; } 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; } function elementWithoutNonce(element) { if (element.hasAttribute("nonce")) { element.setAttribute("nonce", ""); } return element; } 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] = "contentTypeMismatch"; })(SystemStatusCode || (SystemStatusCode = {})); class Visit { constructor(delegate, location, restorationIdentifier, options = {}) { this.identifier = uuid(); this.timingMetrics = {}; this.followedRedirect = false; this.historyChanged = false; this.scrolled = false; this.snapshotCached = false; this.state = VisitState.initialized; this.delegate = delegate; this.location = location; this.restorationIdentifier = restorationIdentifier || uuid(); const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options); this.action = action; this.historyChanged = historyChanged; this.referrer = referrer; this.snapshotHTML = snapshotHTML; this.response = response; this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.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.state = VisitState.completed; this.adapter.visitCompleted(this); this.delegate.visitCompleted(this); this.followRedirect(); } } fail() { if (this.state == VisitState.started) { this.state = VisitState.failed; this.adapter.visitFailed(this); } } changeHistory() { var _a; if (!this.historyChanged) { const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action; const method = this.getHistoryMethodForAction(actionForHistory); this.history.update(method, this.location, this.restorationIdentifier); this.historyChanged = true; } } issueRequest() { if (this.hasPreloadedResponse()) { this.simulateRequest(); } else if (this.shouldIssueRequest() && !this.request) { this.request = new FetchRequest(this, FetchMethod.get, this.location); this.request.perform(); } } simulateRequest() { if (this.response) { this.startRequest(); this.recordResponse(); this.finishRequest(); } } startRequest() { this.recordTimingMetric(TimingMetric.requestStart); this.adapter.visitRequestStarted(this); } recordResponse(response = this.response) { this.response = response; if (response) { const { statusCode } = response; if (isSuccessful(statusCode)) { this.adapter.visitRequestCompleted(this); } else { this.adapter.visitRequestFailedWithStatusCode(this, statusCode); } } } finishRequest() { this.recordTimingMetric(TimingMetric.requestEnd); this.adapter.visitRequestFinished(this); } loadResponse() { if (this.response) { const { statusCode, responseHTML } = this.response; this.render(async () => { this.cacheSnapshot(); if (this.view.renderPromise) await this.view.renderPromise; if (isSuccessful(statusCode) && responseHTML != null) { await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML)); this.adapter.visitRendered(this); this.complete(); } else { await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML)); this.adapter.visitRendered(this); this.fail(); } }); } } getCachedSnapshot() { const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot(); if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) { if (this.action == "restore" || snapshot.isPreviewable) { return snapshot; } } } getPreloadedSnapshot() { if (this.snapshotHTML) { return PageSnapshot.fromHTMLString(this.snapshotHTML); } } hasCachedSnapshot() { return this.getCachedSnapshot() != null; } loadCachedSnapshot() { const snapshot = this.getCachedSnapshot(); if (snapshot) { const isPreview = this.shouldIssueRequest(); this.render(async () => { this.cacheSnapshot(); if (this.isSamePage) { this.adapter.visitRendered(this); } else { if (this.view.renderPromise) await this.view.renderPromise; await this.view.renderPage(snapshot, isPreview); this.adapter.vi