From df3fecdf13078cd3afdf43ba9ee2b384b9700f5a Mon Sep 17 00:00:00 2001
From: Vincent Rolea <3525369+virolea@users.noreply.github.com>
Date: Thu, 29 Aug 2024 16:18:49 +0200
Subject: [PATCH] Add Turbo
---
Gemfile.lock | 5 +
Procfile.dev | 1 +
app/assets/builds/rosetta/application.js | 5799 +++++++++++++++++
app/assets/config/rosetta_manifest.js | 2 +-
app/assets/javascripts/rosetta/application.js | 1 +
.../layouts/rosetta/application.html.erb | 1 +
package-lock.json | 449 ++
package.json | 8 +-
rosetta.gemspec | 1 +
9 files changed, 6265 insertions(+), 2 deletions(-)
create mode 100644 app/assets/builds/rosetta/application.js
create mode 100644 app/assets/javascripts/rosetta/application.js
diff --git a/Gemfile.lock b/Gemfile.lock
index 7a11a45..3939452 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,6 +3,7 @@ PATH
specs:
rosetta (0.1.0)
rails (>= 7.2.0)
+ turbo-rails (~> 2.0)
GEM
remote: https://rubygems.org/
@@ -219,6 +220,10 @@ GEM
strscan (3.1.0)
thor (1.3.1)
timeout (0.4.1)
+ turbo-rails (2.0.6)
+ actionpack (>= 6.0.0)
+ activejob (>= 6.0.0)
+ railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
diff --git a/Procfile.dev b/Procfile.dev
index e443012..e87afe3 100644
--- a/Procfile.dev
+++ b/Procfile.dev
@@ -1,2 +1,3 @@
web: env RUBY_DEBUG_OPEN=true bin/rails server
css: npm run build:css:watch
+js: npm run build:js:watch
diff --git a/app/assets/builds/rosetta/application.js b/app/assets/builds/rosetta/application.js
new file mode 100644
index 0000000..dff84fd
--- /dev/null
+++ b/app/assets/builds/rosetta/application.js
@@ -0,0 +1,5799 @@
+var __defProp = Object.defineProperty;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __esm = (fn, res) => function __init() {
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
+};
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+
+// node_modules/@rails/actioncable/src/adapters.js
+var adapters_default;
+var init_adapters = __esm({
+ "node_modules/@rails/actioncable/src/adapters.js"() {
+ adapters_default = {
+ logger: typeof console !== "undefined" ? console : void 0,
+ WebSocket: typeof WebSocket !== "undefined" ? WebSocket : void 0
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/logger.js
+var logger_default;
+var init_logger = __esm({
+ "node_modules/@rails/actioncable/src/logger.js"() {
+ init_adapters();
+ logger_default = {
+ log(...messages) {
+ if (this.enabled) {
+ messages.push(Date.now());
+ adapters_default.logger.log("[ActionCable]", ...messages);
+ }
+ }
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/connection_monitor.js
+var now, secondsSince, ConnectionMonitor, connection_monitor_default;
+var init_connection_monitor = __esm({
+ "node_modules/@rails/actioncable/src/connection_monitor.js"() {
+ init_logger();
+ now = () => (/* @__PURE__ */ new Date()).getTime();
+ secondsSince = (time) => (now() - time) / 1e3;
+ ConnectionMonitor = class {
+ constructor(connection) {
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
+ this.connection = connection;
+ this.reconnectAttempts = 0;
+ }
+ start() {
+ if (!this.isRunning()) {
+ this.startedAt = now();
+ delete this.stoppedAt;
+ this.startPolling();
+ addEventListener("visibilitychange", this.visibilityDidChange);
+ logger_default.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`);
+ }
+ }
+ stop() {
+ if (this.isRunning()) {
+ this.stoppedAt = now();
+ this.stopPolling();
+ removeEventListener("visibilitychange", this.visibilityDidChange);
+ logger_default.log("ConnectionMonitor stopped");
+ }
+ }
+ isRunning() {
+ return this.startedAt && !this.stoppedAt;
+ }
+ recordMessage() {
+ this.pingedAt = now();
+ }
+ recordConnect() {
+ this.reconnectAttempts = 0;
+ delete this.disconnectedAt;
+ logger_default.log("ConnectionMonitor recorded connect");
+ }
+ recordDisconnect() {
+ this.disconnectedAt = now();
+ logger_default.log("ConnectionMonitor recorded disconnect");
+ }
+ // Private
+ startPolling() {
+ this.stopPolling();
+ this.poll();
+ }
+ stopPolling() {
+ clearTimeout(this.pollTimeout);
+ }
+ poll() {
+ this.pollTimeout = setTimeout(
+ () => {
+ this.reconnectIfStale();
+ this.poll();
+ },
+ this.getPollInterval()
+ );
+ }
+ getPollInterval() {
+ const { staleThreshold, reconnectionBackoffRate } = this.constructor;
+ const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10));
+ const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate;
+ const jitter = jitterMax * Math.random();
+ return staleThreshold * 1e3 * backoff * (1 + jitter);
+ }
+ reconnectIfStale() {
+ if (this.connectionIsStale()) {
+ logger_default.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
+ this.reconnectAttempts++;
+ if (this.disconnectedRecently()) {
+ logger_default.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`);
+ } else {
+ logger_default.log("ConnectionMonitor reopening");
+ this.connection.reopen();
+ }
+ }
+ }
+ get refreshedAt() {
+ return this.pingedAt ? this.pingedAt : this.startedAt;
+ }
+ connectionIsStale() {
+ return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
+ }
+ disconnectedRecently() {
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
+ }
+ visibilityDidChange() {
+ if (document.visibilityState === "visible") {
+ setTimeout(
+ () => {
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
+ logger_default.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`);
+ this.connection.reopen();
+ }
+ },
+ 200
+ );
+ }
+ }
+ };
+ ConnectionMonitor.staleThreshold = 6;
+ ConnectionMonitor.reconnectionBackoffRate = 0.15;
+ connection_monitor_default = ConnectionMonitor;
+ }
+});
+
+// node_modules/@rails/actioncable/src/internal.js
+var internal_default;
+var init_internal = __esm({
+ "node_modules/@rails/actioncable/src/internal.js"() {
+ internal_default = {
+ "message_types": {
+ "welcome": "welcome",
+ "disconnect": "disconnect",
+ "ping": "ping",
+ "confirmation": "confirm_subscription",
+ "rejection": "reject_subscription"
+ },
+ "disconnect_reasons": {
+ "unauthorized": "unauthorized",
+ "invalid_request": "invalid_request",
+ "server_restart": "server_restart",
+ "remote": "remote"
+ },
+ "default_mount_path": "/cable",
+ "protocols": [
+ "actioncable-v1-json",
+ "actioncable-unsupported"
+ ]
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/connection.js
+var message_types, protocols, supportedProtocols, indexOf, Connection, connection_default;
+var init_connection = __esm({
+ "node_modules/@rails/actioncable/src/connection.js"() {
+ init_adapters();
+ init_connection_monitor();
+ init_internal();
+ init_logger();
+ ({ message_types, protocols } = internal_default);
+ supportedProtocols = protocols.slice(0, protocols.length - 1);
+ indexOf = [].indexOf;
+ Connection = class {
+ constructor(consumer2) {
+ this.open = this.open.bind(this);
+ this.consumer = consumer2;
+ this.subscriptions = this.consumer.subscriptions;
+ this.monitor = new connection_monitor_default(this);
+ this.disconnected = true;
+ }
+ send(data) {
+ if (this.isOpen()) {
+ this.webSocket.send(JSON.stringify(data));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ open() {
+ if (this.isActive()) {
+ logger_default.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
+ return false;
+ } else {
+ const socketProtocols = [...protocols, ...this.consumer.subprotocols || []];
+ logger_default.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
+ if (this.webSocket) {
+ this.uninstallEventHandlers();
+ }
+ this.webSocket = new adapters_default.WebSocket(this.consumer.url, socketProtocols);
+ this.installEventHandlers();
+ this.monitor.start();
+ return true;
+ }
+ }
+ close({ allowReconnect } = { allowReconnect: true }) {
+ if (!allowReconnect) {
+ this.monitor.stop();
+ }
+ if (this.isOpen()) {
+ return this.webSocket.close();
+ }
+ }
+ reopen() {
+ logger_default.log(`Reopening WebSocket, current state is ${this.getState()}`);
+ if (this.isActive()) {
+ try {
+ return this.close();
+ } catch (error) {
+ logger_default.log("Failed to reopen WebSocket", error);
+ } finally {
+ logger_default.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`);
+ setTimeout(this.open, this.constructor.reopenDelay);
+ }
+ } else {
+ return this.open();
+ }
+ }
+ getProtocol() {
+ if (this.webSocket) {
+ return this.webSocket.protocol;
+ }
+ }
+ isOpen() {
+ return this.isState("open");
+ }
+ isActive() {
+ return this.isState("open", "connecting");
+ }
+ triedToReconnect() {
+ return this.monitor.reconnectAttempts > 0;
+ }
+ // Private
+ isProtocolSupported() {
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
+ }
+ isState(...states) {
+ return indexOf.call(states, this.getState()) >= 0;
+ }
+ getState() {
+ if (this.webSocket) {
+ for (let state in adapters_default.WebSocket) {
+ if (adapters_default.WebSocket[state] === this.webSocket.readyState) {
+ return state.toLowerCase();
+ }
+ }
+ }
+ return null;
+ }
+ installEventHandlers() {
+ for (let eventName in this.events) {
+ const handler = this.events[eventName].bind(this);
+ this.webSocket[`on${eventName}`] = handler;
+ }
+ }
+ uninstallEventHandlers() {
+ for (let eventName in this.events) {
+ this.webSocket[`on${eventName}`] = function() {
+ };
+ }
+ }
+ };
+ Connection.reopenDelay = 500;
+ Connection.prototype.events = {
+ message(event) {
+ if (!this.isProtocolSupported()) {
+ return;
+ }
+ const { identifier, message, reason, reconnect, type } = JSON.parse(event.data);
+ this.monitor.recordMessage();
+ switch (type) {
+ case message_types.welcome:
+ if (this.triedToReconnect()) {
+ this.reconnectAttempted = true;
+ }
+ this.monitor.recordConnect();
+ return this.subscriptions.reload();
+ case message_types.disconnect:
+ logger_default.log(`Disconnecting. Reason: ${reason}`);
+ return this.close({ allowReconnect: reconnect });
+ case message_types.ping:
+ return null;
+ case message_types.confirmation:
+ this.subscriptions.confirmSubscription(identifier);
+ if (this.reconnectAttempted) {
+ this.reconnectAttempted = false;
+ return this.subscriptions.notify(identifier, "connected", { reconnected: true });
+ } else {
+ return this.subscriptions.notify(identifier, "connected", { reconnected: false });
+ }
+ case message_types.rejection:
+ return this.subscriptions.reject(identifier);
+ default:
+ return this.subscriptions.notify(identifier, "received", message);
+ }
+ },
+ open() {
+ logger_default.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`);
+ this.disconnected = false;
+ if (!this.isProtocolSupported()) {
+ logger_default.log("Protocol is unsupported. Stopping monitor and disconnecting.");
+ return this.close({ allowReconnect: false });
+ }
+ },
+ close(event) {
+ logger_default.log("WebSocket onclose event");
+ if (this.disconnected) {
+ return;
+ }
+ this.disconnected = true;
+ this.monitor.recordDisconnect();
+ return this.subscriptions.notifyAll("disconnected", { willAttemptReconnect: this.monitor.isRunning() });
+ },
+ error() {
+ logger_default.log("WebSocket onerror event");
+ }
+ };
+ connection_default = Connection;
+ }
+});
+
+// node_modules/@rails/actioncable/src/subscription.js
+var extend, Subscription;
+var init_subscription = __esm({
+ "node_modules/@rails/actioncable/src/subscription.js"() {
+ extend = function(object, properties) {
+ if (properties != null) {
+ for (let key in properties) {
+ const value = properties[key];
+ object[key] = value;
+ }
+ }
+ return object;
+ };
+ Subscription = class {
+ constructor(consumer2, params = {}, mixin) {
+ this.consumer = consumer2;
+ this.identifier = JSON.stringify(params);
+ extend(this, mixin);
+ }
+ // Perform a channel action with the optional data passed as an attribute
+ perform(action, data = {}) {
+ data.action = action;
+ return this.send(data);
+ }
+ send(data) {
+ return this.consumer.send({ command: "message", identifier: this.identifier, data: JSON.stringify(data) });
+ }
+ unsubscribe() {
+ return this.consumer.subscriptions.remove(this);
+ }
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/subscription_guarantor.js
+var SubscriptionGuarantor, subscription_guarantor_default;
+var init_subscription_guarantor = __esm({
+ "node_modules/@rails/actioncable/src/subscription_guarantor.js"() {
+ init_logger();
+ SubscriptionGuarantor = class {
+ constructor(subscriptions) {
+ this.subscriptions = subscriptions;
+ this.pendingSubscriptions = [];
+ }
+ guarantee(subscription) {
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
+ logger_default.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
+ this.pendingSubscriptions.push(subscription);
+ } else {
+ logger_default.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
+ }
+ this.startGuaranteeing();
+ }
+ forget(subscription) {
+ logger_default.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s) => s !== subscription);
+ }
+ startGuaranteeing() {
+ this.stopGuaranteeing();
+ this.retrySubscribing();
+ }
+ stopGuaranteeing() {
+ clearTimeout(this.retryTimeout);
+ }
+ retrySubscribing() {
+ this.retryTimeout = setTimeout(
+ () => {
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
+ this.pendingSubscriptions.map((subscription) => {
+ logger_default.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
+ this.subscriptions.subscribe(subscription);
+ });
+ }
+ },
+ 500
+ );
+ }
+ };
+ subscription_guarantor_default = SubscriptionGuarantor;
+ }
+});
+
+// node_modules/@rails/actioncable/src/subscriptions.js
+var Subscriptions;
+var init_subscriptions = __esm({
+ "node_modules/@rails/actioncable/src/subscriptions.js"() {
+ init_subscription();
+ init_subscription_guarantor();
+ init_logger();
+ Subscriptions = class {
+ constructor(consumer2) {
+ this.consumer = consumer2;
+ this.guarantor = new subscription_guarantor_default(this);
+ this.subscriptions = [];
+ }
+ create(channelName, mixin) {
+ const channel = channelName;
+ const params = typeof channel === "object" ? channel : { channel };
+ const subscription = new Subscription(this.consumer, params, mixin);
+ return this.add(subscription);
+ }
+ // Private
+ add(subscription) {
+ this.subscriptions.push(subscription);
+ this.consumer.ensureActiveConnection();
+ this.notify(subscription, "initialized");
+ this.subscribe(subscription);
+ return subscription;
+ }
+ remove(subscription) {
+ this.forget(subscription);
+ if (!this.findAll(subscription.identifier).length) {
+ this.sendCommand(subscription, "unsubscribe");
+ }
+ return subscription;
+ }
+ reject(identifier) {
+ return this.findAll(identifier).map((subscription) => {
+ this.forget(subscription);
+ this.notify(subscription, "rejected");
+ return subscription;
+ });
+ }
+ forget(subscription) {
+ this.guarantor.forget(subscription);
+ this.subscriptions = this.subscriptions.filter((s) => s !== subscription);
+ return subscription;
+ }
+ findAll(identifier) {
+ return this.subscriptions.filter((s) => s.identifier === identifier);
+ }
+ reload() {
+ return this.subscriptions.map((subscription) => this.subscribe(subscription));
+ }
+ notifyAll(callbackName, ...args) {
+ return this.subscriptions.map((subscription) => this.notify(subscription, callbackName, ...args));
+ }
+ notify(subscription, callbackName, ...args) {
+ let subscriptions;
+ if (typeof subscription === "string") {
+ subscriptions = this.findAll(subscription);
+ } else {
+ subscriptions = [subscription];
+ }
+ return subscriptions.map((subscription2) => typeof subscription2[callbackName] === "function" ? subscription2[callbackName](...args) : void 0);
+ }
+ subscribe(subscription) {
+ if (this.sendCommand(subscription, "subscribe")) {
+ this.guarantor.guarantee(subscription);
+ }
+ }
+ confirmSubscription(identifier) {
+ logger_default.log(`Subscription confirmed ${identifier}`);
+ this.findAll(identifier).map((subscription) => this.guarantor.forget(subscription));
+ }
+ sendCommand(subscription, command) {
+ const { identifier } = subscription;
+ return this.consumer.send({ command, identifier });
+ }
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/consumer.js
+function createWebSocketURL(url) {
+ if (typeof url === "function") {
+ url = url();
+ }
+ if (url && !/^wss?:/i.test(url)) {
+ const a = document.createElement("a");
+ a.href = url;
+ a.href = a.href;
+ a.protocol = a.protocol.replace("http", "ws");
+ return a.href;
+ } else {
+ return url;
+ }
+}
+var Consumer;
+var init_consumer = __esm({
+ "node_modules/@rails/actioncable/src/consumer.js"() {
+ init_connection();
+ init_subscriptions();
+ Consumer = class {
+ constructor(url) {
+ this._url = url;
+ this.subscriptions = new Subscriptions(this);
+ this.connection = new connection_default(this);
+ this.subprotocols = [];
+ }
+ get url() {
+ return createWebSocketURL(this._url);
+ }
+ send(data) {
+ return this.connection.send(data);
+ }
+ connect() {
+ return this.connection.open();
+ }
+ disconnect() {
+ return this.connection.close({ allowReconnect: false });
+ }
+ ensureActiveConnection() {
+ if (!this.connection.isActive()) {
+ return this.connection.open();
+ }
+ }
+ addSubProtocol(subprotocol) {
+ this.subprotocols = [...this.subprotocols, subprotocol];
+ }
+ };
+ }
+});
+
+// node_modules/@rails/actioncable/src/index.js
+var src_exports = {};
+__export(src_exports, {
+ Connection: () => connection_default,
+ ConnectionMonitor: () => connection_monitor_default,
+ Consumer: () => Consumer,
+ INTERNAL: () => internal_default,
+ Subscription: () => Subscription,
+ SubscriptionGuarantor: () => subscription_guarantor_default,
+ Subscriptions: () => Subscriptions,
+ adapters: () => adapters_default,
+ createConsumer: () => createConsumer,
+ createWebSocketURL: () => createWebSocketURL,
+ getConfig: () => getConfig,
+ logger: () => logger_default
+});
+function createConsumer(url = getConfig("url") || internal_default.default_mount_path) {
+ return new Consumer(url);
+}
+function getConfig(name) {
+ const element = document.head.querySelector(`meta[name='action-cable-${name}']`);
+ if (element) {
+ return element.getAttribute("content");
+ }
+}
+var init_src = __esm({
+ "node_modules/@rails/actioncable/src/index.js"() {
+ init_connection();
+ init_connection_monitor();
+ init_consumer();
+ init_internal();
+ init_subscription();
+ init_subscriptions();
+ init_subscription_guarantor();
+ init_adapters();
+ init_logger();
+ }
+});
+
+// node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js
+var turbo_es2017_esm_exports = {};
+__export(turbo_es2017_esm_exports, {
+ FetchEnctype: () => FetchEnctype,
+ FetchMethod: () => FetchMethod,
+ FetchRequest: () => FetchRequest,
+ FetchResponse: () => FetchResponse,
+ FrameElement: () => FrameElement,
+ FrameLoadingStyle: () => FrameLoadingStyle,
+ FrameRenderer: () => FrameRenderer,
+ PageRenderer: () => PageRenderer,
+ PageSnapshot: () => PageSnapshot,
+ StreamActions: () => StreamActions,
+ StreamElement: () => StreamElement,
+ StreamSourceElement: () => StreamSourceElement,
+ cache: () => cache,
+ clearCache: () => clearCache,
+ connectStreamSource: () => connectStreamSource,
+ disconnectStreamSource: () => disconnectStreamSource,
+ fetch: () => fetchWithTurboHeaders,
+ fetchEnctypeFromString: () => fetchEnctypeFromString,
+ fetchMethodFromString: () => fetchMethodFromString,
+ isSafe: () => isSafe,
+ navigator: () => navigator$1,
+ registerAdapter: () => registerAdapter,
+ renderStreamMessage: () => renderStreamMessage,
+ session: () => session,
+ setConfirmMethod: () => setConfirmMethod,
+ setFormMode: () => setFormMode,
+ setProgressBarDelay: () => setProgressBarDelay,
+ start: () => start,
+ visit: () => visit
+});
+(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);
+var submittersByForm = /* @__PURE__ */ new WeakMap();
+function findSubmitterFromClickTarget(target) {
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
+ const candidate = element ? element.closest("input, button") : null;
+ return candidate?.type == "submit" ? candidate : null;
+}
+function clickCaptured(event) {
+ const submitter = findSubmitterFromClickTarget(event.target);
+ if (submitter && submitter.form) {
+ submittersByForm.set(submitter.form, submitter);
+ }
+}
+(function() {
+ if ("submitter" in Event.prototype) return;
+ let prototype = window.Event.prototype;
+ if ("SubmitEvent" in window) {
+ const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
+ if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
+ prototype = prototypeOfSubmitEvent;
+ } else {
+ return;
+ }
+ }
+ addEventListener("click", clickCaptured, true);
+ Object.defineProperty(prototype, "submitter", {
+ get() {
+ if (this.type == "submit" && this.target instanceof HTMLFormElement) {
+ return submittersByForm.get(this.target);
+ }
+ }
+ });
+})();
+var FrameLoadingStyle = {
+ eager: "eager",
+ lazy: "lazy"
+};
+var FrameElement = class _FrameElement extends HTMLElement {
+ static delegateConstructor = void 0;
+ loaded = Promise.resolve();
+ static get observedAttributes() {
+ return ["disabled", "loading", "src"];
+ }
+ constructor() {
+ super();
+ this.delegate = new _FrameElement.delegateConstructor(this);
+ }
+ connectedCallback() {
+ this.delegate.connect();
+ }
+ disconnectedCallback() {
+ this.delegate.disconnect();
+ }
+ reload() {
+ return this.delegate.sourceURLReloaded();
+ }
+ attributeChangedCallback(name) {
+ if (name == "loading") {
+ this.delegate.loadingStyleChanged();
+ } else if (name == "src") {
+ this.delegate.sourceURLChanged();
+ } else if (name == "disabled") {
+ this.delegate.disabledChanged();
+ }
+ }
+ /**
+ * Gets the URL to lazily load source HTML from
+ */
+ get src() {
+ return this.getAttribute("src");
+ }
+ /**
+ * Sets the URL to lazily load source HTML from
+ */
+ set src(value) {
+ if (value) {
+ this.setAttribute("src", value);
+ } else {
+ this.removeAttribute("src");
+ }
+ }
+ /**
+ * Gets the refresh mode for the frame.
+ */
+ get refresh() {
+ return this.getAttribute("refresh");
+ }
+ /**
+ * Sets the refresh mode for the frame.
+ */
+ set refresh(value) {
+ if (value) {
+ this.setAttribute("refresh", value);
+ } else {
+ this.removeAttribute("refresh");
+ }
+ }
+ /**
+ * Determines if the element is loading
+ */
+ get loading() {
+ return frameLoadingStyleFromString(this.getAttribute("loading") || "");
+ }
+ /**
+ * Sets the value of if the element is loading
+ */
+ set loading(value) {
+ if (value) {
+ this.setAttribute("loading", value);
+ } else {
+ this.removeAttribute("loading");
+ }
+ }
+ /**
+ * Gets the disabled state of the frame.
+ *
+ * If disabled, no requests will be intercepted by the frame.
+ */
+ get disabled() {
+ return this.hasAttribute("disabled");
+ }
+ /**
+ * Sets the disabled state of the frame.
+ *
+ * If disabled, no requests will be intercepted by the frame.
+ */
+ set disabled(value) {
+ if (value) {
+ this.setAttribute("disabled", "");
+ } else {
+ this.removeAttribute("disabled");
+ }
+ }
+ /**
+ * Gets the autoscroll state of the frame.
+ *
+ * If true, the frame will be scrolled into view automatically on update.
+ */
+ get autoscroll() {
+ return this.hasAttribute("autoscroll");
+ }
+ /**
+ * Sets the autoscroll state of the frame.
+ *
+ * If true, the frame will be scrolled into view automatically on update.
+ */
+ set autoscroll(value) {
+ if (value) {
+ this.setAttribute("autoscroll", "");
+ } else {
+ this.removeAttribute("autoscroll");
+ }
+ }
+ /**
+ * Determines if the element has finished loading
+ */
+ get complete() {
+ return !this.delegate.isLoading;
+ }
+ /**
+ * Gets the active state of the frame.
+ *
+ * If inactive, source changes will not be observed.
+ */
+ get isActive() {
+ return this.ownerDocument === document && !this.isPreview;
+ }
+ /**
+ * Sets the active state of the frame.
+ *
+ * If inactive, source changes will not be observed.
+ */
+ get isPreview() {
+ return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
+ }
+};
+function frameLoadingStyleFromString(style) {
+ switch (style.toLowerCase()) {
+ case "lazy":
+ return FrameLoadingStyle.lazy;
+ default:
+ return FrameLoadingStyle.eager;
+ }
+}
+function expandURL(locatable) {
+ return new URL(locatable.toString(), document.baseURI);
+}
+function getAnchor(url) {
+ let anchorMatch;
+ if (url.hash) {
+ return url.hash.slice(1);
+ } else if (anchorMatch = url.href.match(/#(.*)$/)) {
+ return anchorMatch[1];
+ }
+}
+function getAction$1(form, submitter) {
+ const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
+ return expandURL(action);
+}
+function getExtension(url) {
+ return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
+}
+function 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(location2, rootLocation) {
+ return isPrefixedBy(location2, rootLocation) && isHTML(location2);
+}
+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 + "/";
+}
+var FetchResponse = class {
+ 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(void 0);
+ }
+ }
+ header(name) {
+ return this.response.headers.get(name);
+ }
+};
+function activateScriptElement(element) {
+ if (element.getAttribute("data-turbo-eval") == "false") {
+ return element;
+ } else {
+ const createdScriptElement = document.createElement("script");
+ const cspNonce = getMetaContent("csp-nonce");
+ if (cspNonce) {
+ createdScriptElement.nonce = cspNonce;
+ }
+ createdScriptElement.textContent = element.textContent;
+ createdScriptElement.async = false;
+ copyElementAttributes(createdScriptElement, element);
+ return createdScriptElement;
+ }
+}
+function copyElementAttributes(destinationElement, sourceElement) {
+ for (const { name, value } of sourceElement.attributes) {
+ destinationElement.setAttribute(name, value);
+ }
+}
+function createDocumentFragment(html) {
+ const template = document.createElement("template");
+ template.innerHTML = html;
+ return template.content;
+}
+function dispatch(eventName, { target, cancelable, detail } = {}) {
+ const event = new CustomEvent(eventName, {
+ cancelable,
+ bubbles: true,
+ composed: true,
+ detail
+ });
+ if (target && target.isConnected) {
+ target.dispatchEvent(event);
+ } else {
+ document.documentElement.dispatchEvent(event);
+ }
+ return event;
+}
+function nextRepaint() {
+ if (document.visibilityState === "hidden") {
+ return nextEventLoopTick();
+ } else {
+ return nextAnimationFrame();
+ }
+}
+function nextAnimationFrame() {
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
+}
+function nextEventLoopTick() {
+ return new Promise((resolve) => setTimeout(() => resolve(), 0));
+}
+function nextMicrotask() {
+ return Promise.resolve();
+}
+function parseHTMLDocument(html = "") {
+ return new DOMParser().parseFromString(html, "text/html");
+}
+function unindent(strings, ...values) {
+ const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
+ const match = lines[0].match(/^\s+/);
+ const indent = match ? match[0].length : 0;
+ return lines.map((line) => line.slice(indent)).join("\n");
+}
+function interpolate(strings, values) {
+ return strings.reduce((result, string, i) => {
+ const value = values[i] == void 0 ? "" : values[i];
+ return result + string + value;
+ }, "");
+}
+function uuid() {
+ return Array.from({ length: 36 }).map((_, i) => {
+ if (i == 8 || i == 13 || i == 18 || i == 23) {
+ return "-";
+ } else if (i == 14) {
+ return "4";
+ } else if (i == 19) {
+ return (Math.floor(Math.random() * 4) + 8).toString(16);
+ } else {
+ return Math.floor(Math.random() * 15).toString(16);
+ }
+ }).join("");
+}
+function getAttribute(attributeName, ...elements) {
+ for (const value of elements.map((element) => element?.getAttribute(attributeName))) {
+ if (typeof value == "string") return value;
+ }
+ return null;
+}
+function hasAttribute(attributeName, ...elements) {
+ return elements.some((element) => element && element.hasAttribute(attributeName));
+}
+function markAsBusy(...elements) {
+ for (const element of elements) {
+ if (element.localName == "turbo-frame") {
+ element.setAttribute("busy", "");
+ }
+ element.setAttribute("aria-busy", "true");
+ }
+}
+function clearBusyState(...elements) {
+ for (const element of elements) {
+ if (element.localName == "turbo-frame") {
+ element.removeAttribute("busy");
+ }
+ element.removeAttribute("aria-busy");
+ }
+}
+function waitForLoad(element, timeoutInMilliseconds = 2e3) {
+ return new Promise((resolve) => {
+ const onComplete = () => {
+ element.removeEventListener("error", onComplete);
+ element.removeEventListener("load", onComplete);
+ resolve();
+ };
+ element.addEventListener("load", onComplete, { once: true });
+ element.addEventListener("error", onComplete, { once: true });
+ setTimeout(resolve, timeoutInMilliseconds);
+ });
+}
+function getHistoryMethodForAction(action) {
+ switch (action) {
+ case "replace":
+ return history.replaceState;
+ case "advance":
+ case "restore":
+ return history.pushState;
+ }
+}
+function isAction(action) {
+ return action == "advance" || action == "replace" || action == "restore";
+}
+function getVisitAction(...elements) {
+ const action = getAttribute("data-turbo-action", ...elements);
+ return isAction(action) ? action : null;
+}
+function getMetaElement(name) {
+ return document.querySelector(`meta[name="${name}"]`);
+}
+function getMetaContent(name) {
+ const element = getMetaElement(name);
+ return element && element.content;
+}
+function setMetaContent(name, content) {
+ let element = getMetaElement(name);
+ if (!element) {
+ element = document.createElement("meta");
+ element.setAttribute("name", name);
+ document.head.appendChild(element);
+ }
+ element.setAttribute("content", content);
+ return element;
+}
+function findClosestRecursively(element, selector) {
+ if (element instanceof Element) {
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
+ }
+}
+function elementIsFocusable(element) {
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
+ return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
+}
+function queryAutofocusableElement(elementOrDocumentFragment) {
+ return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
+}
+async function around(callback, reader) {
+ const before = reader();
+ callback();
+ await nextAnimationFrame();
+ const after = reader();
+ return [before, after];
+}
+function doesNotTargetIFrame(name) {
+ if (name === "_blank") {
+ return false;
+ } else if (name) {
+ for (const element of document.getElementsByName(name)) {
+ if (element instanceof HTMLIFrameElement) return false;
+ }
+ return true;
+ } else {
+ return true;
+ }
+}
+function findLinkFromClickTarget(target) {
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
+}
+function getLocationForLink(link) {
+ return expandURL(link.getAttribute("href") || "");
+}
+function debounce(fn, delay) {
+ let timeoutId = null;
+ return (...args) => {
+ const callback = () => fn.apply(this, args);
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(callback, delay);
+ };
+}
+var LimitedSet = class extends Set {
+ constructor(maxSize) {
+ super();
+ this.maxSize = maxSize;
+ }
+ add(value) {
+ if (this.size >= this.maxSize) {
+ const iterator = this.values();
+ const oldestValue = iterator.next().value;
+ this.delete(oldestValue);
+ }
+ super.add(value);
+ }
+};
+var recentRequests = new LimitedSet(20);
+var nativeFetch = window.fetch;
+function fetchWithTurboHeaders(url, options = {}) {
+ const modifiedHeaders = new Headers(options.headers || {});
+ const requestUID = uuid();
+ recentRequests.add(requestUID);
+ modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
+ return nativeFetch(url, {
+ ...options,
+ headers: modifiedHeaders
+ });
+}
+function fetchMethodFromString(method) {
+ switch (method.toLowerCase()) {
+ case "get":
+ return FetchMethod.get;
+ case "post":
+ return FetchMethod.post;
+ case "put":
+ return FetchMethod.put;
+ case "patch":
+ return FetchMethod.patch;
+ case "delete":
+ return FetchMethod.delete;
+ }
+}
+var FetchMethod = {
+ get: "get",
+ post: "post",
+ put: "put",
+ patch: "patch",
+ delete: "delete"
+};
+function fetchEnctypeFromString(encoding) {
+ switch (encoding.toLowerCase()) {
+ case FetchEnctype.multipart:
+ return FetchEnctype.multipart;
+ case FetchEnctype.plain:
+ return FetchEnctype.plain;
+ default:
+ return FetchEnctype.urlEncoded;
+ }
+}
+var FetchEnctype = {
+ urlEncoded: "application/x-www-form-urlencoded",
+ multipart: "multipart/form-data",
+ plain: "text/plain"
+};
+var FetchRequest = class {
+ abortController = new AbortController();
+ #resolveRequestPromise = (_value) => {
+ };
+ constructor(delegate, method, location2, requestBody = new URLSearchParams(), target = null, enctype = FetchEnctype.urlEncoded) {
+ const [url, body] = buildResourceAndBody(expandURL(location2), method, requestBody, enctype);
+ this.delegate = delegate;
+ this.url = url;
+ this.target = target;
+ this.fetchOptions = {
+ credentials: "same-origin",
+ redirect: "follow",
+ method: method.toUpperCase(),
+ headers: { ...this.defaultHeaders },
+ body,
+ signal: this.abortSignal,
+ referrer: this.delegate.referrer?.href
+ };
+ this.enctype = enctype;
+ }
+ get method() {
+ return this.fetchOptions.method;
+ }
+ set method(value) {
+ const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData();
+ const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
+ this.url.search = "";
+ const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
+ this.url = url;
+ this.fetchOptions.body = body;
+ this.fetchOptions.method = fetchMethod.toUpperCase();
+ }
+ get headers() {
+ return this.fetchOptions.headers;
+ }
+ set headers(value) {
+ this.fetchOptions.headers = value;
+ }
+ get body() {
+ if (this.isSafe) {
+ return this.url.searchParams;
+ } else {
+ return this.fetchOptions.body;
+ }
+ }
+ set body(value) {
+ this.fetchOptions.body = value;
+ }
+ get location() {
+ return this.url;
+ }
+ get params() {
+ return this.url.searchParams;
+ }
+ get entries() {
+ return this.body ? Array.from(this.body.entries()) : [];
+ }
+ cancel() {
+ this.abortController.abort();
+ }
+ async perform() {
+ const { fetchOptions } = this;
+ this.delegate.prepareRequest(this);
+ const event = await this.#allowRequestToBeIntercepted(fetchOptions);
+ try {
+ this.delegate.requestStarted(this);
+ if (event.detail.fetchRequest) {
+ this.response = event.detail.fetchRequest.response;
+ } else {
+ this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
+ }
+ const response = await this.response;
+ return await this.receive(response);
+ } catch (error) {
+ if (error.name !== "AbortError") {
+ if (this.#willDelegateErrorHandling(error)) {
+ this.delegate.requestErrored(this, error);
+ }
+ throw error;
+ }
+ } finally {
+ this.delegate.requestFinished(this);
+ }
+ }
+ async receive(response) {
+ const fetchResponse = new FetchResponse(response);
+ const event = dispatch("turbo:before-fetch-response", {
+ cancelable: true,
+ detail: { fetchResponse },
+ target: this.target
+ });
+ if (event.defaultPrevented) {
+ this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
+ } else if (fetchResponse.succeeded) {
+ this.delegate.requestSucceededWithResponse(this, fetchResponse);
+ } else {
+ this.delegate.requestFailedWithResponse(this, fetchResponse);
+ }
+ return fetchResponse;
+ }
+ get defaultHeaders() {
+ return {
+ Accept: "text/html, application/xhtml+xml"
+ };
+ }
+ get isSafe() {
+ return isSafe(this.method);
+ }
+ get abortSignal() {
+ return this.abortController.signal;
+ }
+ acceptResponseType(mimeType) {
+ this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
+ }
+ async #allowRequestToBeIntercepted(fetchOptions) {
+ const requestInterception = new Promise((resolve) => this.#resolveRequestPromise = resolve);
+ const event = dispatch("turbo:before-fetch-request", {
+ cancelable: true,
+ detail: {
+ fetchOptions,
+ url: this.url,
+ resume: this.#resolveRequestPromise
+ },
+ target: this.target
+ });
+ this.url = event.detail.url;
+ if (event.defaultPrevented) await requestInterception;
+ return event;
+ }
+ #willDelegateErrorHandling(error) {
+ const event = dispatch("turbo:fetch-request-error", {
+ target: this.target,
+ cancelable: true,
+ detail: { request: this, error }
+ });
+ return !event.defaultPrevented;
+ }
+};
+function isSafe(fetchMethod) {
+ return fetchMethodFromString(fetchMethod) == FetchMethod.get;
+}
+function buildResourceAndBody(resource, method, requestBody, enctype) {
+ const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
+ if (isSafe(method)) {
+ return [mergeIntoURLSearchParams(resource, searchParams), null];
+ } else if (enctype == FetchEnctype.urlEncoded) {
+ return [resource, searchParams];
+ } else {
+ return [resource, requestBody];
+ }
+}
+function entriesExcludingFiles(requestBody) {
+ const entries = [];
+ for (const [name, value] of requestBody) {
+ if (value instanceof File) continue;
+ else entries.push([name, value]);
+ }
+ return entries;
+}
+function mergeIntoURLSearchParams(url, requestBody) {
+ const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
+ url.search = searchParams.toString();
+ return url;
+}
+var AppearanceObserver = class {
+ started = false;
+ constructor(delegate, element) {
+ this.delegate = delegate;
+ this.element = element;
+ this.intersectionObserver = new IntersectionObserver(this.intersect);
+ }
+ start() {
+ if (!this.started) {
+ this.started = true;
+ this.intersectionObserver.observe(this.element);
+ }
+ }
+ stop() {
+ if (this.started) {
+ this.started = false;
+ this.intersectionObserver.unobserve(this.element);
+ }
+ }
+ intersect = (entries) => {
+ const lastEntry = entries.slice(-1)[0];
+ if (lastEntry?.isIntersecting) {
+ this.delegate.elementAppearedInViewport(this.element);
+ }
+ };
+};
+var StreamMessage = class {
+ static contentType = "text/vnd.turbo-stream.html";
+ static wrap(message) {
+ if (typeof message == "string") {
+ return new this(createDocumentFragment(message));
+ } else {
+ return message;
+ }
+ }
+ constructor(fragment) {
+ this.fragment = importStreamElements(fragment);
+ }
+};
+function importStreamElements(fragment) {
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
+ const streamElement = document.importNode(element, true);
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
+ }
+ element.replaceWith(streamElement);
+ }
+ return fragment;
+}
+var PREFETCH_DELAY = 100;
+var PrefetchCache = class {
+ #prefetchTimeout = null;
+ #prefetched = null;
+ get(url) {
+ if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
+ return this.#prefetched.request;
+ }
+ }
+ setLater(url, request, ttl) {
+ this.clear();
+ this.#prefetchTimeout = setTimeout(() => {
+ request.perform();
+ this.set(url, request, ttl);
+ this.#prefetchTimeout = null;
+ }, PREFETCH_DELAY);
+ }
+ set(url, request, ttl) {
+ this.#prefetched = { url, request, expire: new Date((/* @__PURE__ */ new Date()).getTime() + ttl) };
+ }
+ clear() {
+ if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
+ this.#prefetched = null;
+ }
+};
+var cacheTtl = 10 * 1e3;
+var prefetchCache = new PrefetchCache();
+var FormSubmissionState = {
+ initialized: "initialized",
+ requesting: "requesting",
+ waiting: "waiting",
+ receiving: "receiving",
+ stopping: "stopping",
+ stopped: "stopped"
+};
+var FormSubmission = class _FormSubmission {
+ state = FormSubmissionState.initialized;
+ static confirmMethod(message, _element, _submitter) {
+ return Promise.resolve(confirm(message));
+ }
+ constructor(delegate, formElement, submitter, mustRedirect = false) {
+ const method = getMethod(formElement, submitter);
+ const action = getAction(getFormAction(formElement, submitter), method);
+ const body = buildFormData(formElement, submitter);
+ const enctype = getEnctype(formElement, submitter);
+ this.delegate = delegate;
+ this.formElement = formElement;
+ this.submitter = submitter;
+ this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
+ this.mustRedirect = mustRedirect;
+ }
+ get method() {
+ return this.fetchRequest.method;
+ }
+ set method(value) {
+ this.fetchRequest.method = value;
+ }
+ get action() {
+ return this.fetchRequest.url.toString();
+ }
+ set action(value) {
+ this.fetchRequest.url = expandURL(value);
+ }
+ get body() {
+ return this.fetchRequest.body;
+ }
+ get enctype() {
+ return this.fetchRequest.enctype;
+ }
+ get isSafe() {
+ return this.fetchRequest.isSafe;
+ }
+ get location() {
+ return this.fetchRequest.url;
+ }
+ // The submission process
+ 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;
+ }
+ }
+ // Fetch request delegate
+ prepareRequest(request) {
+ if (!request.isSafe) {
+ const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
+ if (token) {
+ request.headers["X-CSRF-Token"] = token;
+ }
+ }
+ if (this.requestAcceptsTurboStreamResponse(request)) {
+ request.acceptResponseType(StreamMessage.contentType);
+ }
+ }
+ requestStarted(_request) {
+ this.state = FormSubmissionState.waiting;
+ this.submitter?.setAttribute("disabled", "");
+ this.setSubmitsWith();
+ markAsBusy(this.formElement);
+ dispatch("turbo:submit-start", {
+ target: this.formElement,
+ detail: { formSubmission: this }
+ });
+ this.delegate.formSubmissionStarted(this);
+ }
+ requestPreventedHandlingResponse(request, response) {
+ prefetchCache.clear();
+ this.result = { success: response.succeeded, fetchResponse: response };
+ }
+ requestSucceededWithResponse(request, response) {
+ if (response.clientError || response.serverError) {
+ this.delegate.formSubmissionFailedWithResponse(this, response);
+ return;
+ }
+ prefetchCache.clear();
+ if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
+ const error = new Error("Form responses must redirect to another location");
+ this.delegate.formSubmissionErrored(this, error);
+ } else {
+ this.state = FormSubmissionState.receiving;
+ this.result = { success: true, fetchResponse: response };
+ this.delegate.formSubmissionSucceededWithResponse(this, response);
+ }
+ }
+ requestFailedWithResponse(request, response) {
+ this.result = { success: false, fetchResponse: response };
+ this.delegate.formSubmissionFailedWithResponse(this, response);
+ }
+ requestErrored(request, error) {
+ this.result = { success: false, error };
+ this.delegate.formSubmissionErrored(this, error);
+ }
+ requestFinished(_request) {
+ this.state = FormSubmissionState.stopped;
+ this.submitter?.removeAttribute("disabled");
+ this.resetSubmitterText();
+ clearBusyState(this.formElement);
+ dispatch("turbo:submit-end", {
+ target: this.formElement,
+ detail: { formSubmission: this, ...this.result }
+ });
+ this.delegate.formSubmissionFinished(this);
+ }
+ // Private
+ setSubmitsWith() {
+ if (!this.submitter || !this.submitsWith) return;
+ if (this.submitter.matches("button")) {
+ this.originalSubmitText = this.submitter.innerHTML;
+ this.submitter.innerHTML = this.submitsWith;
+ } else if (this.submitter.matches("input")) {
+ const input = this.submitter;
+ this.originalSubmitText = input.value;
+ input.value = this.submitsWith;
+ }
+ }
+ resetSubmitterText() {
+ if (!this.submitter || !this.originalSubmitText) return;
+ if (this.submitter.matches("button")) {
+ this.submitter.innerHTML = this.originalSubmitText;
+ } else if (this.submitter.matches("input")) {
+ const input = this.submitter;
+ input.value = this.originalSubmitText;
+ }
+ }
+ requestMustRedirect(request) {
+ return !request.isSafe && this.mustRedirect;
+ }
+ requestAcceptsTurboStreamResponse(request) {
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
+ }
+ get submitsWith() {
+ return this.submitter?.getAttribute("data-turbo-submits-with");
+ }
+};
+function buildFormData(formElement, submitter) {
+ const formData = new FormData(formElement);
+ const name = submitter?.getAttribute("name");
+ const value = submitter?.getAttribute("value");
+ if (name) {
+ formData.append(name, value || "");
+ }
+ return formData;
+}
+function getCookieValue(cookieName) {
+ if (cookieName != null) {
+ const cookies = document.cookie ? document.cookie.split("; ") : [];
+ const cookie = cookies.find((cookie2) => cookie2.startsWith(cookieName));
+ if (cookie) {
+ const value = cookie.split("=").slice(1).join("=");
+ return value ? decodeURIComponent(value) : void 0;
+ }
+ }
+}
+function responseSucceededWithoutRedirect(response) {
+ return response.statusCode == 200 && !response.redirected;
+}
+function getFormAction(formElement, submitter) {
+ const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
+ if (submitter?.hasAttribute("formaction")) {
+ return submitter.getAttribute("formaction") || "";
+ } else {
+ return formElement.getAttribute("action") || formElementAction || "";
+ }
+}
+function getAction(formAction, fetchMethod) {
+ const action = expandURL(formAction);
+ if (isSafe(fetchMethod)) {
+ action.search = "";
+ }
+ return action;
+}
+function getMethod(formElement, submitter) {
+ const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
+ return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
+}
+function getEnctype(formElement, submitter) {
+ return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
+}
+var Snapshot = class {
+ constructor(element) {
+ this.element = element;
+ }
+ get activeElement() {
+ return this.element.ownerDocument.activeElement;
+ }
+ get children() {
+ return [...this.element.children];
+ }
+ hasAnchor(anchor) {
+ return this.getElementForAnchor(anchor) != null;
+ }
+ getElementForAnchor(anchor) {
+ return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;
+ }
+ get isConnected() {
+ return this.element.isConnected;
+ }
+ get firstAutofocusableElement() {
+ return queryAutofocusableElement(this.element);
+ }
+ get permanentElements() {
+ return queryPermanentElementsAll(this.element);
+ }
+ getPermanentElementById(id) {
+ return getPermanentElementById(this.element, id);
+ }
+ getPermanentElementMapForSnapshot(snapshot) {
+ const permanentElementMap = {};
+ for (const currentPermanentElement of this.permanentElements) {
+ const { id } = 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]");
+}
+var FormSubmitObserver = class {
+ started = false;
+ constructor(delegate, eventTarget) {
+ this.delegate = delegate;
+ this.eventTarget = eventTarget;
+ }
+ start() {
+ if (!this.started) {
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
+ this.started = true;
+ }
+ }
+ stop() {
+ if (this.started) {
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
+ this.started = false;
+ }
+ }
+ submitCaptured = () => {
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
+ };
+ submitBubbled = (event) => {
+ if (!event.defaultPrevented) {
+ const form = event.target instanceof HTMLFormElement ? event.target : void 0;
+ const submitter = event.submitter || void 0;
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ this.delegate.formSubmitted(form, submitter);
+ }
+ }
+ };
+};
+function submissionDoesNotDismissDialog(form, submitter) {
+ const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
+ return method != "dialog";
+}
+function submissionDoesNotTargetIFrame(form, submitter) {
+ const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
+ return doesNotTargetIFrame(target);
+}
+var View = class {
+ #resolveRenderPromise = (_value) => {
+ };
+ #resolveInterceptionPromise = (_value) => {
+ };
+ constructor(delegate, element) {
+ this.delegate = delegate;
+ this.element = element;
+ }
+ // Scrolling
+ scrollToAnchor(anchor) {
+ const element = this.snapshot.getElementForAnchor(anchor);
+ if (element) {
+ this.scrollToElement(element);
+ this.focusElement(element);
+ } else {
+ this.scrollToPosition({ x: 0, y: 0 });
+ }
+ }
+ scrollToAnchorFromLocation(location2) {
+ this.scrollToAnchor(getAnchor(location2));
+ }
+ 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;
+ }
+ // Rendering
+ async render(renderer) {
+ const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer;
+ const shouldInvalidate = willRender;
+ if (shouldRender) {
+ try {
+ this.renderPromise = new Promise((resolve) => this.#resolveRenderPromise = resolve);
+ this.renderer = renderer;
+ await this.prepareToRenderSnapshot(renderer);
+ const renderInterception = new Promise((resolve) => this.#resolveInterceptionPromise = resolve);
+ const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod };
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
+ if (!immediateRender) await renderInterception;
+ await this.renderSnapshot(renderer);
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
+ this.delegate.preloadOnLoadLinksForView(this.element);
+ this.finishRenderingSnapshot(renderer);
+ } finally {
+ delete this.renderer;
+ this.#resolveRenderPromise(void 0);
+ delete this.renderPromise;
+ }
+ } else if (shouldInvalidate) {
+ this.invalidate(renderer.reloadReason);
+ }
+ }
+ invalidate(reason) {
+ this.delegate.viewInvalidated(reason);
+ }
+ async prepareToRenderSnapshot(renderer) {
+ this.markAsPreview(renderer.isPreview);
+ await renderer.prepareToRender();
+ }
+ markAsPreview(isPreview) {
+ if (isPreview) {
+ this.element.setAttribute("data-turbo-preview", "");
+ } else {
+ this.element.removeAttribute("data-turbo-preview");
+ }
+ }
+ markVisitDirection(direction) {
+ this.element.setAttribute("data-turbo-visit-direction", direction);
+ }
+ unmarkVisitDirection() {
+ this.element.removeAttribute("data-turbo-visit-direction");
+ }
+ async renderSnapshot(renderer) {
+ await renderer.render();
+ }
+ finishRenderingSnapshot(renderer) {
+ renderer.finishRendering();
+ }
+};
+var FrameView = class extends View {
+ missing() {
+ this.element.innerHTML = `Content missing`;
+ }
+ get snapshot() {
+ return new Snapshot(this.element);
+ }
+};
+var LinkInterceptor = class {
+ constructor(delegate, element) {
+ this.delegate = delegate;
+ this.element = element;
+ }
+ start() {
+ this.element.addEventListener("click", this.clickBubbled);
+ document.addEventListener("turbo:click", this.linkClicked);
+ document.addEventListener("turbo:before-visit", this.willVisit);
+ }
+ stop() {
+ this.element.removeEventListener("click", this.clickBubbled);
+ document.removeEventListener("turbo:click", this.linkClicked);
+ document.removeEventListener("turbo:before-visit", this.willVisit);
+ }
+ clickBubbled = (event) => {
+ if (this.clickEventIsSignificant(event)) {
+ this.clickEvent = event;
+ } else {
+ delete this.clickEvent;
+ }
+ };
+ linkClicked = (event) => {
+ if (this.clickEvent && this.clickEventIsSignificant(event)) {
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
+ this.clickEvent.preventDefault();
+ event.preventDefault();
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
+ }
+ }
+ delete this.clickEvent;
+ };
+ willVisit = (_event) => {
+ delete this.clickEvent;
+ };
+ clickEventIsSignificant(event) {
+ const target = event.composed ? event.target?.parentElement : event.target;
+ const element = findLinkFromClickTarget(target) || target;
+ return element instanceof Element && element.closest("turbo-frame, html") == this.element;
+ }
+};
+var LinkClickObserver = class {
+ started = false;
+ constructor(delegate, eventTarget) {
+ this.delegate = delegate;
+ this.eventTarget = eventTarget;
+ }
+ start() {
+ if (!this.started) {
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
+ this.started = true;
+ }
+ }
+ stop() {
+ if (this.started) {
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
+ this.started = false;
+ }
+ }
+ clickCaptured = () => {
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
+ };
+ clickBubbled = (event) => {
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
+ const target = event.composedPath && event.composedPath()[0] || event.target;
+ const link = findLinkFromClickTarget(target);
+ if (link && doesNotTargetIFrame(link.target)) {
+ const location2 = getLocationForLink(link);
+ if (this.delegate.willFollowLinkToLocation(link, location2, event)) {
+ event.preventDefault();
+ this.delegate.followedLinkToLocation(link, location2);
+ }
+ }
+ }
+ };
+ clickEventIsSignificant(event) {
+ return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
+ }
+};
+var FormLinkClickObserver = class {
+ constructor(delegate, element) {
+ this.delegate = delegate;
+ this.linkInterceptor = new LinkClickObserver(this, element);
+ }
+ start() {
+ this.linkInterceptor.start();
+ }
+ stop() {
+ this.linkInterceptor.stop();
+ }
+ // Link hover observer delegate
+ canPrefetchRequestToLocation(link, location2) {
+ return false;
+ }
+ prefetchAndCacheRequestToLocation(link, location2) {
+ return;
+ }
+ // Link click observer delegate
+ willFollowLinkToLocation(link, location2, originalEvent) {
+ return this.delegate.willSubmitFormLinkToLocation(link, location2, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
+ }
+ followedLinkToLocation(link, location2) {
+ const form = document.createElement("form");
+ const type = "hidden";
+ for (const [name, value] of location2.searchParams) {
+ form.append(Object.assign(document.createElement("input"), { type, name, value }));
+ }
+ const action = Object.assign(location2, { search: "" });
+ form.setAttribute("data-turbo", "true");
+ form.setAttribute("action", action.href);
+ form.setAttribute("hidden", "");
+ const method = link.getAttribute("data-turbo-method");
+ if (method) form.setAttribute("method", method);
+ const turboFrame = link.getAttribute("data-turbo-frame");
+ if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
+ const turboAction = getVisitAction(link);
+ if (turboAction) form.setAttribute("data-turbo-action", turboAction);
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
+ if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
+ const turboStream = link.hasAttribute("data-turbo-stream");
+ if (turboStream) form.setAttribute("data-turbo-stream", "");
+ this.delegate.submittedFormLinkToLocation(link, location2, form);
+ document.body.appendChild(form);
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
+ requestAnimationFrame(() => form.requestSubmit());
+ }
+};
+var Bardo = class {
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
+ const bardo = new this(delegate, permanentElementMap);
+ bardo.enter();
+ await callback();
+ bardo.leave();
+ }
+ constructor(delegate, permanentElementMap) {
+ this.delegate = delegate;
+ this.permanentElementMap = permanentElementMap;
+ }
+ enter() {
+ for (const id in this.permanentElementMap) {
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
+ this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
+ }
+ }
+ leave() {
+ for (const id in this.permanentElementMap) {
+ const [currentPermanentElement] = this.permanentElementMap[id];
+ this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
+ this.replacePlaceholderWithPermanentElement(currentPermanentElement);
+ this.delegate.leavingBardo(currentPermanentElement);
+ }
+ }
+ replaceNewPermanentElementWithPlaceholder(permanentElement) {
+ const placeholder = createPlaceholderForPermanentElement(permanentElement);
+ permanentElement.replaceWith(placeholder);
+ }
+ replaceCurrentPermanentElementWithClone(permanentElement) {
+ const clone = permanentElement.cloneNode(true);
+ permanentElement.replaceWith(clone);
+ }
+ replacePlaceholderWithPermanentElement(permanentElement) {
+ const placeholder = this.getPlaceholderById(permanentElement.id);
+ placeholder?.replaceWith(permanentElement);
+ }
+ getPlaceholderById(id) {
+ return this.placeholders.find((element) => element.content == id);
+ }
+ get placeholders() {
+ return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
+ }
+};
+function createPlaceholderForPermanentElement(permanentElement) {
+ const element = document.createElement("meta");
+ element.setAttribute("name", "turbo-permanent-placeholder");
+ element.setAttribute("content", permanentElement.id);
+ return element;
+}
+var Renderer = class {
+ #activeElement = null;
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
+ 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 shouldAutofocus() {
+ return true;
+ }
+ get reloadReason() {
+ return;
+ }
+ prepareToRender() {
+ return;
+ }
+ render() {
+ }
+ finishRendering() {
+ if (this.resolvingFunctions) {
+ this.resolvingFunctions.resolve();
+ delete this.resolvingFunctions;
+ }
+ }
+ async preservingPermanentElements(callback) {
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
+ }
+ focusFirstAutofocusableElement() {
+ if (this.shouldAutofocus) {
+ const element = this.connectedSnapshot.firstAutofocusableElement;
+ if (element) {
+ element.focus();
+ }
+ }
+ }
+ // Bardo delegate
+ enteringBardo(currentPermanentElement) {
+ if (this.#activeElement) return;
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
+ this.#activeElement = this.currentSnapshot.activeElement;
+ }
+ }
+ leavingBardo(currentPermanentElement) {
+ if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
+ this.#activeElement.focus();
+ this.#activeElement = null;
+ }
+ }
+ get connectedSnapshot() {
+ return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
+ }
+ get currentElement() {
+ return this.currentSnapshot.element;
+ }
+ get newElement() {
+ return this.newSnapshot.element;
+ }
+ get permanentElementMap() {
+ return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
+ }
+ get renderMethod() {
+ return "replace";
+ }
+};
+var FrameRenderer = class extends Renderer {
+ static renderElement(currentElement, newElement) {
+ const destinationRange = document.createRange();
+ destinationRange.selectNodeContents(currentElement);
+ destinationRange.deleteContents();
+ const frameElement = newElement;
+ const sourceRange = frameElement.ownerDocument?.createRange();
+ if (sourceRange) {
+ sourceRange.selectNodeContents(frameElement);
+ currentElement.appendChild(sourceRange.extractContents());
+ }
+ }
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
+ this.delegate = delegate;
+ }
+ get shouldRender() {
+ return true;
+ }
+ async render() {
+ await nextRepaint();
+ this.preservingPermanentElements(() => {
+ this.loadFrameElement();
+ });
+ this.scrollFrameIntoView();
+ await nextRepaint();
+ this.focusFirstAutofocusableElement();
+ await nextRepaint();
+ this.activateScriptElements();
+ }
+ loadFrameElement() {
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
+ this.renderElement(this.currentElement, this.newElement);
+ }
+ scrollFrameIntoView() {
+ if (this.currentElement.autoscroll || this.newElement.autoscroll) {
+ const element = this.currentElement.firstElementChild;
+ const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
+ if (element) {
+ element.scrollIntoView({ block, behavior });
+ return true;
+ }
+ }
+ return false;
+ }
+ activateScriptElements() {
+ for (const inertScriptElement of this.newScriptElements) {
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
+ inertScriptElement.replaceWith(activatedScriptElement);
+ }
+ }
+ get newScriptElements() {
+ return this.currentElement.querySelectorAll("script");
+ }
+};
+function readScrollLogicalPosition(value, defaultValue) {
+ if (value == "end" || value == "start" || value == "center" || value == "nearest") {
+ return value;
+ } else {
+ return defaultValue;
+ }
+}
+function readScrollBehavior(value, defaultValue) {
+ if (value == "auto" || value == "smooth") {
+ return value;
+ } else {
+ return defaultValue;
+ }
+}
+var ProgressBar = class _ProgressBar {
+ static animationDuration = 300;
+ /*ms*/
+ static get defaultCSS() {
+ return unindent`
+ .turbo-progress-bar {
+ position: fixed;
+ display: block;
+ top: 0;
+ left: 0;
+ height: 3px;
+ background: #0076ff;
+ z-index: 2147483647;
+ transition:
+ width ${_ProgressBar.animationDuration}ms ease-out,
+ opacity ${_ProgressBar.animationDuration / 2}ms ${_ProgressBar.animationDuration / 2}ms ease-in;
+ transform: translate3d(0, 0, 0);
+ }
+ `;
+ }
+ hiding = false;
+ value = 0;
+ visible = false;
+ constructor() {
+ this.stylesheetElement = this.createStylesheetElement();
+ this.progressElement = this.createProgressElement();
+ this.installStylesheetElement();
+ this.setValue(0);
+ }
+ show() {
+ if (!this.visible) {
+ this.visible = true;
+ this.installProgressElement();
+ this.startTrickling();
+ }
+ }
+ hide() {
+ if (this.visible && !this.hiding) {
+ this.hiding = true;
+ this.fadeProgressElement(() => {
+ this.uninstallProgressElement();
+ this.stopTrickling();
+ this.visible = false;
+ this.hiding = false;
+ });
+ }
+ }
+ setValue(value) {
+ this.value = value;
+ this.refresh();
+ }
+ // Private
+ installStylesheetElement() {
+ document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
+ }
+ installProgressElement() {
+ this.progressElement.style.width = "0";
+ this.progressElement.style.opacity = "1";
+ document.documentElement.insertBefore(this.progressElement, document.body);
+ this.refresh();
+ }
+ fadeProgressElement(callback) {
+ this.progressElement.style.opacity = "0";
+ setTimeout(callback, _ProgressBar.animationDuration * 1.5);
+ }
+ uninstallProgressElement() {
+ if (this.progressElement.parentNode) {
+ document.documentElement.removeChild(this.progressElement);
+ }
+ }
+ startTrickling() {
+ if (!this.trickleInterval) {
+ this.trickleInterval = window.setInterval(this.trickle, _ProgressBar.animationDuration);
+ }
+ }
+ stopTrickling() {
+ window.clearInterval(this.trickleInterval);
+ delete this.trickleInterval;
+ }
+ trickle = () => {
+ this.setValue(this.value + Math.random() / 100);
+ };
+ refresh() {
+ requestAnimationFrame(() => {
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
+ });
+ }
+ createStylesheetElement() {
+ const element = document.createElement("style");
+ element.type = "text/css";
+ element.textContent = _ProgressBar.defaultCSS;
+ if (this.cspNonce) {
+ element.nonce = this.cspNonce;
+ }
+ return element;
+ }
+ createProgressElement() {
+ const element = document.createElement("div");
+ element.className = "turbo-progress-bar";
+ return element;
+ }
+ get cspNonce() {
+ return getMetaContent("csp-nonce");
+ }
+};
+var HeadSnapshot = class extends Snapshot {
+ 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 {
+ ...result,
+ [outerHTML]: {
+ ...details,
+ elements: [...details.elements, element]
+ }
+ };
+ }, {});
+ get trackedElementSignature() {
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked).join("");
+ }
+ getScriptElementsNotInSnapshot(snapshot) {
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
+ }
+ getStylesheetElementsNotInSnapshot(snapshot) {
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
+ }
+ getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML)).map((outerHTML) => this.detailsByOuterHTML[outerHTML]).filter(({ type }) => type == 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;
+ }, void 0 | void 0);
+ }
+};
+function elementType(element) {
+ if (elementIsScript(element)) {
+ return "script";
+ } else if (elementIsStylesheet(element)) {
+ return "stylesheet";
+ }
+}
+function elementIsTracked(element) {
+ return element.getAttribute("data-turbo-track") == "reload";
+}
+function elementIsScript(element) {
+ const tagName = element.localName;
+ return tagName == "script";
+}
+function elementIsNoscript(element) {
+ const tagName = element.localName;
+ return tagName == "noscript";
+}
+function elementIsStylesheet(element) {
+ const tagName = element.localName;
+ return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
+}
+function elementIsMetaElementWithName(element, name) {
+ const tagName = element.localName;
+ return tagName == "meta" && element.getAttribute("name") == name;
+}
+function elementWithoutNonce(element) {
+ if (element.hasAttribute("nonce")) {
+ element.setAttribute("nonce", "");
+ }
+ return element;
+}
+var PageSnapshot = class _PageSnapshot extends Snapshot {
+ static fromHTMLString(html = "") {
+ return this.fromDocument(parseHTMLDocument(html));
+ }
+ static fromElement(element) {
+ return this.fromDocument(element.ownerDocument);
+ }
+ static fromDocument({ documentElement, body, head }) {
+ return new this(documentElement, body, new HeadSnapshot(head));
+ }
+ constructor(documentElement, body, headSnapshot) {
+ super(body);
+ this.documentElement = documentElement;
+ this.headSnapshot = headSnapshot;
+ }
+ clone() {
+ const clonedElement = this.element.cloneNode(true);
+ const selectElements = this.element.querySelectorAll("select");
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
+ for (const [index, source] of selectElements.entries()) {
+ const clone = clonedSelectElements[index];
+ for (const option of clone.selectedOptions) option.selected = false;
+ for (const option of source.selectedOptions) clone.options[option.index].selected = true;
+ }
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
+ clonedPasswordInput.value = "";
+ }
+ return new _PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
+ }
+ get lang() {
+ return this.documentElement.getAttribute("lang");
+ }
+ get headElement() {
+ return this.headSnapshot.element;
+ }
+ get rootLocation() {
+ const root = this.getSetting("root") ?? "/";
+ return expandURL(root);
+ }
+ get cacheControlValue() {
+ return this.getSetting("cache-control");
+ }
+ get isPreviewable() {
+ return this.cacheControlValue != "no-preview";
+ }
+ get isCacheable() {
+ return this.cacheControlValue != "no-cache";
+ }
+ get isVisitable() {
+ return this.getSetting("visit-control") != "reload";
+ }
+ get prefersViewTransitions() {
+ return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
+ }
+ get shouldMorphPage() {
+ return this.getSetting("refresh-method") === "morph";
+ }
+ get shouldPreserveScrollPosition() {
+ return this.getSetting("refresh-scroll") === "preserve";
+ }
+ // Private
+ getSetting(name) {
+ return this.headSnapshot.getMetaValue(`turbo-${name}`);
+ }
+};
+var ViewTransitioner = class {
+ #viewTransitionStarted = false;
+ #lastOperation = Promise.resolve();
+ renderChange(useViewTransition, render) {
+ if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
+ this.#viewTransitionStarted = true;
+ this.#lastOperation = this.#lastOperation.then(async () => {
+ await document.startViewTransition(render).finished;
+ });
+ } else {
+ this.#lastOperation = this.#lastOperation.then(render);
+ }
+ return this.#lastOperation;
+ }
+ get viewTransitionsAvailable() {
+ return document.startViewTransition;
+ }
+};
+var defaultOptions = {
+ action: "advance",
+ historyChanged: false,
+ visitCachedSnapshot: () => {
+ },
+ willRender: true,
+ updateHistory: true,
+ shouldCacheSnapshot: true,
+ acceptsStreamResponse: false
+};
+var TimingMetric = {
+ visitStart: "visitStart",
+ requestStart: "requestStart",
+ requestEnd: "requestEnd",
+ visitEnd: "visitEnd"
+};
+var VisitState = {
+ initialized: "initialized",
+ started: "started",
+ canceled: "canceled",
+ failed: "failed",
+ completed: "completed"
+};
+var SystemStatusCode = {
+ networkFailure: 0,
+ timeoutFailure: -1,
+ contentTypeMismatch: -2
+};
+var Direction = {
+ advance: "forward",
+ restore: "back",
+ replace: "none"
+};
+var Visit = class {
+ identifier = uuid();
+ // Required by turbo-ios
+ timingMetrics = {};
+ followedRedirect = false;
+ historyChanged = false;
+ scrolled = false;
+ shouldCacheSnapshot = true;
+ acceptsStreamResponse = false;
+ snapshotCached = false;
+ state = VisitState.initialized;
+ viewTransitioner = new ViewTransitioner();
+ constructor(delegate, location2, restorationIdentifier, options = {}) {
+ this.delegate = delegate;
+ this.location = location2;
+ this.restorationIdentifier = restorationIdentifier || uuid();
+ const {
+ action,
+ historyChanged,
+ referrer,
+ snapshot,
+ snapshotHTML,
+ response,
+ visitCachedSnapshot,
+ willRender,
+ updateHistory,
+ shouldCacheSnapshot,
+ acceptsStreamResponse,
+ direction
+ } = {
+ ...defaultOptions,
+ ...options
+ };
+ this.action = action;
+ this.historyChanged = historyChanged;
+ this.referrer = referrer;
+ this.snapshot = snapshot;
+ this.snapshotHTML = snapshotHTML;
+ this.response = response;
+ this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
+ this.isPageRefresh = this.view.isPageRefresh(this);
+ this.visitCachedSnapshot = visitCachedSnapshot;
+ this.willRender = willRender;
+ this.updateHistory = updateHistory;
+ this.scrolled = !willRender;
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
+ this.acceptsStreamResponse = acceptsStreamResponse;
+ this.direction = direction || Direction[action];
+ }
+ get adapter() {
+ return this.delegate.adapter;
+ }
+ get view() {
+ return this.delegate.view;
+ }
+ get history() {
+ return this.delegate.history;
+ }
+ get restorationData() {
+ return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
+ }
+ get silent() {
+ return this.isSamePage;
+ }
+ start() {
+ if (this.state == VisitState.initialized) {
+ this.recordTimingMetric(TimingMetric.visitStart);
+ this.state = VisitState.started;
+ this.adapter.visitStarted(this);
+ this.delegate.visitStarted(this);
+ }
+ }
+ cancel() {
+ if (this.state == VisitState.started) {
+ if (this.request) {
+ this.request.cancel();
+ }
+ this.cancelRender();
+ this.state = VisitState.canceled;
+ }
+ }
+ complete() {
+ if (this.state == VisitState.started) {
+ this.recordTimingMetric(TimingMetric.visitEnd);
+ this.adapter.visitCompleted(this);
+ this.state = VisitState.completed;
+ this.followRedirect();
+ if (!this.followedRedirect) {
+ this.delegate.visitCompleted(this);
+ }
+ }
+ }
+ fail() {
+ if (this.state == VisitState.started) {
+ this.state = VisitState.failed;
+ this.adapter.visitFailed(this);
+ this.delegate.visitCompleted(this);
+ }
+ }
+ changeHistory() {
+ if (!this.historyChanged && this.updateHistory) {
+ const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
+ const method = getHistoryMethodForAction(actionForHistory);
+ this.history.update(method, this.location, this.restorationIdentifier);
+ this.historyChanged = true;
+ }
+ }
+ issueRequest() {
+ if (this.hasPreloadedResponse()) {
+ this.simulateRequest();
+ } else if (this.shouldIssueRequest() && !this.request) {
+ this.request = new FetchRequest(this, FetchMethod.get, this.location);
+ this.request.perform();
+ }
+ }
+ simulateRequest() {
+ if (this.response) {
+ this.startRequest();
+ this.recordResponse();
+ this.finishRequest();
+ }
+ }
+ startRequest() {
+ this.recordTimingMetric(TimingMetric.requestStart);
+ this.adapter.visitRequestStarted(this);
+ }
+ recordResponse(response = this.response) {
+ this.response = response;
+ if (response) {
+ const { statusCode } = response;
+ if (isSuccessful(statusCode)) {
+ this.adapter.visitRequestCompleted(this);
+ } else {
+ this.adapter.visitRequestFailedWithStatusCode(this, statusCode);
+ }
+ }
+ }
+ finishRequest() {
+ this.recordTimingMetric(TimingMetric.requestEnd);
+ this.adapter.visitRequestFinished(this);
+ }
+ loadResponse() {
+ if (this.response) {
+ const { statusCode, responseHTML } = this.response;
+ this.render(async () => {
+ if (this.shouldCacheSnapshot) this.cacheSnapshot();
+ if (this.view.renderPromise) await this.view.renderPromise;
+ if (isSuccessful(statusCode) && responseHTML != null) {
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
+ await this.renderPageSnapshot(snapshot, false);
+ this.adapter.visitRendered(this);
+ this.complete();
+ } else {
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
+ this.adapter.visitRendered(this);
+ this.fail();
+ }
+ });
+ }
+ }
+ getCachedSnapshot() {
+ const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
+ if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
+ if (this.action == "restore" || snapshot.isPreviewable) {
+ return snapshot;
+ }
+ }
+ }
+ getPreloadedSnapshot() {
+ if (this.snapshotHTML) {
+ return PageSnapshot.fromHTMLString(this.snapshotHTML);
+ }
+ }
+ hasCachedSnapshot() {
+ return this.getCachedSnapshot() != null;
+ }
+ loadCachedSnapshot() {
+ const snapshot = this.getCachedSnapshot();
+ if (snapshot) {
+ const isPreview = this.shouldIssueRequest();
+ this.render(async () => {
+ this.cacheSnapshot();
+ if (this.isSamePage || this.isPageRefresh) {
+ this.adapter.visitRendered(this);
+ } else {
+ if (this.view.renderPromise) await this.view.renderPromise;
+ await this.renderPageSnapshot(snapshot, isPreview);
+ this.adapter.visitRendered(this);
+ if (!isPreview) {
+ this.complete();
+ }
+ }
+ });
+ }
+ }
+ followRedirect() {
+ if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
+ this.adapter.visitProposedToLocation(this.redirectedToLocation, {
+ action: "replace",
+ response: this.response,
+ shouldCacheSnapshot: false,
+ willRender: false
+ });
+ this.followedRedirect = true;
+ }
+ }
+ goToSamePageAnchor() {
+ if (this.isSamePage) {
+ this.render(async () => {
+ this.cacheSnapshot();
+ this.performScroll();
+ this.changeHistory();
+ this.adapter.visitRendered(this);
+ });
+ }
+ }
+ // Fetch request delegate
+ prepareRequest(request) {
+ if (this.acceptsStreamResponse) {
+ request.acceptResponseType(StreamMessage.contentType);
+ }
+ }
+ requestStarted() {
+ this.startRequest();
+ }
+ requestPreventedHandlingResponse(_request, _response) {
+ }
+ async requestSucceededWithResponse(request, response) {
+ const responseHTML = await response.responseHTML;
+ const { redirected, statusCode } = response;
+ if (responseHTML == void 0) {
+ this.recordResponse({
+ statusCode: SystemStatusCode.contentTypeMismatch,
+ redirected
+ });
+ } else {
+ this.redirectedToLocation = response.redirected ? response.location : void 0;
+ this.recordResponse({ statusCode, responseHTML, redirected });
+ }
+ }
+ async requestFailedWithResponse(request, response) {
+ const responseHTML = await response.responseHTML;
+ const { redirected, statusCode } = response;
+ if (responseHTML == void 0) {
+ this.recordResponse({
+ statusCode: SystemStatusCode.contentTypeMismatch,
+ redirected
+ });
+ } else {
+ this.recordResponse({ statusCode, responseHTML, redirected });
+ }
+ }
+ requestErrored(_request, _error) {
+ this.recordResponse({
+ statusCode: SystemStatusCode.networkFailure,
+ redirected: false
+ });
+ }
+ requestFinished() {
+ this.finishRequest();
+ }
+ // Scrolling
+ performScroll() {
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
+ if (this.action == "restore") {
+ this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
+ } else {
+ this.scrollToAnchor() || this.view.scrollToTop();
+ }
+ if (this.isSamePage) {
+ this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
+ }
+ this.scrolled = true;
+ }
+ }
+ scrollToRestoredPosition() {
+ const { scrollPosition } = this.restorationData;
+ if (scrollPosition) {
+ this.view.scrollToPosition(scrollPosition);
+ return true;
+ }
+ }
+ scrollToAnchor() {
+ const anchor = getAnchor(this.location);
+ if (anchor != null) {
+ this.view.scrollToAnchor(anchor);
+ return true;
+ }
+ }
+ // Instrumentation
+ recordTimingMetric(metric) {
+ this.timingMetrics[metric] = (/* @__PURE__ */ new Date()).getTime();
+ }
+ getTimingMetrics() {
+ return { ...this.timingMetrics };
+ }
+ // Private
+ getHistoryMethodForAction(action) {
+ switch (action) {
+ case "replace":
+ return history.replaceState;
+ case "advance":
+ case "restore":
+ return history.pushState;
+ }
+ }
+ hasPreloadedResponse() {
+ return typeof this.response == "object";
+ }
+ shouldIssueRequest() {
+ if (this.isSamePage) {
+ return false;
+ } else if (this.action == "restore") {
+ return !this.hasCachedSnapshot();
+ } else {
+ return this.willRender;
+ }
+ }
+ cacheSnapshot() {
+ if (!this.snapshotCached) {
+ this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
+ this.snapshotCached = true;
+ }
+ }
+ async render(callback) {
+ this.cancelRender();
+ this.frame = await nextRepaint();
+ await callback();
+ delete this.frame;
+ }
+ async renderPageSnapshot(snapshot, isPreview) {
+ await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), async () => {
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
+ this.performScroll();
+ });
+ }
+ cancelRender() {
+ if (this.frame) {
+ cancelAnimationFrame(this.frame);
+ delete this.frame;
+ }
+ }
+};
+function isSuccessful(statusCode) {
+ return statusCode >= 200 && statusCode < 300;
+}
+var BrowserAdapter = class {
+ progressBar = new ProgressBar();
+ constructor(session2) {
+ this.session = session2;
+ }
+ visitProposedToLocation(location2, options) {
+ if (locationIsVisitable(location2, this.navigator.rootLocation)) {
+ this.navigator.startVisit(location2, options?.restorationIdentifier || uuid(), options);
+ } else {
+ window.location.href = location2.toString();
+ }
+ }
+ visitStarted(visit2) {
+ this.location = visit2.location;
+ visit2.loadCachedSnapshot();
+ visit2.issueRequest();
+ visit2.goToSamePageAnchor();
+ }
+ visitRequestStarted(visit2) {
+ this.progressBar.setValue(0);
+ if (visit2.hasCachedSnapshot() || visit2.action != "restore") {
+ this.showVisitProgressBarAfterDelay();
+ } else {
+ this.showProgressBar();
+ }
+ }
+ visitRequestCompleted(visit2) {
+ visit2.loadResponse();
+ }
+ visitRequestFailedWithStatusCode(visit2, statusCode) {
+ switch (statusCode) {
+ case SystemStatusCode.networkFailure:
+ case SystemStatusCode.timeoutFailure:
+ case SystemStatusCode.contentTypeMismatch:
+ return this.reload({
+ reason: "request_failed",
+ context: {
+ statusCode
+ }
+ });
+ default:
+ return visit2.loadResponse();
+ }
+ }
+ visitRequestFinished(_visit) {
+ }
+ visitCompleted(_visit) {
+ this.progressBar.setValue(1);
+ this.hideVisitProgressBar();
+ }
+ pageInvalidated(reason) {
+ this.reload(reason);
+ }
+ visitFailed(_visit) {
+ this.progressBar.setValue(1);
+ this.hideVisitProgressBar();
+ }
+ visitRendered(_visit) {
+ }
+ // Form Submission Delegate
+ formSubmissionStarted(_formSubmission) {
+ this.progressBar.setValue(0);
+ this.showFormProgressBarAfterDelay();
+ }
+ formSubmissionFinished(_formSubmission) {
+ this.progressBar.setValue(1);
+ this.hideFormProgressBar();
+ }
+ // Private
+ showVisitProgressBarAfterDelay() {
+ this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
+ }
+ hideVisitProgressBar() {
+ this.progressBar.hide();
+ if (this.visitProgressBarTimeout != null) {
+ window.clearTimeout(this.visitProgressBarTimeout);
+ delete this.visitProgressBarTimeout;
+ }
+ }
+ showFormProgressBarAfterDelay() {
+ if (this.formProgressBarTimeout == null) {
+ this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
+ }
+ }
+ hideFormProgressBar() {
+ this.progressBar.hide();
+ if (this.formProgressBarTimeout != null) {
+ window.clearTimeout(this.formProgressBarTimeout);
+ delete this.formProgressBarTimeout;
+ }
+ }
+ showProgressBar = () => {
+ this.progressBar.show();
+ };
+ reload(reason) {
+ dispatch("turbo:reload", { detail: reason });
+ window.location.href = this.location?.toString() || window.location.href;
+ }
+ get navigator() {
+ return this.session.navigator;
+ }
+};
+var CacheObserver = class {
+ selector = "[data-turbo-temporary]";
+ deprecatedSelector = "[data-turbo-cache=false]";
+ started = false;
+ start() {
+ if (!this.started) {
+ this.started = true;
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
+ }
+ }
+ stop() {
+ if (this.started) {
+ this.started = false;
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
+ }
+ }
+ removeTemporaryElements = (_event) => {
+ for (const element of this.temporaryElements) {
+ element.remove();
+ }
+ };
+ get temporaryElements() {
+ return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];
+ }
+ get temporaryElementsWithDeprecation() {
+ const elements = document.querySelectorAll(this.deprecatedSelector);
+ if (elements.length) {
+ console.warn(
+ `The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`
+ );
+ }
+ return [...elements];
+ }
+};
+var FrameRedirector = class {
+ constructor(session2, element) {
+ this.session = session2;
+ this.element = element;
+ this.linkInterceptor = new LinkInterceptor(this, element);
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
+ }
+ start() {
+ this.linkInterceptor.start();
+ this.formSubmitObserver.start();
+ }
+ stop() {
+ this.linkInterceptor.stop();
+ this.formSubmitObserver.stop();
+ }
+ // Link interceptor delegate
+ shouldInterceptLinkClick(element, _location, _event) {
+ return this.#shouldRedirect(element);
+ }
+ linkClickIntercepted(element, url, event) {
+ const frame = this.#findFrameElement(element);
+ if (frame) {
+ frame.delegate.linkClickIntercepted(element, url, event);
+ }
+ }
+ // Form submit observer delegate
+ willSubmitForm(element, submitter) {
+ return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
+ }
+ formSubmitted(element, submitter) {
+ const frame = this.#findFrameElement(element, submitter);
+ if (frame) {
+ frame.delegate.formSubmitted(element, submitter);
+ }
+ }
+ #shouldSubmit(form, submitter) {
+ const action = getAction$1(form, submitter);
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
+ const rootLocation = expandURL(meta?.content ?? "/");
+ return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
+ }
+ #shouldRedirect(element, submitter) {
+ const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
+ if (isNavigatable) {
+ const frame = this.#findFrameElement(element, submitter);
+ return frame ? frame != element.closest("turbo-frame") : false;
+ } else {
+ return false;
+ }
+ }
+ #findFrameElement(element, submitter) {
+ const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
+ if (id && id != "_top") {
+ const frame = this.element.querySelector(`#${id}:not([disabled])`);
+ if (frame instanceof FrameElement) {
+ return frame;
+ }
+ }
+ }
+};
+var History = class {
+ location;
+ restorationIdentifier = uuid();
+ restorationData = {};
+ started = false;
+ pageLoaded = false;
+ currentIndex = 0;
+ constructor(delegate) {
+ this.delegate = delegate;
+ }
+ start() {
+ if (!this.started) {
+ addEventListener("popstate", this.onPopState, false);
+ addEventListener("load", this.onPageLoad, false);
+ this.currentIndex = history.state?.turbo?.restorationIndex || 0;
+ this.started = true;
+ this.replace(new URL(window.location.href));
+ }
+ }
+ stop() {
+ if (this.started) {
+ removeEventListener("popstate", this.onPopState, false);
+ removeEventListener("load", this.onPageLoad, false);
+ this.started = false;
+ }
+ }
+ push(location2, restorationIdentifier) {
+ this.update(history.pushState, location2, restorationIdentifier);
+ }
+ replace(location2, restorationIdentifier) {
+ this.update(history.replaceState, location2, restorationIdentifier);
+ }
+ update(method, location2, restorationIdentifier = uuid()) {
+ if (method === history.pushState) ++this.currentIndex;
+ const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } };
+ method.call(history, state, "", location2.href);
+ this.location = location2;
+ this.restorationIdentifier = restorationIdentifier;
+ }
+ // Restoration data
+ getRestorationDataForIdentifier(restorationIdentifier) {
+ return this.restorationData[restorationIdentifier] || {};
+ }
+ updateRestorationData(additionalData) {
+ const { restorationIdentifier } = this;
+ const restorationData = this.restorationData[restorationIdentifier];
+ this.restorationData[restorationIdentifier] = {
+ ...restorationData,
+ ...additionalData
+ };
+ }
+ // Scroll restoration
+ assumeControlOfScrollRestoration() {
+ if (!this.previousScrollRestoration) {
+ this.previousScrollRestoration = history.scrollRestoration ?? "auto";
+ history.scrollRestoration = "manual";
+ }
+ }
+ relinquishControlOfScrollRestoration() {
+ if (this.previousScrollRestoration) {
+ history.scrollRestoration = this.previousScrollRestoration;
+ delete this.previousScrollRestoration;
+ }
+ }
+ // Event handlers
+ onPopState = (event) => {
+ if (this.shouldHandlePopState()) {
+ const { turbo } = event.state || {};
+ if (turbo) {
+ this.location = new URL(window.location.href);
+ const { restorationIdentifier, restorationIndex } = turbo;
+ this.restorationIdentifier = restorationIdentifier;
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
+ this.currentIndex = restorationIndex;
+ }
+ }
+ };
+ onPageLoad = async (_event) => {
+ await nextMicrotask();
+ this.pageLoaded = true;
+ };
+ // Private
+ shouldHandlePopState() {
+ return this.pageIsLoaded();
+ }
+ pageIsLoaded() {
+ return this.pageLoaded || document.readyState == "complete";
+ }
+};
+var LinkPrefetchObserver = class {
+ started = false;
+ #prefetchedLink = null;
+ constructor(delegate, eventTarget) {
+ this.delegate = delegate;
+ this.eventTarget = eventTarget;
+ }
+ start() {
+ if (this.started) return;
+ if (this.eventTarget.readyState === "loading") {
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, { once: true });
+ } else {
+ this.#enable();
+ }
+ }
+ stop() {
+ if (!this.started) return;
+ this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
+ capture: true,
+ passive: true
+ });
+ this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
+ capture: true,
+ passive: true
+ });
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
+ this.started = false;
+ }
+ #enable = () => {
+ this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
+ capture: true,
+ passive: true
+ });
+ this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
+ capture: true,
+ passive: true
+ });
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
+ this.started = true;
+ };
+ #tryToPrefetchRequest = (event) => {
+ if (getMetaContent("turbo-prefetch") === "false") return;
+ const target = event.target;
+ const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
+ if (isLink && this.#isPrefetchable(target)) {
+ const link = target;
+ const location2 = getLocationForLink(link);
+ if (this.delegate.canPrefetchRequestToLocation(link, location2)) {
+ this.#prefetchedLink = link;
+ const fetchRequest = new FetchRequest(
+ this,
+ FetchMethod.get,
+ location2,
+ new URLSearchParams(),
+ target
+ );
+ prefetchCache.setLater(location2.toString(), fetchRequest, this.#cacheTtl);
+ }
+ }
+ };
+ #cancelRequestIfObsolete = (event) => {
+ if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
+ };
+ #cancelPrefetchRequest = () => {
+ prefetchCache.clear();
+ this.#prefetchedLink = null;
+ };
+ #tryToUsePrefetchedRequest = (event) => {
+ if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
+ const cached = prefetchCache.get(event.detail.url.toString());
+ if (cached) {
+ event.detail.fetchRequest = cached;
+ }
+ prefetchCache.clear();
+ }
+ };
+ prepareRequest(request) {
+ const link = request.target;
+ request.headers["X-Sec-Purpose"] = "prefetch";
+ const turboFrame = link.closest("turbo-frame");
+ const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
+ if (turboFrameTarget && turboFrameTarget !== "_top") {
+ request.headers["Turbo-Frame"] = turboFrameTarget;
+ }
+ }
+ // Fetch request interface
+ requestSucceededWithResponse() {
+ }
+ requestStarted(fetchRequest) {
+ }
+ requestErrored(fetchRequest) {
+ }
+ requestFinished(fetchRequest) {
+ }
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {
+ }
+ requestFailedWithResponse(fetchRequest, fetchResponse) {
+ }
+ get #cacheTtl() {
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
+ }
+ #isPrefetchable(link) {
+ const href = link.getAttribute("href");
+ if (!href) return false;
+ if (unfetchableLink(link)) return false;
+ if (linkToTheSamePage(link)) return false;
+ if (linkOptsOut(link)) return false;
+ if (nonSafeLink(link)) return false;
+ if (eventPrevented(link)) return false;
+ return true;
+ }
+};
+var unfetchableLink = (link) => {
+ return link.origin !== document.location.origin || !["http:", "https:"].includes(link.protocol) || link.hasAttribute("target");
+};
+var linkToTheSamePage = (link) => {
+ return link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
+};
+var linkOptsOut = (link) => {
+ if (link.getAttribute("data-turbo-prefetch") === "false") return true;
+ if (link.getAttribute("data-turbo") === "false") return true;
+ const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
+ if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
+ return false;
+};
+var nonSafeLink = (link) => {
+ const turboMethod = link.getAttribute("data-turbo-method");
+ if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
+ if (isUJS(link)) return true;
+ if (link.hasAttribute("data-turbo-confirm")) return true;
+ if (link.hasAttribute("data-turbo-stream")) return true;
+ return false;
+};
+var isUJS = (link) => {
+ return link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
+};
+var eventPrevented = (link) => {
+ const event = dispatch("turbo:before-prefetch", { target: link, cancelable: true });
+ return event.defaultPrevented;
+};
+var Navigator = class {
+ constructor(delegate) {
+ this.delegate = delegate;
+ }
+ proposeVisit(location2, options = {}) {
+ if (this.delegate.allowsVisitingLocationWithAction(location2, options.action)) {
+ this.delegate.visitProposedToLocation(location2, options);
+ }
+ }
+ startVisit(locatable, restorationIdentifier, options = {}) {
+ this.stop();
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
+ referrer: this.location,
+ ...options
+ });
+ this.currentVisit.start();
+ }
+ submitForm(form, submitter) {
+ this.stop();
+ this.formSubmission = new FormSubmission(this, form, submitter, true);
+ this.formSubmission.start();
+ }
+ stop() {
+ if (this.formSubmission) {
+ this.formSubmission.stop();
+ delete this.formSubmission;
+ }
+ if (this.currentVisit) {
+ this.currentVisit.cancel();
+ delete this.currentVisit;
+ }
+ }
+ get adapter() {
+ return this.delegate.adapter;
+ }
+ get view() {
+ return this.delegate.view;
+ }
+ get rootLocation() {
+ return this.view.snapshot.rootLocation;
+ }
+ get history() {
+ return this.delegate.history;
+ }
+ // Form submission delegate
+ formSubmissionStarted(formSubmission) {
+ if (typeof this.adapter.formSubmissionStarted === "function") {
+ this.adapter.formSubmissionStarted(formSubmission);
+ }
+ }
+ async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
+ if (formSubmission == this.formSubmission) {
+ const responseHTML = await fetchResponse.responseHTML;
+ if (responseHTML) {
+ const shouldCacheSnapshot = formSubmission.isSafe;
+ if (!shouldCacheSnapshot) {
+ this.view.clearSnapshotCache();
+ }
+ const { statusCode, redirected } = fetchResponse;
+ const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
+ const visitOptions = {
+ action,
+ shouldCacheSnapshot,
+ response: { statusCode, responseHTML, redirected }
+ };
+ this.proposeVisit(fetchResponse.location, visitOptions);
+ }
+ }
+ }
+ async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
+ const responseHTML = await fetchResponse.responseHTML;
+ if (responseHTML) {
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
+ if (fetchResponse.serverError) {
+ await this.view.renderError(snapshot, this.currentVisit);
+ } else {
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
+ }
+ if (!snapshot.shouldPreserveScrollPosition) {
+ this.view.scrollToTop();
+ }
+ this.view.clearSnapshotCache();
+ }
+ }
+ formSubmissionErrored(formSubmission, error) {
+ console.error(error);
+ }
+ formSubmissionFinished(formSubmission) {
+ if (typeof this.adapter.formSubmissionFinished === "function") {
+ this.adapter.formSubmissionFinished(formSubmission);
+ }
+ }
+ // Visit delegate
+ visitStarted(visit2) {
+ this.delegate.visitStarted(visit2);
+ }
+ visitCompleted(visit2) {
+ this.delegate.visitCompleted(visit2);
+ delete this.currentVisit;
+ }
+ locationWithActionIsSamePage(location2, action) {
+ const anchor = getAnchor(location2);
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
+ return action !== "replace" && getRequestURL(location2) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
+ }
+ visitScrolledToSamePageLocation(oldURL, newURL) {
+ this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
+ }
+ // Visits
+ get location() {
+ return this.history.location;
+ }
+ get restorationIdentifier() {
+ return this.history.restorationIdentifier;
+ }
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
+ const { submitter, formElement } = formSubmission;
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
+ }
+ #getDefaultAction(fetchResponse) {
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
+ return sameLocationRedirect ? "replace" : "advance";
+ }
+};
+var PageStage = {
+ initial: 0,
+ loading: 1,
+ interactive: 2,
+ complete: 3
+};
+var PageObserver = class {
+ stage = PageStage.initial;
+ started = false;
+ constructor(delegate) {
+ this.delegate = delegate;
+ }
+ start() {
+ if (!this.started) {
+ if (this.stage == PageStage.initial) {
+ this.stage = PageStage.loading;
+ }
+ document.addEventListener("readystatechange", this.interpretReadyState, false);
+ addEventListener("pagehide", this.pageWillUnload, false);
+ this.started = true;
+ }
+ }
+ stop() {
+ if (this.started) {
+ document.removeEventListener("readystatechange", this.interpretReadyState, false);
+ removeEventListener("pagehide", this.pageWillUnload, false);
+ this.started = false;
+ }
+ }
+ interpretReadyState = () => {
+ const { readyState } = this;
+ if (readyState == "interactive") {
+ this.pageIsInteractive();
+ } else if (readyState == "complete") {
+ this.pageIsComplete();
+ }
+ };
+ pageIsInteractive() {
+ if (this.stage == PageStage.loading) {
+ this.stage = PageStage.interactive;
+ this.delegate.pageBecameInteractive();
+ }
+ }
+ pageIsComplete() {
+ this.pageIsInteractive();
+ if (this.stage == PageStage.interactive) {
+ this.stage = PageStage.complete;
+ this.delegate.pageLoaded();
+ }
+ }
+ pageWillUnload = () => {
+ this.delegate.pageWillUnload();
+ };
+ get readyState() {
+ return document.readyState;
+ }
+};
+var ScrollObserver = class {
+ started = false;
+ constructor(delegate) {
+ this.delegate = delegate;
+ }
+ start() {
+ if (!this.started) {
+ addEventListener("scroll", this.onScroll, false);
+ this.onScroll();
+ this.started = true;
+ }
+ }
+ stop() {
+ if (this.started) {
+ removeEventListener("scroll", this.onScroll, false);
+ this.started = false;
+ }
+ }
+ onScroll = () => {
+ this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });
+ };
+ // Private
+ updatePosition(position) {
+ this.delegate.scrollPositionChanged(position);
+ }
+};
+var StreamMessageRenderer = class {
+ render({ fragment }) {
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => {
+ withAutofocusFromFragment(fragment, () => {
+ withPreservedFocus(() => {
+ document.documentElement.appendChild(fragment);
+ });
+ });
+ });
+ }
+ // Bardo delegate
+ enteringBardo(currentPermanentElement, newPermanentElement) {
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
+ }
+ leavingBardo() {
+ }
+};
+function getPermanentElementMapForFragment(fragment) {
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
+ const permanentElementMap = {};
+ for (const permanentElementInDocument of permanentElementsInDocument) {
+ const { id } = permanentElementInDocument;
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
+ if (elementInStream) {
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
+ }
+ }
+ }
+ return permanentElementMap;
+}
+async function withAutofocusFromFragment(fragment, callback) {
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
+ let willAutofocusId = null;
+ if (elementWithAutofocus) {
+ if (elementWithAutofocus.id) {
+ willAutofocusId = elementWithAutofocus.id;
+ } else {
+ willAutofocusId = generatedID;
+ }
+ elementWithAutofocus.id = willAutofocusId;
+ }
+ callback();
+ await nextRepaint();
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
+ if (hasNoActiveElement && willAutofocusId) {
+ const elementToAutofocus = document.getElementById(willAutofocusId);
+ if (elementIsFocusable(elementToAutofocus)) {
+ elementToAutofocus.focus();
+ }
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
+ elementToAutofocus.removeAttribute("id");
+ }
+ }
+}
+async function withPreservedFocus(callback) {
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, () => document.activeElement);
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
+ if (restoreFocusTo) {
+ const elementToFocus = document.getElementById(restoreFocusTo);
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
+ elementToFocus.focus();
+ }
+ }
+}
+function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
+ for (const streamElement of nodeListOfStreamElements) {
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
+ if (elementWithAutofocus) return elementWithAutofocus;
+ }
+ return null;
+}
+var StreamObserver = class {
+ sources = /* @__PURE__ */ new Set();
+ #started = false;
+ constructor(delegate) {
+ this.delegate = delegate;
+ }
+ start() {
+ if (!this.#started) {
+ this.#started = true;
+ addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
+ }
+ }
+ stop() {
+ if (this.#started) {
+ this.#started = false;
+ removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
+ }
+ }
+ connectStreamSource(source) {
+ if (!this.streamSourceIsConnected(source)) {
+ this.sources.add(source);
+ source.addEventListener("message", this.receiveMessageEvent, false);
+ }
+ }
+ disconnectStreamSource(source) {
+ if (this.streamSourceIsConnected(source)) {
+ this.sources.delete(source);
+ source.removeEventListener("message", this.receiveMessageEvent, false);
+ }
+ }
+ streamSourceIsConnected(source) {
+ return this.sources.has(source);
+ }
+ inspectFetchResponse = (event) => {
+ const response = fetchResponseFromEvent(event);
+ if (response && fetchResponseIsStream(response)) {
+ event.preventDefault();
+ this.receiveMessageResponse(response);
+ }
+ };
+ receiveMessageEvent = (event) => {
+ if (this.#started && typeof event.data == "string") {
+ this.receiveMessageHTML(event.data);
+ }
+ };
+ async receiveMessageResponse(response) {
+ const html = await response.responseHTML;
+ if (html) {
+ this.receiveMessageHTML(html);
+ }
+ }
+ receiveMessageHTML(html) {
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
+ }
+};
+function fetchResponseFromEvent(event) {
+ const fetchResponse = event.detail?.fetchResponse;
+ if (fetchResponse instanceof FetchResponse) {
+ return fetchResponse;
+ }
+}
+function fetchResponseIsStream(response) {
+ const contentType = response.contentType ?? "";
+ return contentType.startsWith(StreamMessage.contentType);
+}
+var ErrorRenderer = class extends Renderer {
+ static renderElement(currentElement, newElement) {
+ const { documentElement, body } = document;
+ documentElement.replaceChild(newElement, body);
+ }
+ async render() {
+ this.replaceHeadAndBody();
+ this.activateScriptElements();
+ }
+ replaceHeadAndBody() {
+ const { documentElement, head } = document;
+ documentElement.replaceChild(this.newHead, head);
+ this.renderElement(this.currentElement, this.newElement);
+ }
+ activateScriptElements() {
+ for (const replaceableElement of this.scriptElements) {
+ const parentNode = replaceableElement.parentNode;
+ if (parentNode) {
+ const element = activateScriptElement(replaceableElement);
+ parentNode.replaceChild(element, replaceableElement);
+ }
+ }
+ }
+ get newHead() {
+ return this.newSnapshot.headSnapshot.element;
+ }
+ get scriptElements() {
+ return document.documentElement.querySelectorAll("script");
+ }
+};
+var Idiomorph = /* @__PURE__ */ function() {
+ let EMPTY_SET = /* @__PURE__ */ new Set();
+ let defaults = {
+ morphStyle: "outerHTML",
+ callbacks: {
+ beforeNodeAdded: noOp,
+ afterNodeAdded: noOp,
+ beforeNodeMorphed: noOp,
+ afterNodeMorphed: noOp,
+ beforeNodeRemoved: noOp,
+ afterNodeRemoved: noOp,
+ beforeAttributeUpdated: noOp
+ },
+ head: {
+ style: "merge",
+ shouldPreserve: function(elt) {
+ return elt.getAttribute("im-preserve") === "true";
+ },
+ shouldReAppend: function(elt) {
+ return elt.getAttribute("im-re-append") === "true";
+ },
+ shouldRemove: noOp,
+ afterHeadMorphed: noOp
+ }
+ };
+ function morph(oldNode, newContent, config = {}) {
+ if (oldNode instanceof Document) {
+ oldNode = oldNode.documentElement;
+ }
+ if (typeof newContent === "string") {
+ newContent = parseContent(newContent);
+ }
+ let normalizedContent = normalizeContent(newContent);
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
+ }
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
+ if (ctx.head.block) {
+ let oldHead = oldNode.querySelector("head");
+ let newHead = normalizedNewContent.querySelector("head");
+ if (oldHead && newHead) {
+ let promises = handleHeadElement(newHead, oldHead, ctx);
+ Promise.all(promises).then(function() {
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
+ head: {
+ block: false,
+ ignore: true
+ }
+ }));
+ });
+ return;
+ }
+ }
+ if (ctx.morphStyle === "innerHTML") {
+ morphChildren2(normalizedNewContent, oldNode, ctx);
+ return oldNode.children;
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
+ let previousSibling = bestMatch?.previousSibling;
+ let nextSibling = bestMatch?.nextSibling;
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
+ if (bestMatch) {
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
+ } else {
+ return [];
+ }
+ } else {
+ throw "Do not understand how to morph style " + ctx.morphStyle;
+ }
+ }
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
+ return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
+ }
+ function morphOldNodeTo(oldNode, newContent, ctx) {
+ if (ctx.ignoreActive && oldNode === document.activeElement) ;
+ else if (newContent == null) {
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
+ oldNode.remove();
+ ctx.callbacks.afterNodeRemoved(oldNode);
+ return null;
+ } else if (!isSoftMatch(oldNode, newContent)) {
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
+ oldNode.parentElement.replaceChild(newContent, oldNode);
+ ctx.callbacks.afterNodeAdded(newContent);
+ ctx.callbacks.afterNodeRemoved(oldNode);
+ return newContent;
+ } else {
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ;
+ else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
+ handleHeadElement(newContent, oldNode, ctx);
+ } else {
+ syncNodeFrom(newContent, oldNode, ctx);
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
+ morphChildren2(newContent, oldNode, ctx);
+ }
+ }
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
+ return oldNode;
+ }
+ }
+ function morphChildren2(newParent, oldParent, ctx) {
+ let nextNewChild = newParent.firstChild;
+ let insertionPoint = oldParent.firstChild;
+ let newChild;
+ while (nextNewChild) {
+ newChild = nextNewChild;
+ nextNewChild = newChild.nextSibling;
+ if (insertionPoint == null) {
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
+ oldParent.appendChild(newChild);
+ ctx.callbacks.afterNodeAdded(newChild);
+ removeIdsFromConsideration(ctx, newChild);
+ continue;
+ }
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
+ morphOldNodeTo(insertionPoint, newChild, ctx);
+ insertionPoint = insertionPoint.nextSibling;
+ removeIdsFromConsideration(ctx, newChild);
+ continue;
+ }
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
+ if (idSetMatch) {
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
+ morphOldNodeTo(idSetMatch, newChild, ctx);
+ removeIdsFromConsideration(ctx, newChild);
+ continue;
+ }
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
+ if (softMatch) {
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
+ morphOldNodeTo(softMatch, newChild, ctx);
+ removeIdsFromConsideration(ctx, newChild);
+ continue;
+ }
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
+ oldParent.insertBefore(newChild, insertionPoint);
+ ctx.callbacks.afterNodeAdded(newChild);
+ removeIdsFromConsideration(ctx, newChild);
+ }
+ while (insertionPoint !== null) {
+ let tempNode = insertionPoint;
+ insertionPoint = insertionPoint.nextSibling;
+ removeNode(tempNode, ctx);
+ }
+ }
+ function ignoreAttribute(attr, to, updateType, ctx) {
+ if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
+ return true;
+ }
+ return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
+ }
+ function syncNodeFrom(from, to, ctx) {
+ let type = from.nodeType;
+ if (type === 1) {
+ const fromAttributes = from.attributes;
+ const toAttributes = to.attributes;
+ for (const fromAttribute of fromAttributes) {
+ if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
+ continue;
+ }
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
+ }
+ }
+ for (let i = toAttributes.length - 1; 0 <= i; i--) {
+ const toAttribute = toAttributes[i];
+ if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
+ continue;
+ }
+ if (!from.hasAttribute(toAttribute.name)) {
+ to.removeAttribute(toAttribute.name);
+ }
+ }
+ }
+ if (type === 8 || type === 3) {
+ if (to.nodeValue !== from.nodeValue) {
+ to.nodeValue = from.nodeValue;
+ }
+ }
+ if (!ignoreValueOfActiveElement(to, ctx)) {
+ syncInputValue(from, to, ctx);
+ }
+ }
+ function syncBooleanAttribute(from, to, attributeName, ctx) {
+ if (from[attributeName] !== to[attributeName]) {
+ let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
+ if (!ignoreUpdate) {
+ to[attributeName] = from[attributeName];
+ }
+ if (from[attributeName]) {
+ if (!ignoreUpdate) {
+ to.setAttribute(attributeName, from[attributeName]);
+ }
+ } else {
+ if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
+ to.removeAttribute(attributeName);
+ }
+ }
+ }
+ }
+ function syncInputValue(from, to, ctx) {
+ if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
+ let fromValue = from.value;
+ let toValue = to.value;
+ syncBooleanAttribute(from, to, "checked", ctx);
+ syncBooleanAttribute(from, to, "disabled", ctx);
+ if (!from.hasAttribute("value")) {
+ if (!ignoreAttribute("value", to, "remove", ctx)) {
+ to.value = "";
+ to.removeAttribute("value");
+ }
+ } else if (fromValue !== toValue) {
+ if (!ignoreAttribute("value", to, "update", ctx)) {
+ to.setAttribute("value", fromValue);
+ to.value = fromValue;
+ }
+ }
+ } else if (from instanceof HTMLOptionElement) {
+ syncBooleanAttribute(from, to, "selected", ctx);
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
+ let fromValue = from.value;
+ let toValue = to.value;
+ if (ignoreAttribute("value", to, "update", ctx)) {
+ return;
+ }
+ if (fromValue !== toValue) {
+ to.value = fromValue;
+ }
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
+ to.firstChild.nodeValue = fromValue;
+ }
+ }
+ }
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
+ let added = [];
+ let removed = [];
+ let preserved = [];
+ let nodesToAppend = [];
+ let headMergeStyle = ctx.head.style;
+ let srcToNewHeadNodes = /* @__PURE__ */ new Map();
+ for (const newHeadChild of newHeadTag.children) {
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
+ }
+ for (const currentHeadElt of currentHead.children) {
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
+ if (inNewContent || isPreserved) {
+ if (isReAppended) {
+ removed.push(currentHeadElt);
+ } else {
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
+ preserved.push(currentHeadElt);
+ }
+ } else {
+ if (headMergeStyle === "append") {
+ if (isReAppended) {
+ removed.push(currentHeadElt);
+ nodesToAppend.push(currentHeadElt);
+ }
+ } else {
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
+ removed.push(currentHeadElt);
+ }
+ }
+ }
+ }
+ nodesToAppend.push(...srcToNewHeadNodes.values());
+ let promises = [];
+ for (const newNode of nodesToAppend) {
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
+ if (newElt.href || newElt.src) {
+ let resolve = null;
+ let promise = new Promise(function(_resolve) {
+ resolve = _resolve;
+ });
+ newElt.addEventListener("load", function() {
+ resolve();
+ });
+ promises.push(promise);
+ }
+ currentHead.appendChild(newElt);
+ ctx.callbacks.afterNodeAdded(newElt);
+ added.push(newElt);
+ }
+ }
+ for (const removedElement of removed) {
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
+ currentHead.removeChild(removedElement);
+ ctx.callbacks.afterNodeRemoved(removedElement);
+ }
+ }
+ ctx.head.afterHeadMorphed(currentHead, { added, kept: preserved, removed });
+ return promises;
+ }
+ function noOp() {
+ }
+ function mergeDefaults(config) {
+ let finalConfig = {};
+ Object.assign(finalConfig, defaults);
+ Object.assign(finalConfig, config);
+ finalConfig.callbacks = {};
+ Object.assign(finalConfig.callbacks, defaults.callbacks);
+ Object.assign(finalConfig.callbacks, config.callbacks);
+ finalConfig.head = {};
+ Object.assign(finalConfig.head, defaults.head);
+ Object.assign(finalConfig.head, config.head);
+ return finalConfig;
+ }
+ function createMorphContext(oldNode, newContent, config) {
+ config = mergeDefaults(config);
+ return {
+ target: oldNode,
+ newContent,
+ config,
+ morphStyle: config.morphStyle,
+ ignoreActive: config.ignoreActive,
+ ignoreActiveValue: config.ignoreActiveValue,
+ idMap: createIdMap(oldNode, newContent),
+ deadIds: /* @__PURE__ */ new Set(),
+ callbacks: config.callbacks,
+ head: config.head
+ };
+ }
+ function isIdSetMatch(node1, node2, ctx) {
+ if (node1 == null || node2 == null) {
+ return false;
+ }
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
+ if (node1.id !== "" && node1.id === node2.id) {
+ return true;
+ } else {
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
+ }
+ }
+ return false;
+ }
+ function isSoftMatch(node1, node2) {
+ if (node1 == null || node2 == null) {
+ return false;
+ }
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
+ }
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
+ while (startInclusive !== endExclusive) {
+ let tempNode = startInclusive;
+ startInclusive = startInclusive.nextSibling;
+ removeNode(tempNode, ctx);
+ }
+ removeIdsFromConsideration(ctx, endExclusive);
+ return endExclusive.nextSibling;
+ }
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
+ let potentialMatch = null;
+ if (newChildPotentialIdCount > 0) {
+ let potentialMatch2 = insertionPoint;
+ let otherMatchCount = 0;
+ while (potentialMatch2 != null) {
+ if (isIdSetMatch(newChild, potentialMatch2, ctx)) {
+ return potentialMatch2;
+ }
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch2, newContent);
+ if (otherMatchCount > newChildPotentialIdCount) {
+ return null;
+ }
+ potentialMatch2 = potentialMatch2.nextSibling;
+ }
+ }
+ return potentialMatch;
+ }
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
+ let potentialSoftMatch = insertionPoint;
+ let nextSibling = newChild.nextSibling;
+ let siblingSoftMatchCount = 0;
+ while (potentialSoftMatch != null) {
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
+ return null;
+ }
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
+ return potentialSoftMatch;
+ }
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
+ siblingSoftMatchCount++;
+ nextSibling = nextSibling.nextSibling;
+ if (siblingSoftMatchCount >= 2) {
+ return null;
+ }
+ }
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
+ }
+ return potentialSoftMatch;
+ }
+ function parseContent(newContent) {
+ let parser = new DOMParser();
+ let contentWithSvgsRemoved = newContent.replace(/