turbo-rails 1.5.0 → 2.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +126 -16
  3. data/app/assets/javascripts/turbo.js +2226 -953
  4. data/app/assets/javascripts/turbo.min.js +9 -5
  5. data/app/assets/javascripts/turbo.min.js.map +1 -1
  6. data/app/channels/turbo/streams/broadcasts.rb +47 -10
  7. data/app/channels/turbo/streams_channel.rb +15 -15
  8. data/app/controllers/concerns/turbo/request_id_tracking.rb +12 -0
  9. data/app/controllers/turbo/frames/frame_request.rb +2 -2
  10. data/app/controllers/turbo/native/navigation.rb +17 -11
  11. data/app/helpers/turbo/drive_helper.rb +72 -14
  12. data/app/helpers/turbo/frames_helper.rb +8 -8
  13. data/app/helpers/turbo/streams/action_helper.rb +12 -4
  14. data/app/helpers/turbo/streams_helper.rb +5 -0
  15. data/app/javascript/turbo/cable_stream_source_element.js +10 -0
  16. data/app/javascript/turbo/index.js +2 -0
  17. data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -2
  18. data/app/jobs/turbo/streams/broadcast_job.rb +1 -1
  19. data/app/jobs/turbo/streams/broadcast_stream_job.rb +7 -0
  20. data/app/models/concerns/turbo/broadcastable.rb +201 -42
  21. data/app/models/turbo/debouncer.rb +24 -0
  22. data/app/models/turbo/streams/tag_builder.rb +50 -12
  23. data/app/models/turbo/thread_debouncer.rb +28 -0
  24. data/config/routes.rb +3 -4
  25. data/lib/install/turbo_with_importmap.rb +1 -1
  26. data/lib/tasks/turbo_tasks.rake +0 -22
  27. data/lib/turbo/broadcastable/test_helper.rb +5 -5
  28. data/lib/turbo/engine.rb +80 -9
  29. data/lib/turbo/system_test_helper.rb +128 -0
  30. data/lib/turbo/test_assertions/integration_test_assertions.rb +2 -2
  31. data/lib/turbo/test_assertions.rb +2 -2
  32. data/lib/turbo/version.rb +1 -1
  33. data/lib/turbo-rails.rb +10 -0
  34. metadata +10 -19
  35. data/lib/install/turbo_needs_redis.rb +0 -20
@@ -1,19 +1,7 @@
1
- (function() {
2
- if (window.Reflect === undefined || window.customElements === undefined || window.customElements.polyfillWrapFlushCallback) {
3
- return;
4
- }
5
- const BuiltInHTMLElement = HTMLElement;
6
- const wrapperForTheName = {
7
- HTMLElement: function HTMLElement() {
8
- return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
9
- }
10
- };
11
- window.HTMLElement = wrapperForTheName["HTMLElement"];
12
- HTMLElement.prototype = BuiltInHTMLElement.prototype;
13
- HTMLElement.prototype.constructor = HTMLElement;
14
- Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
15
- })();
16
-
1
+ /*!
2
+ Turbo 8.0.12
3
+ Copyright © 2024 37signals LLC
4
+ */
17
5
  (function(prototype) {
18
6
  if (typeof prototype.requestSubmit == "function") return;
19
7
  prototype.requestSubmit = function(submitter) {
@@ -44,7 +32,7 @@ const submittersByForm = new WeakMap;
44
32
  function findSubmitterFromClickTarget(target) {
45
33
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
46
34
  const candidate = element ? element.closest("input, button") : null;
47
- return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null;
35
+ return candidate?.type == "submit" ? candidate : null;
48
36
  }
49
37
 
50
38
  function clickCaptured(event) {
@@ -57,10 +45,13 @@ function clickCaptured(event) {
57
45
  (function() {
58
46
  if ("submitter" in Event.prototype) return;
59
47
  let prototype = window.Event.prototype;
60
- if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
61
- prototype = window.SubmitEvent.prototype;
62
- } else if ("SubmitEvent" in window) {
63
- return;
48
+ if ("SubmitEvent" in window) {
49
+ const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
50
+ if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
51
+ prototype = prototypeOfSubmitEvent;
52
+ } else {
53
+ return;
54
+ }
64
55
  }
65
56
  addEventListener("click", clickCaptured, true);
66
57
  Object.defineProperty(prototype, "submitter", {
@@ -72,20 +63,19 @@ function clickCaptured(event) {
72
63
  });
73
64
  })();
74
65
 
75
- var FrameLoadingStyle;
76
-
77
- (function(FrameLoadingStyle) {
78
- FrameLoadingStyle["eager"] = "eager";
79
- FrameLoadingStyle["lazy"] = "lazy";
80
- })(FrameLoadingStyle || (FrameLoadingStyle = {}));
66
+ const FrameLoadingStyle = {
67
+ eager: "eager",
68
+ lazy: "lazy"
69
+ };
81
70
 
82
71
  class FrameElement extends HTMLElement {
72
+ static delegateConstructor=undefined;
73
+ loaded=Promise.resolve();
83
74
  static get observedAttributes() {
84
- return [ "disabled", "complete", "loading", "src" ];
75
+ return [ "disabled", "loading", "src" ];
85
76
  }
86
77
  constructor() {
87
78
  super();
88
- this.loaded = Promise.resolve();
89
79
  this.delegate = new FrameElement.delegateConstructor(this);
90
80
  }
91
81
  connectedCallback() {
@@ -100,11 +90,9 @@ class FrameElement extends HTMLElement {
100
90
  attributeChangedCallback(name) {
101
91
  if (name == "loading") {
102
92
  this.delegate.loadingStyleChanged();
103
- } else if (name == "complete") {
104
- this.delegate.completeChanged();
105
93
  } else if (name == "src") {
106
94
  this.delegate.sourceURLChanged();
107
- } else {
95
+ } else if (name == "disabled") {
108
96
  this.delegate.disabledChanged();
109
97
  }
110
98
  }
@@ -118,6 +106,19 @@ class FrameElement extends HTMLElement {
118
106
  this.removeAttribute("src");
119
107
  }
120
108
  }
109
+ get refresh() {
110
+ return this.getAttribute("refresh");
111
+ }
112
+ set refresh(value) {
113
+ if (value) {
114
+ this.setAttribute("refresh", value);
115
+ } else {
116
+ this.removeAttribute("refresh");
117
+ }
118
+ }
119
+ get shouldReloadWithMorph() {
120
+ return this.src && this.refresh === "morph";
121
+ }
121
122
  get loading() {
122
123
  return frameLoadingStyleFromString(this.getAttribute("loading") || "");
123
124
  }
@@ -155,8 +156,7 @@ class FrameElement extends HTMLElement {
155
156
  return this.ownerDocument === document && !this.isPreview;
156
157
  }
157
158
  get isPreview() {
158
- var _a, _b;
159
- return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
159
+ return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
160
160
  }
161
161
  }
162
162
 
@@ -170,122 +170,18 @@ function frameLoadingStyleFromString(style) {
170
170
  }
171
171
  }
172
172
 
173
- function expandURL(locatable) {
174
- return new URL(locatable.toString(), document.baseURI);
175
- }
176
-
177
- function getAnchor(url) {
178
- let anchorMatch;
179
- if (url.hash) {
180
- return url.hash.slice(1);
181
- } else if (anchorMatch = url.href.match(/#(.*)$/)) {
182
- return anchorMatch[1];
183
- }
184
- }
185
-
186
- function getAction(form, submitter) {
187
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
188
- return expandURL(action);
189
- }
190
-
191
- function getExtension(url) {
192
- return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
193
- }
194
-
195
- function isHTML(url) {
196
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
197
- }
198
-
199
- function isPrefixedBy(baseURL, url) {
200
- const prefix = getPrefix(url);
201
- return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
202
- }
203
-
204
- function locationIsVisitable(location, rootLocation) {
205
- return isPrefixedBy(location, rootLocation) && isHTML(location);
206
- }
207
-
208
- function getRequestURL(url) {
209
- const anchor = getAnchor(url);
210
- return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
211
- }
212
-
213
- function toCacheKey(url) {
214
- return getRequestURL(url);
215
- }
216
-
217
- function urlsAreEqual(left, right) {
218
- return expandURL(left).href == expandURL(right).href;
219
- }
220
-
221
- function getPathComponents(url) {
222
- return url.pathname.split("/").slice(1);
223
- }
224
-
225
- function getLastPathComponent(url) {
226
- return getPathComponents(url).slice(-1)[0];
227
- }
228
-
229
- function getPrefix(url) {
230
- return addTrailingSlash(url.origin + url.pathname);
231
- }
232
-
233
- function addTrailingSlash(value) {
234
- return value.endsWith("/") ? value : value + "/";
235
- }
236
-
237
- class FetchResponse {
238
- constructor(response) {
239
- this.response = response;
240
- }
241
- get succeeded() {
242
- return this.response.ok;
243
- }
244
- get failed() {
245
- return !this.succeeded;
246
- }
247
- get clientError() {
248
- return this.statusCode >= 400 && this.statusCode <= 499;
249
- }
250
- get serverError() {
251
- return this.statusCode >= 500 && this.statusCode <= 599;
252
- }
253
- get redirected() {
254
- return this.response.redirected;
255
- }
256
- get location() {
257
- return expandURL(this.response.url);
258
- }
259
- get isHTML() {
260
- return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
261
- }
262
- get statusCode() {
263
- return this.response.status;
264
- }
265
- get contentType() {
266
- return this.header("Content-Type");
267
- }
268
- get responseText() {
269
- return this.response.clone().text();
270
- }
271
- get responseHTML() {
272
- if (this.isHTML) {
273
- return this.response.clone().text();
274
- } else {
275
- return Promise.resolve(undefined);
276
- }
277
- }
278
- header(name) {
279
- return this.response.headers.get(name);
280
- }
281
- }
173
+ const drive = {
174
+ enabled: true,
175
+ progressBarDelay: 500,
176
+ unvisitableExtensions: new Set([ ".7z", ".aac", ".apk", ".avi", ".bmp", ".bz2", ".css", ".csv", ".deb", ".dmg", ".doc", ".docx", ".exe", ".gif", ".gz", ".heic", ".heif", ".ico", ".iso", ".jpeg", ".jpg", ".js", ".json", ".m4a", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi", ".ogg", ".ogv", ".pdf", ".pkg", ".png", ".ppt", ".pptx", ".rar", ".rtf", ".svg", ".tar", ".tif", ".tiff", ".txt", ".wav", ".webm", ".webp", ".wma", ".wmv", ".xls", ".xlsx", ".xml", ".zip" ])
177
+ };
282
178
 
283
179
  function activateScriptElement(element) {
284
180
  if (element.getAttribute("data-turbo-eval") == "false") {
285
181
  return element;
286
182
  } else {
287
183
  const createdScriptElement = document.createElement("script");
288
- const cspNonce = getMetaContent("csp-nonce");
184
+ const cspNonce = getCspNonce();
289
185
  if (cspNonce) {
290
186
  createdScriptElement.nonce = cspNonce;
291
187
  }
@@ -323,6 +219,19 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
323
219
  return event;
324
220
  }
325
221
 
222
+ function cancelEvent(event) {
223
+ event.preventDefault();
224
+ event.stopImmediatePropagation();
225
+ }
226
+
227
+ function nextRepaint() {
228
+ if (document.visibilityState === "hidden") {
229
+ return nextEventLoopTick();
230
+ } else {
231
+ return nextAnimationFrame();
232
+ }
233
+ }
234
+
326
235
  function nextAnimationFrame() {
327
236
  return new Promise((resolve => requestAnimationFrame((() => resolve()))));
328
237
  }
@@ -370,7 +279,7 @@ function uuid() {
370
279
  }
371
280
 
372
281
  function getAttribute(attributeName, ...elements) {
373
- for (const value of elements.map((element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName)))) {
282
+ for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
374
283
  if (typeof value == "string") return value;
375
284
  }
376
285
  return null;
@@ -444,6 +353,14 @@ function getMetaContent(name) {
444
353
  return element && element.content;
445
354
  }
446
355
 
356
+ function getCspNonce() {
357
+ const element = getMetaElement("csp-nonce");
358
+ if (element) {
359
+ const {nonce: nonce, content: content} = element;
360
+ return nonce == "" ? content : nonce;
361
+ }
362
+ }
363
+
447
364
  function setMetaContent(name, content) {
448
365
  let element = getMetaElement(name);
449
366
  if (!element) {
@@ -456,21 +373,233 @@ function setMetaContent(name, content) {
456
373
  }
457
374
 
458
375
  function findClosestRecursively(element, selector) {
459
- var _a;
460
376
  if (element instanceof Element) {
461
- return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
377
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
378
+ }
379
+ }
380
+
381
+ function elementIsFocusable(element) {
382
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
383
+ return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
384
+ }
385
+
386
+ function queryAutofocusableElement(elementOrDocumentFragment) {
387
+ return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
388
+ }
389
+
390
+ async function around(callback, reader) {
391
+ const before = reader();
392
+ callback();
393
+ await nextAnimationFrame();
394
+ const after = reader();
395
+ return [ before, after ];
396
+ }
397
+
398
+ function doesNotTargetIFrame(name) {
399
+ if (name === "_blank") {
400
+ return false;
401
+ } else if (name) {
402
+ for (const element of document.getElementsByName(name)) {
403
+ if (element instanceof HTMLIFrameElement) return false;
404
+ }
405
+ return true;
406
+ } else {
407
+ return true;
408
+ }
409
+ }
410
+
411
+ function findLinkFromClickTarget(target) {
412
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
413
+ }
414
+
415
+ function getLocationForLink(link) {
416
+ return expandURL(link.getAttribute("href") || "");
417
+ }
418
+
419
+ function debounce(fn, delay) {
420
+ let timeoutId = null;
421
+ return (...args) => {
422
+ const callback = () => fn.apply(this, args);
423
+ clearTimeout(timeoutId);
424
+ timeoutId = setTimeout(callback, delay);
425
+ };
426
+ }
427
+
428
+ const submitter = {
429
+ "aria-disabled": {
430
+ beforeSubmit: submitter => {
431
+ submitter.setAttribute("aria-disabled", "true");
432
+ submitter.addEventListener("click", cancelEvent);
433
+ },
434
+ afterSubmit: submitter => {
435
+ submitter.removeAttribute("aria-disabled");
436
+ submitter.removeEventListener("click", cancelEvent);
437
+ }
438
+ },
439
+ disabled: {
440
+ beforeSubmit: submitter => submitter.disabled = true,
441
+ afterSubmit: submitter => submitter.disabled = false
442
+ }
443
+ };
444
+
445
+ class Config {
446
+ #submitter=null;
447
+ constructor(config) {
448
+ Object.assign(this, config);
449
+ }
450
+ get submitter() {
451
+ return this.#submitter;
452
+ }
453
+ set submitter(value) {
454
+ this.#submitter = submitter[value] || value;
462
455
  }
463
456
  }
464
457
 
465
- var FetchMethod;
458
+ const forms = new Config({
459
+ mode: "on",
460
+ submitter: "disabled"
461
+ });
466
462
 
467
- (function(FetchMethod) {
468
- FetchMethod[FetchMethod["get"] = 0] = "get";
469
- FetchMethod[FetchMethod["post"] = 1] = "post";
470
- FetchMethod[FetchMethod["put"] = 2] = "put";
471
- FetchMethod[FetchMethod["patch"] = 3] = "patch";
472
- FetchMethod[FetchMethod["delete"] = 4] = "delete";
473
- })(FetchMethod || (FetchMethod = {}));
463
+ const config = {
464
+ drive: drive,
465
+ forms: forms
466
+ };
467
+
468
+ function expandURL(locatable) {
469
+ return new URL(locatable.toString(), document.baseURI);
470
+ }
471
+
472
+ function getAnchor(url) {
473
+ let anchorMatch;
474
+ if (url.hash) {
475
+ return url.hash.slice(1);
476
+ } else if (anchorMatch = url.href.match(/#(.*)$/)) {
477
+ return anchorMatch[1];
478
+ }
479
+ }
480
+
481
+ function getAction$1(form, submitter) {
482
+ const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
483
+ return expandURL(action);
484
+ }
485
+
486
+ function getExtension(url) {
487
+ return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
488
+ }
489
+
490
+ function isPrefixedBy(baseURL, url) {
491
+ const prefix = getPrefix(url);
492
+ return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
493
+ }
494
+
495
+ function locationIsVisitable(location, rootLocation) {
496
+ return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location));
497
+ }
498
+
499
+ function getRequestURL(url) {
500
+ const anchor = getAnchor(url);
501
+ return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
502
+ }
503
+
504
+ function toCacheKey(url) {
505
+ return getRequestURL(url);
506
+ }
507
+
508
+ function urlsAreEqual(left, right) {
509
+ return expandURL(left).href == expandURL(right).href;
510
+ }
511
+
512
+ function getPathComponents(url) {
513
+ return url.pathname.split("/").slice(1);
514
+ }
515
+
516
+ function getLastPathComponent(url) {
517
+ return getPathComponents(url).slice(-1)[0];
518
+ }
519
+
520
+ function getPrefix(url) {
521
+ return addTrailingSlash(url.origin + url.pathname);
522
+ }
523
+
524
+ function addTrailingSlash(value) {
525
+ return value.endsWith("/") ? value : value + "/";
526
+ }
527
+
528
+ class FetchResponse {
529
+ constructor(response) {
530
+ this.response = response;
531
+ }
532
+ get succeeded() {
533
+ return this.response.ok;
534
+ }
535
+ get failed() {
536
+ return !this.succeeded;
537
+ }
538
+ get clientError() {
539
+ return this.statusCode >= 400 && this.statusCode <= 499;
540
+ }
541
+ get serverError() {
542
+ return this.statusCode >= 500 && this.statusCode <= 599;
543
+ }
544
+ get redirected() {
545
+ return this.response.redirected;
546
+ }
547
+ get location() {
548
+ return expandURL(this.response.url);
549
+ }
550
+ get isHTML() {
551
+ return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
552
+ }
553
+ get statusCode() {
554
+ return this.response.status;
555
+ }
556
+ get contentType() {
557
+ return this.header("Content-Type");
558
+ }
559
+ get responseText() {
560
+ return this.response.clone().text();
561
+ }
562
+ get responseHTML() {
563
+ if (this.isHTML) {
564
+ return this.response.clone().text();
565
+ } else {
566
+ return Promise.resolve(undefined);
567
+ }
568
+ }
569
+ header(name) {
570
+ return this.response.headers.get(name);
571
+ }
572
+ }
573
+
574
+ class LimitedSet extends Set {
575
+ constructor(maxSize) {
576
+ super();
577
+ this.maxSize = maxSize;
578
+ }
579
+ add(value) {
580
+ if (this.size >= this.maxSize) {
581
+ const iterator = this.values();
582
+ const oldestValue = iterator.next().value;
583
+ this.delete(oldestValue);
584
+ }
585
+ super.add(value);
586
+ }
587
+ }
588
+
589
+ const recentRequests = new LimitedSet(20);
590
+
591
+ const nativeFetch = window.fetch;
592
+
593
+ function fetchWithTurboHeaders(url, options = {}) {
594
+ const modifiedHeaders = new Headers(options.headers || {});
595
+ const requestUID = uuid();
596
+ recentRequests.add(requestUID);
597
+ modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
598
+ return nativeFetch(url, {
599
+ ...options,
600
+ headers: modifiedHeaders
601
+ });
602
+ }
474
603
 
475
604
  function fetchMethodFromString(method) {
476
605
  switch (method.toLowerCase()) {
@@ -491,16 +620,81 @@ function fetchMethodFromString(method) {
491
620
  }
492
621
  }
493
622
 
623
+ const FetchMethod = {
624
+ get: "get",
625
+ post: "post",
626
+ put: "put",
627
+ patch: "patch",
628
+ delete: "delete"
629
+ };
630
+
631
+ function fetchEnctypeFromString(encoding) {
632
+ switch (encoding.toLowerCase()) {
633
+ case FetchEnctype.multipart:
634
+ return FetchEnctype.multipart;
635
+
636
+ case FetchEnctype.plain:
637
+ return FetchEnctype.plain;
638
+
639
+ default:
640
+ return FetchEnctype.urlEncoded;
641
+ }
642
+ }
643
+
644
+ const FetchEnctype = {
645
+ urlEncoded: "application/x-www-form-urlencoded",
646
+ multipart: "multipart/form-data",
647
+ plain: "text/plain"
648
+ };
649
+
494
650
  class FetchRequest {
495
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
496
- this.abortController = new AbortController;
497
- this.resolveRequestPromise = _value => {};
651
+ abortController=new AbortController;
652
+ #resolveRequestPromise=_value => {};
653
+ constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
654
+ const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
498
655
  this.delegate = delegate;
499
- this.method = method;
500
- this.headers = this.defaultHeaders;
501
- this.body = body;
502
- this.url = location;
656
+ this.url = url;
503
657
  this.target = target;
658
+ this.fetchOptions = {
659
+ credentials: "same-origin",
660
+ redirect: "follow",
661
+ method: method.toUpperCase(),
662
+ headers: {
663
+ ...this.defaultHeaders
664
+ },
665
+ body: body,
666
+ signal: this.abortSignal,
667
+ referrer: this.delegate.referrer?.href
668
+ };
669
+ this.enctype = enctype;
670
+ }
671
+ get method() {
672
+ return this.fetchOptions.method;
673
+ }
674
+ set method(value) {
675
+ const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
676
+ const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
677
+ this.url.search = "";
678
+ const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
679
+ this.url = url;
680
+ this.fetchOptions.body = body;
681
+ this.fetchOptions.method = fetchMethod.toUpperCase();
682
+ }
683
+ get headers() {
684
+ return this.fetchOptions.headers;
685
+ }
686
+ set headers(value) {
687
+ this.fetchOptions.headers = value;
688
+ }
689
+ get body() {
690
+ if (this.isSafe) {
691
+ return this.url.searchParams;
692
+ } else {
693
+ return this.fetchOptions.body;
694
+ }
695
+ }
696
+ set body(value) {
697
+ this.fetchOptions.body = value;
504
698
  }
505
699
  get location() {
506
700
  return this.url;
@@ -517,14 +711,19 @@ class FetchRequest {
517
711
  async perform() {
518
712
  const {fetchOptions: fetchOptions} = this;
519
713
  this.delegate.prepareRequest(this);
520
- await this.allowRequestToBeIntercepted(fetchOptions);
714
+ const event = await this.#allowRequestToBeIntercepted(fetchOptions);
521
715
  try {
522
716
  this.delegate.requestStarted(this);
523
- const response = await fetch(this.url.href, fetchOptions);
717
+ if (event.detail.fetchRequest) {
718
+ this.response = event.detail.fetchRequest.response;
719
+ } else {
720
+ this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
721
+ }
722
+ const response = await this.response;
524
723
  return await this.receive(response);
525
724
  } catch (error) {
526
725
  if (error.name !== "AbortError") {
527
- if (this.willDelegateErrorHandling(error)) {
726
+ if (this.#willDelegateErrorHandling(error)) {
528
727
  this.delegate.requestErrored(this, error);
529
728
  }
530
729
  throw error;
@@ -551,25 +750,13 @@ class FetchRequest {
551
750
  }
552
751
  return fetchResponse;
553
752
  }
554
- get fetchOptions() {
555
- var _a;
556
- return {
557
- method: FetchMethod[this.method].toUpperCase(),
558
- credentials: "same-origin",
559
- headers: this.headers,
560
- redirect: "follow",
561
- body: this.isSafe ? null : this.body,
562
- signal: this.abortSignal,
563
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
564
- };
565
- }
566
753
  get defaultHeaders() {
567
754
  return {
568
755
  Accept: "text/html, application/xhtml+xml"
569
756
  };
570
757
  }
571
758
  get isSafe() {
572
- return this.method === FetchMethod.get;
759
+ return isSafe(this.method);
573
760
  }
574
761
  get abortSignal() {
575
762
  return this.abortController.signal;
@@ -577,20 +764,22 @@ class FetchRequest {
577
764
  acceptResponseType(mimeType) {
578
765
  this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
579
766
  }
580
- async allowRequestToBeIntercepted(fetchOptions) {
581
- const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
767
+ async #allowRequestToBeIntercepted(fetchOptions) {
768
+ const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
582
769
  const event = dispatch("turbo:before-fetch-request", {
583
770
  cancelable: true,
584
771
  detail: {
585
772
  fetchOptions: fetchOptions,
586
773
  url: this.url,
587
- resume: this.resolveRequestPromise
774
+ resume: this.#resolveRequestPromise
588
775
  },
589
776
  target: this.target
590
777
  });
778
+ this.url = event.detail.url;
591
779
  if (event.defaultPrevented) await requestInterception;
780
+ return event;
592
781
  }
593
- willDelegateErrorHandling(error) {
782
+ #willDelegateErrorHandling(error) {
594
783
  const event = dispatch("turbo:fetch-request-error", {
595
784
  target: this.target,
596
785
  cancelable: true,
@@ -603,15 +792,38 @@ class FetchRequest {
603
792
  }
604
793
  }
605
794
 
795
+ function isSafe(fetchMethod) {
796
+ return fetchMethodFromString(fetchMethod) == FetchMethod.get;
797
+ }
798
+
799
+ function buildResourceAndBody(resource, method, requestBody, enctype) {
800
+ const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
801
+ if (isSafe(method)) {
802
+ return [ mergeIntoURLSearchParams(resource, searchParams), null ];
803
+ } else if (enctype == FetchEnctype.urlEncoded) {
804
+ return [ resource, searchParams ];
805
+ } else {
806
+ return [ resource, requestBody ];
807
+ }
808
+ }
809
+
810
+ function entriesExcludingFiles(requestBody) {
811
+ const entries = [];
812
+ for (const [name, value] of requestBody) {
813
+ if (value instanceof File) continue; else entries.push([ name, value ]);
814
+ }
815
+ return entries;
816
+ }
817
+
818
+ function mergeIntoURLSearchParams(url, requestBody) {
819
+ const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
820
+ url.search = searchParams.toString();
821
+ return url;
822
+ }
823
+
606
824
  class AppearanceObserver {
825
+ started=false;
607
826
  constructor(delegate, element) {
608
- this.started = false;
609
- this.intersect = entries => {
610
- const lastEntry = entries.slice(-1)[0];
611
- if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
612
- this.delegate.elementAppearedInViewport(this.element);
613
- }
614
- };
615
827
  this.delegate = delegate;
616
828
  this.element = element;
617
829
  this.intersectionObserver = new IntersectionObserver(this.intersect);
@@ -628,9 +840,16 @@ class AppearanceObserver {
628
840
  this.intersectionObserver.unobserve(this.element);
629
841
  }
630
842
  }
843
+ intersect=entries => {
844
+ const lastEntry = entries.slice(-1)[0];
845
+ if (lastEntry?.isIntersecting) {
846
+ this.delegate.elementAppearedInViewport(this.element);
847
+ }
848
+ };
631
849
  }
632
850
 
633
851
  class StreamMessage {
852
+ static contentType="text/vnd.turbo-stream.html";
634
853
  static wrap(message) {
635
854
  if (typeof message == "string") {
636
855
  return new this(createDocumentFragment(message));
@@ -643,8 +862,6 @@ class StreamMessage {
643
862
  }
644
863
  }
645
864
 
646
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
647
-
648
865
  function importStreamElements(fragment) {
649
866
  for (const element of fragment.querySelectorAll("turbo-stream")) {
650
867
  const streamElement = document.importNode(element, true);
@@ -656,91 +873,96 @@ function importStreamElements(fragment) {
656
873
  return fragment;
657
874
  }
658
875
 
659
- var FormSubmissionState;
660
-
661
- (function(FormSubmissionState) {
662
- FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
663
- FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
664
- FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
665
- FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
666
- FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
667
- FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
668
- })(FormSubmissionState || (FormSubmissionState = {}));
669
-
670
- var FormEnctype;
876
+ const PREFETCH_DELAY = 100;
671
877
 
672
- (function(FormEnctype) {
673
- FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
674
- FormEnctype["multipart"] = "multipart/form-data";
675
- FormEnctype["plain"] = "text/plain";
676
- })(FormEnctype || (FormEnctype = {}));
677
-
678
- function formEnctypeFromString(encoding) {
679
- switch (encoding.toLowerCase()) {
680
- case FormEnctype.multipart:
681
- return FormEnctype.multipart;
682
-
683
- case FormEnctype.plain:
684
- return FormEnctype.plain;
685
-
686
- default:
687
- return FormEnctype.urlEncoded;
878
+ class PrefetchCache {
879
+ #prefetchTimeout=null;
880
+ #prefetched=null;
881
+ get(url) {
882
+ if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
883
+ return this.#prefetched.request;
884
+ }
885
+ }
886
+ setLater(url, request, ttl) {
887
+ this.clear();
888
+ this.#prefetchTimeout = setTimeout((() => {
889
+ request.perform();
890
+ this.set(url, request, ttl);
891
+ this.#prefetchTimeout = null;
892
+ }), PREFETCH_DELAY);
893
+ }
894
+ set(url, request, ttl) {
895
+ this.#prefetched = {
896
+ url: url,
897
+ request: request,
898
+ expire: new Date((new Date).getTime() + ttl)
899
+ };
900
+ }
901
+ clear() {
902
+ if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
903
+ this.#prefetched = null;
688
904
  }
689
905
  }
690
906
 
907
+ const cacheTtl = 10 * 1e3;
908
+
909
+ const prefetchCache = new PrefetchCache;
910
+
911
+ const FormSubmissionState = {
912
+ initialized: "initialized",
913
+ requesting: "requesting",
914
+ waiting: "waiting",
915
+ receiving: "receiving",
916
+ stopping: "stopping",
917
+ stopped: "stopped"
918
+ };
919
+
691
920
  class FormSubmission {
692
- static confirmMethod(message, _element, _submitter) {
921
+ state=FormSubmissionState.initialized;
922
+ static confirmMethod(message) {
693
923
  return Promise.resolve(confirm(message));
694
924
  }
695
925
  constructor(delegate, formElement, submitter, mustRedirect = false) {
696
- this.state = FormSubmissionState.initialized;
926
+ const method = getMethod(formElement, submitter);
927
+ const action = getAction(getFormAction(formElement, submitter), method);
928
+ const body = buildFormData(formElement, submitter);
929
+ const enctype = getEnctype(formElement, submitter);
697
930
  this.delegate = delegate;
698
931
  this.formElement = formElement;
699
932
  this.submitter = submitter;
700
- this.formData = buildFormData(formElement, submitter);
701
- this.location = expandURL(this.action);
702
- if (this.method == FetchMethod.get) {
703
- mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
704
- }
705
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
933
+ this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
706
934
  this.mustRedirect = mustRedirect;
707
935
  }
708
936
  get method() {
709
- var _a;
710
- const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
711
- return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
937
+ return this.fetchRequest.method;
938
+ }
939
+ set method(value) {
940
+ this.fetchRequest.method = value;
712
941
  }
713
942
  get action() {
714
- var _a;
715
- const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
716
- if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
717
- return this.submitter.getAttribute("formaction") || "";
718
- } else {
719
- return this.formElement.getAttribute("action") || formElementAction || "";
720
- }
943
+ return this.fetchRequest.url.toString();
944
+ }
945
+ set action(value) {
946
+ this.fetchRequest.url = expandURL(value);
721
947
  }
722
948
  get body() {
723
- if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
724
- return new URLSearchParams(this.stringFormData);
725
- } else {
726
- return this.formData;
727
- }
949
+ return this.fetchRequest.body;
728
950
  }
729
951
  get enctype() {
730
- var _a;
731
- return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
952
+ return this.fetchRequest.enctype;
732
953
  }
733
954
  get isSafe() {
734
955
  return this.fetchRequest.isSafe;
735
956
  }
736
- get stringFormData() {
737
- return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
957
+ get location() {
958
+ return this.fetchRequest.url;
738
959
  }
739
960
  async start() {
740
961
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
741
962
  const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
742
963
  if (typeof confirmationMessage === "string") {
743
- const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
964
+ const confirmMethod = typeof config.forms.confirm === "function" ? config.forms.confirm : FormSubmission.confirmMethod;
965
+ const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter);
744
966
  if (!answer) {
745
967
  return;
746
968
  }
@@ -770,10 +992,10 @@ class FormSubmission {
770
992
  }
771
993
  }
772
994
  requestStarted(_request) {
773
- var _a;
774
995
  this.state = FormSubmissionState.waiting;
775
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
996
+ if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);
776
997
  this.setSubmitsWith();
998
+ markAsBusy(this.formElement);
777
999
  dispatch("turbo:submit-start", {
778
1000
  target: this.formElement,
779
1001
  detail: {
@@ -783,6 +1005,7 @@ class FormSubmission {
783
1005
  this.delegate.formSubmissionStarted(this);
784
1006
  }
785
1007
  requestPreventedHandlingResponse(request, response) {
1008
+ prefetchCache.clear();
786
1009
  this.result = {
787
1010
  success: response.succeeded,
788
1011
  fetchResponse: response
@@ -791,7 +1014,10 @@ class FormSubmission {
791
1014
  requestSucceededWithResponse(request, response) {
792
1015
  if (response.clientError || response.serverError) {
793
1016
  this.delegate.formSubmissionFailedWithResponse(this, response);
794
- } else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
1017
+ return;
1018
+ }
1019
+ prefetchCache.clear();
1020
+ if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
795
1021
  const error = new Error("Form responses must redirect to another location");
796
1022
  this.delegate.formSubmissionErrored(this, error);
797
1023
  } else {
@@ -818,15 +1044,16 @@ class FormSubmission {
818
1044
  this.delegate.formSubmissionErrored(this, error);
819
1045
  }
820
1046
  requestFinished(_request) {
821
- var _a;
822
1047
  this.state = FormSubmissionState.stopped;
823
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
1048
+ if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);
824
1049
  this.resetSubmitterText();
1050
+ clearBusyState(this.formElement);
825
1051
  dispatch("turbo:submit-end", {
826
1052
  target: this.formElement,
827
- detail: Object.assign({
828
- formSubmission: this
829
- }, this.result)
1053
+ detail: {
1054
+ formSubmission: this,
1055
+ ...this.result
1056
+ }
830
1057
  });
831
1058
  this.delegate.formSubmissionFinished(this);
832
1059
  }
@@ -857,15 +1084,14 @@ class FormSubmission {
857
1084
  return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
858
1085
  }
859
1086
  get submitsWith() {
860
- var _a;
861
- return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
1087
+ return this.submitter?.getAttribute("data-turbo-submits-with");
862
1088
  }
863
1089
  }
864
1090
 
865
1091
  function buildFormData(formElement, submitter) {
866
1092
  const formData = new FormData(formElement);
867
- const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
868
- const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
1093
+ const name = submitter?.getAttribute("name");
1094
+ const value = submitter?.getAttribute("value");
869
1095
  if (name) {
870
1096
  formData.append(name, value || "");
871
1097
  }
@@ -887,14 +1113,30 @@ function responseSucceededWithoutRedirect(response) {
887
1113
  return response.statusCode == 200 && !response.redirected;
888
1114
  }
889
1115
 
890
- function mergeFormDataEntries(url, entries) {
891
- const searchParams = new URLSearchParams;
892
- for (const [name, value] of entries) {
893
- if (value instanceof File) continue;
894
- searchParams.append(name, value);
1116
+ function getFormAction(formElement, submitter) {
1117
+ const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
1118
+ if (submitter?.hasAttribute("formaction")) {
1119
+ return submitter.getAttribute("formaction") || "";
1120
+ } else {
1121
+ return formElement.getAttribute("action") || formElementAction || "";
895
1122
  }
896
- url.search = searchParams.toString();
897
- return url;
1123
+ }
1124
+
1125
+ function getAction(formAction, fetchMethod) {
1126
+ const action = expandURL(formAction);
1127
+ if (isSafe(fetchMethod)) {
1128
+ action.search = "";
1129
+ }
1130
+ return action;
1131
+ }
1132
+
1133
+ function getMethod(formElement, submitter) {
1134
+ const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
1135
+ return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
1136
+ }
1137
+
1138
+ function getEnctype(formElement, submitter) {
1139
+ return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
898
1140
  }
899
1141
 
900
1142
  class Snapshot {
@@ -917,11 +1159,7 @@ class Snapshot {
917
1159
  return this.element.isConnected;
918
1160
  }
919
1161
  get firstAutofocusableElement() {
920
- const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
921
- for (const element of this.element.querySelectorAll("[autofocus]")) {
922
- if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
923
- }
924
- return null;
1162
+ return queryAutofocusableElement(this.element);
925
1163
  }
926
1164
  get permanentElements() {
927
1165
  return queryPermanentElementsAll(this.element);
@@ -951,23 +1189,8 @@ function queryPermanentElementsAll(node) {
951
1189
  }
952
1190
 
953
1191
  class FormSubmitObserver {
1192
+ started=false;
954
1193
  constructor(delegate, eventTarget) {
955
- this.started = false;
956
- this.submitCaptured = () => {
957
- this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
958
- this.eventTarget.addEventListener("submit", this.submitBubbled, false);
959
- };
960
- this.submitBubbled = event => {
961
- if (!event.defaultPrevented) {
962
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
963
- const submitter = event.submitter || undefined;
964
- if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
965
- event.preventDefault();
966
- event.stopImmediatePropagation();
967
- this.delegate.formSubmitted(form, submitter);
968
- }
969
- }
970
- };
971
1194
  this.delegate = delegate;
972
1195
  this.eventTarget = eventTarget;
973
1196
  }
@@ -983,29 +1206,37 @@ class FormSubmitObserver {
983
1206
  this.started = false;
984
1207
  }
985
1208
  }
1209
+ submitCaptured=() => {
1210
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
1211
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
1212
+ };
1213
+ submitBubbled=event => {
1214
+ if (!event.defaultPrevented) {
1215
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1216
+ const submitter = event.submitter || undefined;
1217
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
1218
+ event.preventDefault();
1219
+ event.stopImmediatePropagation();
1220
+ this.delegate.formSubmitted(form, submitter);
1221
+ }
1222
+ }
1223
+ };
986
1224
  }
987
1225
 
988
1226
  function submissionDoesNotDismissDialog(form, submitter) {
989
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1227
+ const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
990
1228
  return method != "dialog";
991
1229
  }
992
1230
 
993
1231
  function submissionDoesNotTargetIFrame(form, submitter) {
994
- if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
995
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
996
- for (const element of document.getElementsByName(target)) {
997
- if (element instanceof HTMLIFrameElement) return false;
998
- }
999
- return true;
1000
- } else {
1001
- return true;
1002
- }
1232
+ const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
1233
+ return doesNotTargetIFrame(target);
1003
1234
  }
1004
1235
 
1005
1236
  class View {
1237
+ #resolveRenderPromise=_value => {};
1238
+ #resolveInterceptionPromise=_value => {};
1006
1239
  constructor(delegate, element) {
1007
- this.resolveRenderPromise = _value => {};
1008
- this.resolveInterceptionPromise = _value => {};
1009
1240
  this.delegate = delegate;
1010
1241
  this.element = element;
1011
1242
  }
@@ -1051,29 +1282,31 @@ class View {
1051
1282
  return window;
1052
1283
  }
1053
1284
  async render(renderer) {
1054
- const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
1285
+ const {isPreview: isPreview, shouldRender: shouldRender, willRender: willRender, newSnapshot: snapshot} = renderer;
1286
+ const shouldInvalidate = willRender;
1055
1287
  if (shouldRender) {
1056
1288
  try {
1057
- this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
1289
+ this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
1058
1290
  this.renderer = renderer;
1059
1291
  await this.prepareToRenderSnapshot(renderer);
1060
- const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
1292
+ const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
1061
1293
  const options = {
1062
- resume: this.resolveInterceptionPromise,
1063
- render: this.renderer.renderElement
1294
+ resume: this.#resolveInterceptionPromise,
1295
+ render: this.renderer.renderElement,
1296
+ renderMethod: this.renderer.renderMethod
1064
1297
  };
1065
1298
  const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
1066
1299
  if (!immediateRender) await renderInterception;
1067
1300
  await this.renderSnapshot(renderer);
1068
- this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1301
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
1069
1302
  this.delegate.preloadOnLoadLinksForView(this.element);
1070
1303
  this.finishRenderingSnapshot(renderer);
1071
1304
  } finally {
1072
1305
  delete this.renderer;
1073
- this.resolveRenderPromise(undefined);
1306
+ this.#resolveRenderPromise(undefined);
1074
1307
  delete this.renderPromise;
1075
1308
  }
1076
- } else {
1309
+ } else if (shouldInvalidate) {
1077
1310
  this.invalidate(renderer.reloadReason);
1078
1311
  }
1079
1312
  }
@@ -1091,6 +1324,12 @@ class View {
1091
1324
  this.element.removeAttribute("data-turbo-preview");
1092
1325
  }
1093
1326
  }
1327
+ markVisitDirection(direction) {
1328
+ this.element.setAttribute("data-turbo-visit-direction", direction);
1329
+ }
1330
+ unmarkVisitDirection() {
1331
+ this.element.removeAttribute("data-turbo-visit-direction");
1332
+ }
1094
1333
  async renderSnapshot(renderer) {
1095
1334
  await renderer.render();
1096
1335
  }
@@ -1110,26 +1349,6 @@ class FrameView extends View {
1110
1349
 
1111
1350
  class LinkInterceptor {
1112
1351
  constructor(delegate, element) {
1113
- this.clickBubbled = event => {
1114
- if (this.respondsToEventTarget(event.target)) {
1115
- this.clickEvent = event;
1116
- } else {
1117
- delete this.clickEvent;
1118
- }
1119
- };
1120
- this.linkClicked = event => {
1121
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1122
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1123
- this.clickEvent.preventDefault();
1124
- event.preventDefault();
1125
- this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1126
- }
1127
- }
1128
- delete this.clickEvent;
1129
- };
1130
- this.willVisit = _event => {
1131
- delete this.clickEvent;
1132
- };
1133
1352
  this.delegate = delegate;
1134
1353
  this.element = element;
1135
1354
  }
@@ -1143,32 +1362,36 @@ class LinkInterceptor {
1143
1362
  document.removeEventListener("turbo:click", this.linkClicked);
1144
1363
  document.removeEventListener("turbo:before-visit", this.willVisit);
1145
1364
  }
1146
- respondsToEventTarget(target) {
1147
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1148
- return element && element.closest("turbo-frame, html") == this.element;
1365
+ clickBubbled=event => {
1366
+ if (this.clickEventIsSignificant(event)) {
1367
+ this.clickEvent = event;
1368
+ } else {
1369
+ delete this.clickEvent;
1370
+ }
1371
+ };
1372
+ linkClicked=event => {
1373
+ if (this.clickEvent && this.clickEventIsSignificant(event)) {
1374
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1375
+ this.clickEvent.preventDefault();
1376
+ event.preventDefault();
1377
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1378
+ }
1379
+ }
1380
+ delete this.clickEvent;
1381
+ };
1382
+ willVisit=_event => {
1383
+ delete this.clickEvent;
1384
+ };
1385
+ clickEventIsSignificant(event) {
1386
+ const target = event.composed ? event.target?.parentElement : event.target;
1387
+ const element = findLinkFromClickTarget(target) || target;
1388
+ return element instanceof Element && element.closest("turbo-frame, html") == this.element;
1149
1389
  }
1150
1390
  }
1151
1391
 
1152
1392
  class LinkClickObserver {
1393
+ started=false;
1153
1394
  constructor(delegate, eventTarget) {
1154
- this.started = false;
1155
- this.clickCaptured = () => {
1156
- this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1157
- this.eventTarget.addEventListener("click", this.clickBubbled, false);
1158
- };
1159
- this.clickBubbled = event => {
1160
- if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1161
- const target = event.composedPath && event.composedPath()[0] || event.target;
1162
- const link = this.findLinkFromClickTarget(target);
1163
- if (link && doesNotTargetIFrame(link)) {
1164
- const location = this.getLocationForLink(link);
1165
- if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1166
- event.preventDefault();
1167
- this.delegate.followedLinkToLocation(link, location);
1168
- }
1169
- }
1170
- }
1171
- };
1172
1395
  this.delegate = delegate;
1173
1396
  this.eventTarget = eventTarget;
1174
1397
  }
@@ -1184,26 +1407,26 @@ class LinkClickObserver {
1184
1407
  this.started = false;
1185
1408
  }
1186
1409
  }
1410
+ clickCaptured=() => {
1411
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1412
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1413
+ };
1414
+ clickBubbled=event => {
1415
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1416
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1417
+ const link = findLinkFromClickTarget(target);
1418
+ if (link && doesNotTargetIFrame(link.target)) {
1419
+ const location = getLocationForLink(link);
1420
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1421
+ event.preventDefault();
1422
+ this.delegate.followedLinkToLocation(link, location);
1423
+ }
1424
+ }
1425
+ }
1426
+ };
1187
1427
  clickEventIsSignificant(event) {
1188
1428
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1189
1429
  }
1190
- findLinkFromClickTarget(target) {
1191
- return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1192
- }
1193
- getLocationForLink(link) {
1194
- return expandURL(link.getAttribute("href") || "");
1195
- }
1196
- }
1197
-
1198
- function doesNotTargetIFrame(anchor) {
1199
- if (anchor.hasAttribute("target")) {
1200
- for (const element of document.getElementsByName(anchor.target)) {
1201
- if (element instanceof HTMLIFrameElement) return false;
1202
- }
1203
- return true;
1204
- } else {
1205
- return true;
1206
- }
1207
1430
  }
1208
1431
 
1209
1432
  class FormLinkClickObserver {
@@ -1217,8 +1440,14 @@ class FormLinkClickObserver {
1217
1440
  stop() {
1218
1441
  this.linkInterceptor.stop();
1219
1442
  }
1443
+ canPrefetchRequestToLocation(link, location) {
1444
+ return false;
1445
+ }
1446
+ prefetchAndCacheRequestToLocation(link, location) {
1447
+ return;
1448
+ }
1220
1449
  willFollowLinkToLocation(link, location, originalEvent) {
1221
- return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1450
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
1222
1451
  }
1223
1452
  followedLinkToLocation(link, location) {
1224
1453
  const form = document.createElement("form");
@@ -1291,7 +1520,7 @@ class Bardo {
1291
1520
  }
1292
1521
  replacePlaceholderWithPermanentElement(permanentElement) {
1293
1522
  const placeholder = this.getPlaceholderById(permanentElement.id);
1294
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
1523
+ placeholder?.replaceWith(permanentElement);
1295
1524
  }
1296
1525
  getPlaceholderById(id) {
1297
1526
  return this.placeholders.find((element => element.content == id));
@@ -1309,13 +1538,14 @@ function createPlaceholderForPermanentElement(permanentElement) {
1309
1538
  }
1310
1539
 
1311
1540
  class Renderer {
1312
- constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1313
- this.activeElement = null;
1541
+ #activeElement=null;
1542
+ static renderElement(currentElement, newElement) {}
1543
+ constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1314
1544
  this.currentSnapshot = currentSnapshot;
1315
1545
  this.newSnapshot = newSnapshot;
1316
1546
  this.isPreview = isPreview;
1317
1547
  this.willRender = willRender;
1318
- this.renderElement = renderElement;
1548
+ this.renderElement = this.constructor.renderElement;
1319
1549
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1320
1550
  resolve: resolve,
1321
1551
  reject: reject
@@ -1324,135 +1554,757 @@ class Renderer {
1324
1554
  get shouldRender() {
1325
1555
  return true;
1326
1556
  }
1557
+ get shouldAutofocus() {
1558
+ return true;
1559
+ }
1327
1560
  get reloadReason() {
1328
1561
  return;
1329
1562
  }
1330
1563
  prepareToRender() {
1331
1564
  return;
1332
1565
  }
1566
+ render() {}
1333
1567
  finishRendering() {
1334
1568
  if (this.resolvingFunctions) {
1335
1569
  this.resolvingFunctions.resolve();
1336
1570
  delete this.resolvingFunctions;
1337
1571
  }
1338
1572
  }
1339
- async preservingPermanentElements(callback) {
1340
- await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1341
- }
1342
- focusFirstAutofocusableElement() {
1343
- const element = this.connectedSnapshot.firstAutofocusableElement;
1344
- if (elementIsFocusable(element)) {
1345
- element.focus();
1573
+ async preservingPermanentElements(callback) {
1574
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1575
+ }
1576
+ focusFirstAutofocusableElement() {
1577
+ if (this.shouldAutofocus) {
1578
+ const element = this.connectedSnapshot.firstAutofocusableElement;
1579
+ if (element) {
1580
+ element.focus();
1581
+ }
1582
+ }
1583
+ }
1584
+ enteringBardo(currentPermanentElement) {
1585
+ if (this.#activeElement) return;
1586
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1587
+ this.#activeElement = this.currentSnapshot.activeElement;
1588
+ }
1589
+ }
1590
+ leavingBardo(currentPermanentElement) {
1591
+ if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
1592
+ this.#activeElement.focus();
1593
+ this.#activeElement = null;
1594
+ }
1595
+ }
1596
+ get connectedSnapshot() {
1597
+ return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1598
+ }
1599
+ get currentElement() {
1600
+ return this.currentSnapshot.element;
1601
+ }
1602
+ get newElement() {
1603
+ return this.newSnapshot.element;
1604
+ }
1605
+ get permanentElementMap() {
1606
+ return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1607
+ }
1608
+ get renderMethod() {
1609
+ return "replace";
1610
+ }
1611
+ }
1612
+
1613
+ class FrameRenderer extends Renderer {
1614
+ static renderElement(currentElement, newElement) {
1615
+ const destinationRange = document.createRange();
1616
+ destinationRange.selectNodeContents(currentElement);
1617
+ destinationRange.deleteContents();
1618
+ const frameElement = newElement;
1619
+ const sourceRange = frameElement.ownerDocument?.createRange();
1620
+ if (sourceRange) {
1621
+ sourceRange.selectNodeContents(frameElement);
1622
+ currentElement.appendChild(sourceRange.extractContents());
1623
+ }
1624
+ }
1625
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1626
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1627
+ this.delegate = delegate;
1628
+ }
1629
+ get shouldRender() {
1630
+ return true;
1631
+ }
1632
+ async render() {
1633
+ await nextRepaint();
1634
+ this.preservingPermanentElements((() => {
1635
+ this.loadFrameElement();
1636
+ }));
1637
+ this.scrollFrameIntoView();
1638
+ await nextRepaint();
1639
+ this.focusFirstAutofocusableElement();
1640
+ await nextRepaint();
1641
+ this.activateScriptElements();
1642
+ }
1643
+ loadFrameElement() {
1644
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1645
+ this.renderElement(this.currentElement, this.newElement);
1646
+ }
1647
+ scrollFrameIntoView() {
1648
+ if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1649
+ const element = this.currentElement.firstElementChild;
1650
+ const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1651
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1652
+ if (element) {
1653
+ element.scrollIntoView({
1654
+ block: block,
1655
+ behavior: behavior
1656
+ });
1657
+ return true;
1658
+ }
1659
+ }
1660
+ return false;
1661
+ }
1662
+ activateScriptElements() {
1663
+ for (const inertScriptElement of this.newScriptElements) {
1664
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1665
+ inertScriptElement.replaceWith(activatedScriptElement);
1666
+ }
1667
+ }
1668
+ get newScriptElements() {
1669
+ return this.currentElement.querySelectorAll("script");
1670
+ }
1671
+ }
1672
+
1673
+ function readScrollLogicalPosition(value, defaultValue) {
1674
+ if (value == "end" || value == "start" || value == "center" || value == "nearest") {
1675
+ return value;
1676
+ } else {
1677
+ return defaultValue;
1678
+ }
1679
+ }
1680
+
1681
+ function readScrollBehavior(value, defaultValue) {
1682
+ if (value == "auto" || value == "smooth") {
1683
+ return value;
1684
+ } else {
1685
+ return defaultValue;
1686
+ }
1687
+ }
1688
+
1689
+ var Idiomorph = function() {
1690
+ let EMPTY_SET = new Set;
1691
+ let defaults = {
1692
+ morphStyle: "outerHTML",
1693
+ callbacks: {
1694
+ beforeNodeAdded: noOp,
1695
+ afterNodeAdded: noOp,
1696
+ beforeNodeMorphed: noOp,
1697
+ afterNodeMorphed: noOp,
1698
+ beforeNodeRemoved: noOp,
1699
+ afterNodeRemoved: noOp,
1700
+ beforeAttributeUpdated: noOp
1701
+ },
1702
+ head: {
1703
+ style: "merge",
1704
+ shouldPreserve: function(elt) {
1705
+ return elt.getAttribute("im-preserve") === "true";
1706
+ },
1707
+ shouldReAppend: function(elt) {
1708
+ return elt.getAttribute("im-re-append") === "true";
1709
+ },
1710
+ shouldRemove: noOp,
1711
+ afterHeadMorphed: noOp
1712
+ }
1713
+ };
1714
+ function morph(oldNode, newContent, config = {}) {
1715
+ if (oldNode instanceof Document) {
1716
+ oldNode = oldNode.documentElement;
1717
+ }
1718
+ if (typeof newContent === "string") {
1719
+ newContent = parseContent(newContent);
1720
+ }
1721
+ let normalizedContent = normalizeContent(newContent);
1722
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
1723
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
1724
+ }
1725
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
1726
+ if (ctx.head.block) {
1727
+ let oldHead = oldNode.querySelector("head");
1728
+ let newHead = normalizedNewContent.querySelector("head");
1729
+ if (oldHead && newHead) {
1730
+ let promises = handleHeadElement(newHead, oldHead, ctx);
1731
+ Promise.all(promises).then((function() {
1732
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
1733
+ head: {
1734
+ block: false,
1735
+ ignore: true
1736
+ }
1737
+ }));
1738
+ }));
1739
+ return;
1740
+ }
1741
+ }
1742
+ if (ctx.morphStyle === "innerHTML") {
1743
+ morphChildren(normalizedNewContent, oldNode, ctx);
1744
+ return oldNode.children;
1745
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
1746
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
1747
+ let previousSibling = bestMatch?.previousSibling;
1748
+ let nextSibling = bestMatch?.nextSibling;
1749
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
1750
+ if (bestMatch) {
1751
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
1752
+ } else {
1753
+ return [];
1754
+ }
1755
+ } else {
1756
+ throw "Do not understand how to morph style " + ctx.morphStyle;
1757
+ }
1758
+ }
1759
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
1760
+ return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
1761
+ }
1762
+ function morphOldNodeTo(oldNode, newContent, ctx) {
1763
+ if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
1764
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1765
+ oldNode.remove();
1766
+ ctx.callbacks.afterNodeRemoved(oldNode);
1767
+ return null;
1768
+ } else if (!isSoftMatch(oldNode, newContent)) {
1769
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1770
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
1771
+ oldNode.parentElement.replaceChild(newContent, oldNode);
1772
+ ctx.callbacks.afterNodeAdded(newContent);
1773
+ ctx.callbacks.afterNodeRemoved(oldNode);
1774
+ return newContent;
1775
+ } else {
1776
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
1777
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
1778
+ handleHeadElement(newContent, oldNode, ctx);
1779
+ } else {
1780
+ syncNodeFrom(newContent, oldNode, ctx);
1781
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
1782
+ morphChildren(newContent, oldNode, ctx);
1783
+ }
1784
+ }
1785
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
1786
+ return oldNode;
1787
+ }
1788
+ }
1789
+ function morphChildren(newParent, oldParent, ctx) {
1790
+ let nextNewChild = newParent.firstChild;
1791
+ let insertionPoint = oldParent.firstChild;
1792
+ let newChild;
1793
+ while (nextNewChild) {
1794
+ newChild = nextNewChild;
1795
+ nextNewChild = newChild.nextSibling;
1796
+ if (insertionPoint == null) {
1797
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1798
+ oldParent.appendChild(newChild);
1799
+ ctx.callbacks.afterNodeAdded(newChild);
1800
+ removeIdsFromConsideration(ctx, newChild);
1801
+ continue;
1802
+ }
1803
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
1804
+ morphOldNodeTo(insertionPoint, newChild, ctx);
1805
+ insertionPoint = insertionPoint.nextSibling;
1806
+ removeIdsFromConsideration(ctx, newChild);
1807
+ continue;
1808
+ }
1809
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1810
+ if (idSetMatch) {
1811
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
1812
+ morphOldNodeTo(idSetMatch, newChild, ctx);
1813
+ removeIdsFromConsideration(ctx, newChild);
1814
+ continue;
1815
+ }
1816
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1817
+ if (softMatch) {
1818
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
1819
+ morphOldNodeTo(softMatch, newChild, ctx);
1820
+ removeIdsFromConsideration(ctx, newChild);
1821
+ continue;
1822
+ }
1823
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1824
+ oldParent.insertBefore(newChild, insertionPoint);
1825
+ ctx.callbacks.afterNodeAdded(newChild);
1826
+ removeIdsFromConsideration(ctx, newChild);
1827
+ }
1828
+ while (insertionPoint !== null) {
1829
+ let tempNode = insertionPoint;
1830
+ insertionPoint = insertionPoint.nextSibling;
1831
+ removeNode(tempNode, ctx);
1832
+ }
1833
+ }
1834
+ function ignoreAttribute(attr, to, updateType, ctx) {
1835
+ if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
1836
+ return true;
1837
+ }
1838
+ return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
1839
+ }
1840
+ function syncNodeFrom(from, to, ctx) {
1841
+ let type = from.nodeType;
1842
+ if (type === 1) {
1843
+ const fromAttributes = from.attributes;
1844
+ const toAttributes = to.attributes;
1845
+ for (const fromAttribute of fromAttributes) {
1846
+ if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
1847
+ continue;
1848
+ }
1849
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
1850
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
1851
+ }
1852
+ }
1853
+ for (let i = toAttributes.length - 1; 0 <= i; i--) {
1854
+ const toAttribute = toAttributes[i];
1855
+ if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
1856
+ continue;
1857
+ }
1858
+ if (!from.hasAttribute(toAttribute.name)) {
1859
+ to.removeAttribute(toAttribute.name);
1860
+ }
1861
+ }
1862
+ }
1863
+ if (type === 8 || type === 3) {
1864
+ if (to.nodeValue !== from.nodeValue) {
1865
+ to.nodeValue = from.nodeValue;
1866
+ }
1867
+ }
1868
+ if (!ignoreValueOfActiveElement(to, ctx)) {
1869
+ syncInputValue(from, to, ctx);
1870
+ }
1871
+ }
1872
+ function syncBooleanAttribute(from, to, attributeName, ctx) {
1873
+ if (from[attributeName] !== to[attributeName]) {
1874
+ let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
1875
+ if (!ignoreUpdate) {
1876
+ to[attributeName] = from[attributeName];
1877
+ }
1878
+ if (from[attributeName]) {
1879
+ if (!ignoreUpdate) {
1880
+ to.setAttribute(attributeName, from[attributeName]);
1881
+ }
1882
+ } else {
1883
+ if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
1884
+ to.removeAttribute(attributeName);
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ function syncInputValue(from, to, ctx) {
1890
+ if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
1891
+ let fromValue = from.value;
1892
+ let toValue = to.value;
1893
+ syncBooleanAttribute(from, to, "checked", ctx);
1894
+ syncBooleanAttribute(from, to, "disabled", ctx);
1895
+ if (!from.hasAttribute("value")) {
1896
+ if (!ignoreAttribute("value", to, "remove", ctx)) {
1897
+ to.value = "";
1898
+ to.removeAttribute("value");
1899
+ }
1900
+ } else if (fromValue !== toValue) {
1901
+ if (!ignoreAttribute("value", to, "update", ctx)) {
1902
+ to.setAttribute("value", fromValue);
1903
+ to.value = fromValue;
1904
+ }
1905
+ }
1906
+ } else if (from instanceof HTMLOptionElement) {
1907
+ syncBooleanAttribute(from, to, "selected", ctx);
1908
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
1909
+ let fromValue = from.value;
1910
+ let toValue = to.value;
1911
+ if (ignoreAttribute("value", to, "update", ctx)) {
1912
+ return;
1913
+ }
1914
+ if (fromValue !== toValue) {
1915
+ to.value = fromValue;
1916
+ }
1917
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
1918
+ to.firstChild.nodeValue = fromValue;
1919
+ }
1920
+ }
1921
+ }
1922
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
1923
+ let added = [];
1924
+ let removed = [];
1925
+ let preserved = [];
1926
+ let nodesToAppend = [];
1927
+ let headMergeStyle = ctx.head.style;
1928
+ let srcToNewHeadNodes = new Map;
1929
+ for (const newHeadChild of newHeadTag.children) {
1930
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
1931
+ }
1932
+ for (const currentHeadElt of currentHead.children) {
1933
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
1934
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
1935
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
1936
+ if (inNewContent || isPreserved) {
1937
+ if (isReAppended) {
1938
+ removed.push(currentHeadElt);
1939
+ } else {
1940
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
1941
+ preserved.push(currentHeadElt);
1942
+ }
1943
+ } else {
1944
+ if (headMergeStyle === "append") {
1945
+ if (isReAppended) {
1946
+ removed.push(currentHeadElt);
1947
+ nodesToAppend.push(currentHeadElt);
1948
+ }
1949
+ } else {
1950
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
1951
+ removed.push(currentHeadElt);
1952
+ }
1953
+ }
1954
+ }
1346
1955
  }
1347
- }
1348
- enteringBardo(currentPermanentElement) {
1349
- if (this.activeElement) return;
1350
- if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1351
- this.activeElement = this.currentSnapshot.activeElement;
1956
+ nodesToAppend.push(...srcToNewHeadNodes.values());
1957
+ let promises = [];
1958
+ for (const newNode of nodesToAppend) {
1959
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
1960
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
1961
+ if (newElt.href || newElt.src) {
1962
+ let resolve = null;
1963
+ let promise = new Promise((function(_resolve) {
1964
+ resolve = _resolve;
1965
+ }));
1966
+ newElt.addEventListener("load", (function() {
1967
+ resolve();
1968
+ }));
1969
+ promises.push(promise);
1970
+ }
1971
+ currentHead.appendChild(newElt);
1972
+ ctx.callbacks.afterNodeAdded(newElt);
1973
+ added.push(newElt);
1974
+ }
1352
1975
  }
1353
- }
1354
- leavingBardo(currentPermanentElement) {
1355
- if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1356
- this.activeElement.focus();
1357
- this.activeElement = null;
1976
+ for (const removedElement of removed) {
1977
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
1978
+ currentHead.removeChild(removedElement);
1979
+ ctx.callbacks.afterNodeRemoved(removedElement);
1980
+ }
1358
1981
  }
1982
+ ctx.head.afterHeadMorphed(currentHead, {
1983
+ added: added,
1984
+ kept: preserved,
1985
+ removed: removed
1986
+ });
1987
+ return promises;
1988
+ }
1989
+ function noOp() {}
1990
+ function mergeDefaults(config) {
1991
+ let finalConfig = {};
1992
+ Object.assign(finalConfig, defaults);
1993
+ Object.assign(finalConfig, config);
1994
+ finalConfig.callbacks = {};
1995
+ Object.assign(finalConfig.callbacks, defaults.callbacks);
1996
+ Object.assign(finalConfig.callbacks, config.callbacks);
1997
+ finalConfig.head = {};
1998
+ Object.assign(finalConfig.head, defaults.head);
1999
+ Object.assign(finalConfig.head, config.head);
2000
+ return finalConfig;
2001
+ }
2002
+ function createMorphContext(oldNode, newContent, config) {
2003
+ config = mergeDefaults(config);
2004
+ return {
2005
+ target: oldNode,
2006
+ newContent: newContent,
2007
+ config: config,
2008
+ morphStyle: config.morphStyle,
2009
+ ignoreActive: config.ignoreActive,
2010
+ ignoreActiveValue: config.ignoreActiveValue,
2011
+ idMap: createIdMap(oldNode, newContent),
2012
+ deadIds: new Set,
2013
+ callbacks: config.callbacks,
2014
+ head: config.head
2015
+ };
1359
2016
  }
1360
- get connectedSnapshot() {
1361
- return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1362
- }
1363
- get currentElement() {
1364
- return this.currentSnapshot.element;
2017
+ function isIdSetMatch(node1, node2, ctx) {
2018
+ if (node1 == null || node2 == null) {
2019
+ return false;
2020
+ }
2021
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
2022
+ if (node1.id !== "" && node1.id === node2.id) {
2023
+ return true;
2024
+ } else {
2025
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
2026
+ }
2027
+ }
2028
+ return false;
1365
2029
  }
1366
- get newElement() {
1367
- return this.newSnapshot.element;
2030
+ function isSoftMatch(node1, node2) {
2031
+ if (node1 == null || node2 == null) {
2032
+ return false;
2033
+ }
2034
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
2035
+ }
2036
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
2037
+ while (startInclusive !== endExclusive) {
2038
+ let tempNode = startInclusive;
2039
+ startInclusive = startInclusive.nextSibling;
2040
+ removeNode(tempNode, ctx);
2041
+ }
2042
+ removeIdsFromConsideration(ctx, endExclusive);
2043
+ return endExclusive.nextSibling;
2044
+ }
2045
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2046
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
2047
+ let potentialMatch = null;
2048
+ if (newChildPotentialIdCount > 0) {
2049
+ let potentialMatch = insertionPoint;
2050
+ let otherMatchCount = 0;
2051
+ while (potentialMatch != null) {
2052
+ if (isIdSetMatch(newChild, potentialMatch, ctx)) {
2053
+ return potentialMatch;
2054
+ }
2055
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
2056
+ if (otherMatchCount > newChildPotentialIdCount) {
2057
+ return null;
2058
+ }
2059
+ potentialMatch = potentialMatch.nextSibling;
2060
+ }
2061
+ }
2062
+ return potentialMatch;
1368
2063
  }
1369
- get permanentElementMap() {
1370
- return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
2064
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2065
+ let potentialSoftMatch = insertionPoint;
2066
+ let nextSibling = newChild.nextSibling;
2067
+ let siblingSoftMatchCount = 0;
2068
+ while (potentialSoftMatch != null) {
2069
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
2070
+ return null;
2071
+ }
2072
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
2073
+ return potentialSoftMatch;
2074
+ }
2075
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
2076
+ siblingSoftMatchCount++;
2077
+ nextSibling = nextSibling.nextSibling;
2078
+ if (siblingSoftMatchCount >= 2) {
2079
+ return null;
2080
+ }
2081
+ }
2082
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
2083
+ }
2084
+ return potentialSoftMatch;
2085
+ }
2086
+ function parseContent(newContent) {
2087
+ let parser = new DOMParser;
2088
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
2089
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
2090
+ let content = parser.parseFromString(newContent, "text/html");
2091
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
2092
+ content.generatedByIdiomorph = true;
2093
+ return content;
2094
+ } else {
2095
+ let htmlElement = content.firstChild;
2096
+ if (htmlElement) {
2097
+ htmlElement.generatedByIdiomorph = true;
2098
+ return htmlElement;
2099
+ } else {
2100
+ return null;
2101
+ }
2102
+ }
2103
+ } else {
2104
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
2105
+ let content = responseDoc.body.querySelector("template").content;
2106
+ content.generatedByIdiomorph = true;
2107
+ return content;
2108
+ }
2109
+ }
2110
+ function normalizeContent(newContent) {
2111
+ if (newContent == null) {
2112
+ const dummyParent = document.createElement("div");
2113
+ return dummyParent;
2114
+ } else if (newContent.generatedByIdiomorph) {
2115
+ return newContent;
2116
+ } else if (newContent instanceof Node) {
2117
+ const dummyParent = document.createElement("div");
2118
+ dummyParent.append(newContent);
2119
+ return dummyParent;
2120
+ } else {
2121
+ const dummyParent = document.createElement("div");
2122
+ for (const elt of [ ...newContent ]) {
2123
+ dummyParent.append(elt);
2124
+ }
2125
+ return dummyParent;
2126
+ }
2127
+ }
2128
+ function insertSiblings(previousSibling, morphedNode, nextSibling) {
2129
+ let stack = [];
2130
+ let added = [];
2131
+ while (previousSibling != null) {
2132
+ stack.push(previousSibling);
2133
+ previousSibling = previousSibling.previousSibling;
2134
+ }
2135
+ while (stack.length > 0) {
2136
+ let node = stack.pop();
2137
+ added.push(node);
2138
+ morphedNode.parentElement.insertBefore(node, morphedNode);
2139
+ }
2140
+ added.push(morphedNode);
2141
+ while (nextSibling != null) {
2142
+ stack.push(nextSibling);
2143
+ added.push(nextSibling);
2144
+ nextSibling = nextSibling.nextSibling;
2145
+ }
2146
+ while (stack.length > 0) {
2147
+ morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
2148
+ }
2149
+ return added;
2150
+ }
2151
+ function findBestNodeMatch(newContent, oldNode, ctx) {
2152
+ let currentElement;
2153
+ currentElement = newContent.firstChild;
2154
+ let bestElement = currentElement;
2155
+ let score = 0;
2156
+ while (currentElement) {
2157
+ let newScore = scoreElement(currentElement, oldNode, ctx);
2158
+ if (newScore > score) {
2159
+ bestElement = currentElement;
2160
+ score = newScore;
2161
+ }
2162
+ currentElement = currentElement.nextSibling;
2163
+ }
2164
+ return bestElement;
1371
2165
  }
1372
- }
1373
-
1374
- function elementIsFocusable(element) {
1375
- return element && typeof element.focus == "function";
1376
- }
1377
-
1378
- class FrameRenderer extends Renderer {
1379
- static renderElement(currentElement, newElement) {
1380
- var _a;
1381
- const destinationRange = document.createRange();
1382
- destinationRange.selectNodeContents(currentElement);
1383
- destinationRange.deleteContents();
1384
- const frameElement = newElement;
1385
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1386
- if (sourceRange) {
1387
- sourceRange.selectNodeContents(frameElement);
1388
- currentElement.appendChild(sourceRange.extractContents());
2166
+ function scoreElement(node1, node2, ctx) {
2167
+ if (isSoftMatch(node1, node2)) {
2168
+ return .5 + getIdIntersectionCount(ctx, node1, node2);
1389
2169
  }
2170
+ return 0;
1390
2171
  }
1391
- constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1392
- super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1393
- this.delegate = delegate;
2172
+ function removeNode(tempNode, ctx) {
2173
+ removeIdsFromConsideration(ctx, tempNode);
2174
+ if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
2175
+ tempNode.remove();
2176
+ ctx.callbacks.afterNodeRemoved(tempNode);
1394
2177
  }
1395
- get shouldRender() {
1396
- return true;
2178
+ function isIdInConsideration(ctx, id) {
2179
+ return !ctx.deadIds.has(id);
1397
2180
  }
1398
- async render() {
1399
- await nextAnimationFrame();
1400
- this.preservingPermanentElements((() => {
1401
- this.loadFrameElement();
1402
- }));
1403
- this.scrollFrameIntoView();
1404
- await nextAnimationFrame();
1405
- this.focusFirstAutofocusableElement();
1406
- await nextAnimationFrame();
1407
- this.activateScriptElements();
2181
+ function idIsWithinNode(ctx, id, targetNode) {
2182
+ let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
2183
+ return idSet.has(id);
1408
2184
  }
1409
- loadFrameElement() {
1410
- this.delegate.willRenderFrame(this.currentElement, this.newElement);
1411
- this.renderElement(this.currentElement, this.newElement);
2185
+ function removeIdsFromConsideration(ctx, node) {
2186
+ let idSet = ctx.idMap.get(node) || EMPTY_SET;
2187
+ for (const id of idSet) {
2188
+ ctx.deadIds.add(id);
2189
+ }
1412
2190
  }
1413
- scrollFrameIntoView() {
1414
- if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1415
- const element = this.currentElement.firstElementChild;
1416
- const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1417
- const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1418
- if (element) {
1419
- element.scrollIntoView({
1420
- block: block,
1421
- behavior: behavior
1422
- });
1423
- return true;
2191
+ function getIdIntersectionCount(ctx, node1, node2) {
2192
+ let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
2193
+ let matchCount = 0;
2194
+ for (const id of sourceSet) {
2195
+ if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
2196
+ ++matchCount;
1424
2197
  }
1425
2198
  }
1426
- return false;
1427
- }
1428
- activateScriptElements() {
1429
- for (const inertScriptElement of this.newScriptElements) {
1430
- const activatedScriptElement = activateScriptElement(inertScriptElement);
1431
- inertScriptElement.replaceWith(activatedScriptElement);
2199
+ return matchCount;
2200
+ }
2201
+ function populateIdMapForNode(node, idMap) {
2202
+ let nodeParent = node.parentElement;
2203
+ let idElements = node.querySelectorAll("[id]");
2204
+ for (const elt of idElements) {
2205
+ let current = elt;
2206
+ while (current !== nodeParent && current != null) {
2207
+ let idSet = idMap.get(current);
2208
+ if (idSet == null) {
2209
+ idSet = new Set;
2210
+ idMap.set(current, idSet);
2211
+ }
2212
+ idSet.add(elt.id);
2213
+ current = current.parentElement;
2214
+ }
1432
2215
  }
1433
2216
  }
1434
- get newScriptElements() {
1435
- return this.currentElement.querySelectorAll("script");
2217
+ function createIdMap(oldContent, newContent) {
2218
+ let idMap = new Map;
2219
+ populateIdMapForNode(oldContent, idMap);
2220
+ populateIdMapForNode(newContent, idMap);
2221
+ return idMap;
1436
2222
  }
2223
+ return {
2224
+ morph: morph,
2225
+ defaults: defaults
2226
+ };
2227
+ }();
2228
+
2229
+ function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
2230
+ Idiomorph.morph(currentElement, newElement, {
2231
+ ...options,
2232
+ callbacks: new DefaultIdiomorphCallbacks(callbacks)
2233
+ });
1437
2234
  }
1438
2235
 
1439
- function readScrollLogicalPosition(value, defaultValue) {
1440
- if (value == "end" || value == "start" || value == "center" || value == "nearest") {
1441
- return value;
1442
- } else {
1443
- return defaultValue;
1444
- }
2236
+ function morphChildren(currentElement, newElement) {
2237
+ morphElements(currentElement, newElement.children, {
2238
+ morphStyle: "innerHTML"
2239
+ });
1445
2240
  }
1446
2241
 
1447
- function readScrollBehavior(value, defaultValue) {
1448
- if (value == "auto" || value == "smooth") {
1449
- return value;
1450
- } else {
1451
- return defaultValue;
2242
+ class DefaultIdiomorphCallbacks {
2243
+ #beforeNodeMorphed;
2244
+ constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
2245
+ this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
2246
+ }
2247
+ beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
2248
+ beforeNodeMorphed=(currentElement, newElement) => {
2249
+ if (currentElement instanceof Element) {
2250
+ if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
2251
+ const event = dispatch("turbo:before-morph-element", {
2252
+ cancelable: true,
2253
+ target: currentElement,
2254
+ detail: {
2255
+ currentElement: currentElement,
2256
+ newElement: newElement
2257
+ }
2258
+ });
2259
+ return !event.defaultPrevented;
2260
+ } else {
2261
+ return false;
2262
+ }
2263
+ }
2264
+ };
2265
+ beforeAttributeUpdated=(attributeName, target, mutationType) => {
2266
+ const event = dispatch("turbo:before-morph-attribute", {
2267
+ cancelable: true,
2268
+ target: target,
2269
+ detail: {
2270
+ attributeName: attributeName,
2271
+ mutationType: mutationType
2272
+ }
2273
+ });
2274
+ return !event.defaultPrevented;
2275
+ };
2276
+ beforeNodeRemoved=node => this.beforeNodeMorphed(node);
2277
+ afterNodeMorphed=(currentElement, newElement) => {
2278
+ if (currentElement instanceof Element) {
2279
+ dispatch("turbo:morph-element", {
2280
+ target: currentElement,
2281
+ detail: {
2282
+ currentElement: currentElement,
2283
+ newElement: newElement
2284
+ }
2285
+ });
2286
+ }
2287
+ };
2288
+ }
2289
+
2290
+ class MorphingFrameRenderer extends FrameRenderer {
2291
+ static renderElement(currentElement, newElement) {
2292
+ dispatch("turbo:before-frame-morph", {
2293
+ target: currentElement,
2294
+ detail: {
2295
+ currentElement: currentElement,
2296
+ newElement: newElement
2297
+ }
2298
+ });
2299
+ morphChildren(currentElement, newElement);
2300
+ }
2301
+ async preservingPermanentElements(callback) {
2302
+ return await callback();
1452
2303
  }
1453
2304
  }
1454
2305
 
1455
2306
  class ProgressBar {
2307
+ static animationDuration=300;
1456
2308
  static get defaultCSS() {
1457
2309
  return unindent`
1458
2310
  .turbo-progress-bar {
@@ -1470,13 +2322,10 @@ class ProgressBar {
1470
2322
  }
1471
2323
  `;
1472
2324
  }
2325
+ hiding=false;
2326
+ value=0;
2327
+ visible=false;
1473
2328
  constructor() {
1474
- this.hiding = false;
1475
- this.value = 0;
1476
- this.visible = false;
1477
- this.trickle = () => {
1478
- this.setValue(this.value + Math.random() / 100);
1479
- };
1480
2329
  this.stylesheetElement = this.createStylesheetElement();
1481
2330
  this.progressElement = this.createProgressElement();
1482
2331
  this.installStylesheetElement();
@@ -1531,6 +2380,9 @@ class ProgressBar {
1531
2380
  window.clearInterval(this.trickleInterval);
1532
2381
  delete this.trickleInterval;
1533
2382
  }
2383
+ trickle=() => {
2384
+ this.setValue(this.value + Math.random() / 100);
2385
+ };
1534
2386
  refresh() {
1535
2387
  requestAnimationFrame((() => {
1536
2388
  this.progressElement.style.width = `${10 + this.value * 90}%`;
@@ -1540,8 +2392,9 @@ class ProgressBar {
1540
2392
  const element = document.createElement("style");
1541
2393
  element.type = "text/css";
1542
2394
  element.textContent = ProgressBar.defaultCSS;
1543
- if (this.cspNonce) {
1544
- element.nonce = this.cspNonce;
2395
+ const cspNonce = getCspNonce();
2396
+ if (cspNonce) {
2397
+ element.nonce = cspNonce;
1545
2398
  }
1546
2399
  return element;
1547
2400
  }
@@ -1550,30 +2403,24 @@ class ProgressBar {
1550
2403
  element.className = "turbo-progress-bar";
1551
2404
  return element;
1552
2405
  }
1553
- get cspNonce() {
1554
- return getMetaContent("csp-nonce");
1555
- }
1556
2406
  }
1557
2407
 
1558
- ProgressBar.animationDuration = 300;
1559
-
1560
2408
  class HeadSnapshot extends Snapshot {
1561
- constructor() {
1562
- super(...arguments);
1563
- this.detailsByOuterHTML = this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1564
- const {outerHTML: outerHTML} = element;
1565
- const details = outerHTML in result ? result[outerHTML] : {
1566
- type: elementType(element),
1567
- tracked: elementIsTracked(element),
1568
- elements: []
1569
- };
1570
- return Object.assign(Object.assign({}, result), {
1571
- [outerHTML]: Object.assign(Object.assign({}, details), {
1572
- elements: [ ...details.elements, element ]
1573
- })
1574
- });
1575
- }), {});
1576
- }
2409
+ detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
2410
+ const {outerHTML: outerHTML} = element;
2411
+ const details = outerHTML in result ? result[outerHTML] : {
2412
+ type: elementType(element),
2413
+ tracked: elementIsTracked(element),
2414
+ elements: []
2415
+ };
2416
+ return {
2417
+ ...result,
2418
+ [outerHTML]: {
2419
+ ...details,
2420
+ elements: [ ...details.elements, element ]
2421
+ }
2422
+ };
2423
+ }), {});
1577
2424
  get trackedElementSignature() {
1578
2425
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1579
2426
  }
@@ -1606,7 +2453,7 @@ class HeadSnapshot extends Snapshot {
1606
2453
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1607
2454
  const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1608
2455
  return elementIsMetaElementWithName(element, name) ? element : result;
1609
- }), undefined);
2456
+ }), undefined | undefined);
1610
2457
  }
1611
2458
  }
1612
2459
 
@@ -1656,11 +2503,12 @@ class PageSnapshot extends Snapshot {
1656
2503
  static fromElement(element) {
1657
2504
  return this.fromDocument(element.ownerDocument);
1658
2505
  }
1659
- static fromDocument({head: head, body: body}) {
1660
- return new this(body, new HeadSnapshot(head));
2506
+ static fromDocument({documentElement: documentElement, body: body, head: head}) {
2507
+ return new this(documentElement, body, new HeadSnapshot(head));
1661
2508
  }
1662
- constructor(element, headSnapshot) {
1663
- super(element);
2509
+ constructor(documentElement, body, headSnapshot) {
2510
+ super(body);
2511
+ this.documentElement = documentElement;
1664
2512
  this.headSnapshot = headSnapshot;
1665
2513
  }
1666
2514
  clone() {
@@ -1675,14 +2523,16 @@ class PageSnapshot extends Snapshot {
1675
2523
  for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1676
2524
  clonedPasswordInput.value = "";
1677
2525
  }
1678
- return new PageSnapshot(clonedElement, this.headSnapshot);
2526
+ return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
2527
+ }
2528
+ get lang() {
2529
+ return this.documentElement.getAttribute("lang");
1679
2530
  }
1680
2531
  get headElement() {
1681
2532
  return this.headSnapshot.element;
1682
2533
  }
1683
2534
  get rootLocation() {
1684
- var _a;
1685
- const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
2535
+ const root = this.getSetting("root") ?? "/";
1686
2536
  return expandURL(root);
1687
2537
  }
1688
2538
  get cacheControlValue() {
@@ -1697,29 +2547,38 @@ class PageSnapshot extends Snapshot {
1697
2547
  get isVisitable() {
1698
2548
  return this.getSetting("visit-control") != "reload";
1699
2549
  }
2550
+ get prefersViewTransitions() {
2551
+ return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
2552
+ }
2553
+ get shouldMorphPage() {
2554
+ return this.getSetting("refresh-method") === "morph";
2555
+ }
2556
+ get shouldPreserveScrollPosition() {
2557
+ return this.getSetting("refresh-scroll") === "preserve";
2558
+ }
1700
2559
  getSetting(name) {
1701
2560
  return this.headSnapshot.getMetaValue(`turbo-${name}`);
1702
2561
  }
1703
2562
  }
1704
2563
 
1705
- var TimingMetric;
1706
-
1707
- (function(TimingMetric) {
1708
- TimingMetric["visitStart"] = "visitStart";
1709
- TimingMetric["requestStart"] = "requestStart";
1710
- TimingMetric["requestEnd"] = "requestEnd";
1711
- TimingMetric["visitEnd"] = "visitEnd";
1712
- })(TimingMetric || (TimingMetric = {}));
1713
-
1714
- var VisitState;
1715
-
1716
- (function(VisitState) {
1717
- VisitState["initialized"] = "initialized";
1718
- VisitState["started"] = "started";
1719
- VisitState["canceled"] = "canceled";
1720
- VisitState["failed"] = "failed";
1721
- VisitState["completed"] = "completed";
1722
- })(VisitState || (VisitState = {}));
2564
+ class ViewTransitioner {
2565
+ #viewTransitionStarted=false;
2566
+ #lastOperation=Promise.resolve();
2567
+ renderChange(useViewTransition, render) {
2568
+ if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
2569
+ this.#viewTransitionStarted = true;
2570
+ this.#lastOperation = this.#lastOperation.then((async () => {
2571
+ await document.startViewTransition(render).finished;
2572
+ }));
2573
+ } else {
2574
+ this.#lastOperation = this.#lastOperation.then(render);
2575
+ }
2576
+ return this.#lastOperation;
2577
+ }
2578
+ get viewTransitionsAvailable() {
2579
+ return document.startViewTransition;
2580
+ }
2581
+ }
1723
2582
 
1724
2583
  const defaultOptions = {
1725
2584
  action: "advance",
@@ -1731,29 +2590,52 @@ const defaultOptions = {
1731
2590
  acceptsStreamResponse: false
1732
2591
  };
1733
2592
 
1734
- var SystemStatusCode;
2593
+ const TimingMetric = {
2594
+ visitStart: "visitStart",
2595
+ requestStart: "requestStart",
2596
+ requestEnd: "requestEnd",
2597
+ visitEnd: "visitEnd"
2598
+ };
2599
+
2600
+ const VisitState = {
2601
+ initialized: "initialized",
2602
+ started: "started",
2603
+ canceled: "canceled",
2604
+ failed: "failed",
2605
+ completed: "completed"
2606
+ };
2607
+
2608
+ const SystemStatusCode = {
2609
+ networkFailure: 0,
2610
+ timeoutFailure: -1,
2611
+ contentTypeMismatch: -2
2612
+ };
1735
2613
 
1736
- (function(SystemStatusCode) {
1737
- SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
1738
- SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
1739
- SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch";
1740
- })(SystemStatusCode || (SystemStatusCode = {}));
2614
+ const Direction = {
2615
+ advance: "forward",
2616
+ restore: "back",
2617
+ replace: "none"
2618
+ };
1741
2619
 
1742
2620
  class Visit {
2621
+ identifier=uuid();
2622
+ timingMetrics={};
2623
+ followedRedirect=false;
2624
+ historyChanged=false;
2625
+ scrolled=false;
2626
+ shouldCacheSnapshot=true;
2627
+ acceptsStreamResponse=false;
2628
+ snapshotCached=false;
2629
+ state=VisitState.initialized;
2630
+ viewTransitioner=new ViewTransitioner;
1743
2631
  constructor(delegate, location, restorationIdentifier, options = {}) {
1744
- this.identifier = uuid();
1745
- this.timingMetrics = {};
1746
- this.followedRedirect = false;
1747
- this.historyChanged = false;
1748
- this.scrolled = false;
1749
- this.shouldCacheSnapshot = true;
1750
- this.acceptsStreamResponse = false;
1751
- this.snapshotCached = false;
1752
- this.state = VisitState.initialized;
1753
2632
  this.delegate = delegate;
1754
2633
  this.location = location;
1755
2634
  this.restorationIdentifier = restorationIdentifier || uuid();
1756
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
2635
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
2636
+ ...defaultOptions,
2637
+ ...options
2638
+ };
1757
2639
  this.action = action;
1758
2640
  this.historyChanged = historyChanged;
1759
2641
  this.referrer = referrer;
@@ -1761,12 +2643,14 @@ class Visit {
1761
2643
  this.snapshotHTML = snapshotHTML;
1762
2644
  this.response = response;
1763
2645
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
2646
+ this.isPageRefresh = this.view.isPageRefresh(this);
1764
2647
  this.visitCachedSnapshot = visitCachedSnapshot;
1765
2648
  this.willRender = willRender;
1766
2649
  this.updateHistory = updateHistory;
1767
2650
  this.scrolled = !willRender;
1768
2651
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1769
2652
  this.acceptsStreamResponse = acceptsStreamResponse;
2653
+ this.direction = direction || Direction[action];
1770
2654
  }
1771
2655
  get adapter() {
1772
2656
  return this.delegate.adapter;
@@ -1803,10 +2687,10 @@ class Visit {
1803
2687
  complete() {
1804
2688
  if (this.state == VisitState.started) {
1805
2689
  this.recordTimingMetric(TimingMetric.visitEnd);
2690
+ this.adapter.visitCompleted(this);
1806
2691
  this.state = VisitState.completed;
1807
2692
  this.followRedirect();
1808
2693
  if (!this.followedRedirect) {
1809
- this.adapter.visitCompleted(this);
1810
2694
  this.delegate.visitCompleted(this);
1811
2695
  }
1812
2696
  }
@@ -1815,12 +2699,12 @@ class Visit {
1815
2699
  if (this.state == VisitState.started) {
1816
2700
  this.state = VisitState.failed;
1817
2701
  this.adapter.visitFailed(this);
2702
+ this.delegate.visitCompleted(this);
1818
2703
  }
1819
2704
  }
1820
2705
  changeHistory() {
1821
- var _a;
1822
2706
  if (!this.historyChanged && this.updateHistory) {
1823
- const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
2707
+ const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
1824
2708
  const method = getHistoryMethodForAction(actionForHistory);
1825
2709
  this.history.update(method, this.location, this.restorationIdentifier);
1826
2710
  this.historyChanged = true;
@@ -1867,8 +2751,8 @@ class Visit {
1867
2751
  if (this.shouldCacheSnapshot) this.cacheSnapshot();
1868
2752
  if (this.view.renderPromise) await this.view.renderPromise;
1869
2753
  if (isSuccessful(statusCode) && responseHTML != null) {
1870
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1871
- this.performScroll();
2754
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2755
+ await this.renderPageSnapshot(snapshot, false);
1872
2756
  this.adapter.visitRendered(this);
1873
2757
  this.complete();
1874
2758
  } else {
@@ -1901,12 +2785,11 @@ class Visit {
1901
2785
  const isPreview = this.shouldIssueRequest();
1902
2786
  this.render((async () => {
1903
2787
  this.cacheSnapshot();
1904
- if (this.isSamePage) {
2788
+ if (this.isSamePage || this.isPageRefresh) {
1905
2789
  this.adapter.visitRendered(this);
1906
2790
  } else {
1907
2791
  if (this.view.renderPromise) await this.view.renderPromise;
1908
- await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1909
- this.performScroll();
2792
+ await this.renderPageSnapshot(snapshot, isPreview);
1910
2793
  this.adapter.visitRendered(this);
1911
2794
  if (!isPreview) {
1912
2795
  this.complete();
@@ -1916,8 +2799,7 @@ class Visit {
1916
2799
  }
1917
2800
  }
1918
2801
  followRedirect() {
1919
- var _a;
1920
- if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
2802
+ if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
1921
2803
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1922
2804
  action: "replace",
1923
2805
  response: this.response,
@@ -1989,7 +2871,7 @@ class Visit {
1989
2871
  this.finishRequest();
1990
2872
  }
1991
2873
  performScroll() {
1992
- if (!this.scrolled && !this.view.forceReloaded) {
2874
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
1993
2875
  if (this.action == "restore") {
1994
2876
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1995
2877
  } else {
@@ -2019,7 +2901,9 @@ class Visit {
2019
2901
  this.timingMetrics[metric] = (new Date).getTime();
2020
2902
  }
2021
2903
  getTimingMetrics() {
2022
- return Object.assign({}, this.timingMetrics);
2904
+ return {
2905
+ ...this.timingMetrics
2906
+ };
2023
2907
  }
2024
2908
  getHistoryMethodForAction(action) {
2025
2909
  switch (action) {
@@ -2052,11 +2936,17 @@ class Visit {
2052
2936
  async render(callback) {
2053
2937
  this.cancelRender();
2054
2938
  await new Promise((resolve => {
2055
- this.frame = requestAnimationFrame((() => resolve()));
2939
+ this.frame = document.visibilityState === "hidden" ? setTimeout((() => resolve()), 0) : requestAnimationFrame((() => resolve()));
2056
2940
  }));
2057
2941
  await callback();
2058
2942
  delete this.frame;
2059
2943
  }
2944
+ async renderPageSnapshot(snapshot, isPreview) {
2945
+ await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
2946
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
2947
+ this.performScroll();
2948
+ }));
2949
+ }
2060
2950
  cancelRender() {
2061
2951
  if (this.frame) {
2062
2952
  cancelAnimationFrame(this.frame);
@@ -2070,15 +2960,16 @@ function isSuccessful(statusCode) {
2070
2960
  }
2071
2961
 
2072
2962
  class BrowserAdapter {
2963
+ progressBar=new ProgressBar;
2073
2964
  constructor(session) {
2074
- this.progressBar = new ProgressBar;
2075
- this.showProgressBar = () => {
2076
- this.progressBar.show();
2077
- };
2078
2965
  this.session = session;
2079
2966
  }
2080
2967
  visitProposedToLocation(location, options) {
2081
- this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
2968
+ if (locationIsVisitable(location, this.navigator.rootLocation)) {
2969
+ this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
2970
+ } else {
2971
+ window.location.href = location.toString();
2972
+ }
2082
2973
  }
2083
2974
  visitStarted(visit) {
2084
2975
  this.location = visit.location;
@@ -2113,15 +3004,18 @@ class BrowserAdapter {
2113
3004
  return visit.loadResponse();
2114
3005
  }
2115
3006
  }
2116
- visitRequestFinished(_visit) {
3007
+ visitRequestFinished(_visit) {}
3008
+ visitCompleted(_visit) {
2117
3009
  this.progressBar.setValue(1);
2118
3010
  this.hideVisitProgressBar();
2119
3011
  }
2120
- visitCompleted(_visit) {}
2121
3012
  pageInvalidated(reason) {
2122
3013
  this.reload(reason);
2123
3014
  }
2124
- visitFailed(_visit) {}
3015
+ visitFailed(_visit) {
3016
+ this.progressBar.setValue(1);
3017
+ this.hideVisitProgressBar();
3018
+ }
2125
3019
  visitRendered(_visit) {}
2126
3020
  formSubmissionStarted(_formSubmission) {
2127
3021
  this.progressBar.setValue(0);
@@ -2153,12 +3047,14 @@ class BrowserAdapter {
2153
3047
  delete this.formProgressBarTimeout;
2154
3048
  }
2155
3049
  }
3050
+ showProgressBar=() => {
3051
+ this.progressBar.show();
3052
+ };
2156
3053
  reload(reason) {
2157
- var _a;
2158
3054
  dispatch("turbo:reload", {
2159
3055
  detail: reason
2160
3056
  });
2161
- window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
3057
+ window.location.href = this.location?.toString() || window.location.href;
2162
3058
  }
2163
3059
  get navigator() {
2164
3060
  return this.session.navigator;
@@ -2166,16 +3062,9 @@ class BrowserAdapter {
2166
3062
  }
2167
3063
 
2168
3064
  class CacheObserver {
2169
- constructor() {
2170
- this.selector = "[data-turbo-temporary]";
2171
- this.deprecatedSelector = "[data-turbo-cache=false]";
2172
- this.started = false;
2173
- this.removeTemporaryElements = _event => {
2174
- for (const element of this.temporaryElements) {
2175
- element.remove();
2176
- }
2177
- };
2178
- }
3065
+ selector="[data-turbo-temporary]";
3066
+ deprecatedSelector="[data-turbo-cache=false]";
3067
+ started=false;
2179
3068
  start() {
2180
3069
  if (!this.started) {
2181
3070
  this.started = true;
@@ -2188,6 +3077,11 @@ class CacheObserver {
2188
3077
  removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2189
3078
  }
2190
3079
  }
3080
+ removeTemporaryElements=_event => {
3081
+ for (const element of this.temporaryElements) {
3082
+ element.remove();
3083
+ }
3084
+ };
2191
3085
  get temporaryElements() {
2192
3086
  return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
2193
3087
  }
@@ -2216,41 +3110,40 @@ class FrameRedirector {
2216
3110
  this.formSubmitObserver.stop();
2217
3111
  }
2218
3112
  shouldInterceptLinkClick(element, _location, _event) {
2219
- return this.shouldRedirect(element);
3113
+ return this.#shouldRedirect(element);
2220
3114
  }
2221
3115
  linkClickIntercepted(element, url, event) {
2222
- const frame = this.findFrameElement(element);
3116
+ const frame = this.#findFrameElement(element);
2223
3117
  if (frame) {
2224
3118
  frame.delegate.linkClickIntercepted(element, url, event);
2225
3119
  }
2226
3120
  }
2227
3121
  willSubmitForm(element, submitter) {
2228
- return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
3122
+ return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
2229
3123
  }
2230
3124
  formSubmitted(element, submitter) {
2231
- const frame = this.findFrameElement(element, submitter);
3125
+ const frame = this.#findFrameElement(element, submitter);
2232
3126
  if (frame) {
2233
3127
  frame.delegate.formSubmitted(element, submitter);
2234
3128
  }
2235
3129
  }
2236
- shouldSubmit(form, submitter) {
2237
- var _a;
2238
- const action = getAction(form, submitter);
3130
+ #shouldSubmit(form, submitter) {
3131
+ const action = getAction$1(form, submitter);
2239
3132
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
2240
- const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
2241
- return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
3133
+ const rootLocation = expandURL(meta?.content ?? "/");
3134
+ return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2242
3135
  }
2243
- shouldRedirect(element, submitter) {
3136
+ #shouldRedirect(element, submitter) {
2244
3137
  const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2245
3138
  if (isNavigatable) {
2246
- const frame = this.findFrameElement(element, submitter);
3139
+ const frame = this.#findFrameElement(element, submitter);
2247
3140
  return frame ? frame != element.closest("turbo-frame") : false;
2248
3141
  } else {
2249
3142
  return false;
2250
3143
  }
2251
3144
  }
2252
- findFrameElement(element, submitter) {
2253
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
3145
+ #findFrameElement(element, submitter) {
3146
+ const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
2254
3147
  if (id && id != "_top") {
2255
3148
  const frame = this.element.querySelector(`#${id}:not([disabled])`);
2256
3149
  if (frame instanceof FrameElement) {
@@ -2261,32 +3154,20 @@ class FrameRedirector {
2261
3154
  }
2262
3155
 
2263
3156
  class History {
3157
+ location;
3158
+ restorationIdentifier=uuid();
3159
+ restorationData={};
3160
+ started=false;
3161
+ pageLoaded=false;
3162
+ currentIndex=0;
2264
3163
  constructor(delegate) {
2265
- this.restorationIdentifier = uuid();
2266
- this.restorationData = {};
2267
- this.started = false;
2268
- this.pageLoaded = false;
2269
- this.onPopState = event => {
2270
- if (this.shouldHandlePopState()) {
2271
- const {turbo: turbo} = event.state || {};
2272
- if (turbo) {
2273
- this.location = new URL(window.location.href);
2274
- const {restorationIdentifier: restorationIdentifier} = turbo;
2275
- this.restorationIdentifier = restorationIdentifier;
2276
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
2277
- }
2278
- }
2279
- };
2280
- this.onPageLoad = async _event => {
2281
- await nextMicrotask();
2282
- this.pageLoaded = true;
2283
- };
2284
3164
  this.delegate = delegate;
2285
3165
  }
2286
3166
  start() {
2287
3167
  if (!this.started) {
2288
3168
  addEventListener("popstate", this.onPopState, false);
2289
3169
  addEventListener("load", this.onPageLoad, false);
3170
+ this.currentIndex = history.state?.turbo?.restorationIndex || 0;
2290
3171
  this.started = true;
2291
3172
  this.replace(new URL(window.location.href));
2292
3173
  }
@@ -2305,9 +3186,11 @@ class History {
2305
3186
  this.update(history.replaceState, location, restorationIdentifier);
2306
3187
  }
2307
3188
  update(method, location, restorationIdentifier = uuid()) {
3189
+ if (method === history.pushState) ++this.currentIndex;
2308
3190
  const state = {
2309
3191
  turbo: {
2310
- restorationIdentifier: restorationIdentifier
3192
+ restorationIdentifier: restorationIdentifier,
3193
+ restorationIndex: this.currentIndex
2311
3194
  }
2312
3195
  };
2313
3196
  method.call(history, state, "", location.href);
@@ -2320,12 +3203,14 @@ class History {
2320
3203
  updateRestorationData(additionalData) {
2321
3204
  const {restorationIdentifier: restorationIdentifier} = this;
2322
3205
  const restorationData = this.restorationData[restorationIdentifier];
2323
- this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
3206
+ this.restorationData[restorationIdentifier] = {
3207
+ ...restorationData,
3208
+ ...additionalData
3209
+ };
2324
3210
  }
2325
3211
  assumeControlOfScrollRestoration() {
2326
- var _a;
2327
3212
  if (!this.previousScrollRestoration) {
2328
- this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
3213
+ this.previousScrollRestoration = history.scrollRestoration ?? "auto";
2329
3214
  history.scrollRestoration = "manual";
2330
3215
  }
2331
3216
  }
@@ -2335,6 +3220,23 @@ class History {
2335
3220
  delete this.previousScrollRestoration;
2336
3221
  }
2337
3222
  }
3223
+ onPopState=event => {
3224
+ if (this.shouldHandlePopState()) {
3225
+ const {turbo: turbo} = event.state || {};
3226
+ if (turbo) {
3227
+ this.location = new URL(window.location.href);
3228
+ const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
3229
+ this.restorationIdentifier = restorationIdentifier;
3230
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
3231
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
3232
+ this.currentIndex = restorationIndex;
3233
+ }
3234
+ }
3235
+ };
3236
+ onPageLoad=async _event => {
3237
+ await nextMicrotask();
3238
+ this.pageLoaded = true;
3239
+ };
2338
3240
  shouldHandlePopState() {
2339
3241
  return this.pageIsLoaded();
2340
3242
  }
@@ -2343,24 +3245,154 @@ class History {
2343
3245
  }
2344
3246
  }
2345
3247
 
3248
+ class LinkPrefetchObserver {
3249
+ started=false;
3250
+ #prefetchedLink=null;
3251
+ constructor(delegate, eventTarget) {
3252
+ this.delegate = delegate;
3253
+ this.eventTarget = eventTarget;
3254
+ }
3255
+ start() {
3256
+ if (this.started) return;
3257
+ if (this.eventTarget.readyState === "loading") {
3258
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
3259
+ once: true
3260
+ });
3261
+ } else {
3262
+ this.#enable();
3263
+ }
3264
+ }
3265
+ stop() {
3266
+ if (!this.started) return;
3267
+ this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
3268
+ capture: true,
3269
+ passive: true
3270
+ });
3271
+ this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
3272
+ capture: true,
3273
+ passive: true
3274
+ });
3275
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
3276
+ this.started = false;
3277
+ }
3278
+ #enable=() => {
3279
+ this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
3280
+ capture: true,
3281
+ passive: true
3282
+ });
3283
+ this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
3284
+ capture: true,
3285
+ passive: true
3286
+ });
3287
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
3288
+ this.started = true;
3289
+ };
3290
+ #tryToPrefetchRequest=event => {
3291
+ if (getMetaContent("turbo-prefetch") === "false") return;
3292
+ const target = event.target;
3293
+ const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
3294
+ if (isLink && this.#isPrefetchable(target)) {
3295
+ const link = target;
3296
+ const location = getLocationForLink(link);
3297
+ if (this.delegate.canPrefetchRequestToLocation(link, location)) {
3298
+ this.#prefetchedLink = link;
3299
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
3300
+ prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
3301
+ }
3302
+ }
3303
+ };
3304
+ #cancelRequestIfObsolete=event => {
3305
+ if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
3306
+ };
3307
+ #cancelPrefetchRequest=() => {
3308
+ prefetchCache.clear();
3309
+ this.#prefetchedLink = null;
3310
+ };
3311
+ #tryToUsePrefetchedRequest=event => {
3312
+ if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
3313
+ const cached = prefetchCache.get(event.detail.url.toString());
3314
+ if (cached) {
3315
+ event.detail.fetchRequest = cached;
3316
+ }
3317
+ prefetchCache.clear();
3318
+ }
3319
+ };
3320
+ prepareRequest(request) {
3321
+ const link = request.target;
3322
+ request.headers["X-Sec-Purpose"] = "prefetch";
3323
+ const turboFrame = link.closest("turbo-frame");
3324
+ const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
3325
+ if (turboFrameTarget && turboFrameTarget !== "_top") {
3326
+ request.headers["Turbo-Frame"] = turboFrameTarget;
3327
+ }
3328
+ }
3329
+ requestSucceededWithResponse() {}
3330
+ requestStarted(fetchRequest) {}
3331
+ requestErrored(fetchRequest) {}
3332
+ requestFinished(fetchRequest) {}
3333
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
3334
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
3335
+ get #cacheTtl() {
3336
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
3337
+ }
3338
+ #isPrefetchable(link) {
3339
+ const href = link.getAttribute("href");
3340
+ if (!href) return false;
3341
+ if (unfetchableLink(link)) return false;
3342
+ if (linkToTheSamePage(link)) return false;
3343
+ if (linkOptsOut(link)) return false;
3344
+ if (nonSafeLink(link)) return false;
3345
+ if (eventPrevented(link)) return false;
3346
+ return true;
3347
+ }
3348
+ }
3349
+
3350
+ const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
3351
+
3352
+ const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
3353
+
3354
+ const linkOptsOut = link => {
3355
+ if (link.getAttribute("data-turbo-prefetch") === "false") return true;
3356
+ if (link.getAttribute("data-turbo") === "false") return true;
3357
+ const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
3358
+ if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
3359
+ return false;
3360
+ };
3361
+
3362
+ const nonSafeLink = link => {
3363
+ const turboMethod = link.getAttribute("data-turbo-method");
3364
+ if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
3365
+ if (isUJS(link)) return true;
3366
+ if (link.hasAttribute("data-turbo-confirm")) return true;
3367
+ if (link.hasAttribute("data-turbo-stream")) return true;
3368
+ return false;
3369
+ };
3370
+
3371
+ const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
3372
+
3373
+ const eventPrevented = link => {
3374
+ const event = dispatch("turbo:before-prefetch", {
3375
+ target: link,
3376
+ cancelable: true
3377
+ });
3378
+ return event.defaultPrevented;
3379
+ };
3380
+
2346
3381
  class Navigator {
2347
3382
  constructor(delegate) {
2348
3383
  this.delegate = delegate;
2349
3384
  }
2350
3385
  proposeVisit(location, options = {}) {
2351
3386
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2352
- if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2353
- this.delegate.visitProposedToLocation(location, options);
2354
- } else {
2355
- window.location.href = location.toString();
2356
- }
3387
+ this.delegate.visitProposedToLocation(location, options);
2357
3388
  }
2358
3389
  }
2359
3390
  startVisit(locatable, restorationIdentifier, options = {}) {
2360
3391
  this.stop();
2361
- this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2362
- referrer: this.location
2363
- }, options));
3392
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
3393
+ referrer: this.location,
3394
+ ...options
3395
+ });
2364
3396
  this.currentVisit.start();
2365
3397
  }
2366
3398
  submitForm(form, submitter) {
@@ -2384,6 +3416,9 @@ class Navigator {
2384
3416
  get view() {
2385
3417
  return this.delegate.view;
2386
3418
  }
3419
+ get rootLocation() {
3420
+ return this.view.snapshot.rootLocation;
3421
+ }
2387
3422
  get history() {
2388
3423
  return this.delegate.history;
2389
3424
  }
@@ -2401,7 +3436,7 @@ class Navigator {
2401
3436
  this.view.clearSnapshotCache();
2402
3437
  }
2403
3438
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2404
- const action = this.getActionForFormSubmission(formSubmission);
3439
+ const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
2405
3440
  const visitOptions = {
2406
3441
  action: action,
2407
3442
  shouldCacheSnapshot: shouldCacheSnapshot,
@@ -2424,7 +3459,9 @@ class Navigator {
2424
3459
  } else {
2425
3460
  await this.view.renderPage(snapshot, false, true, this.currentVisit);
2426
3461
  }
2427
- this.view.scrollToTop();
3462
+ if (!snapshot.shouldPreserveScrollPosition) {
3463
+ this.view.scrollToTop();
3464
+ }
2428
3465
  this.view.clearSnapshotCache();
2429
3466
  }
2430
3467
  }
@@ -2441,6 +3478,7 @@ class Navigator {
2441
3478
  }
2442
3479
  visitCompleted(visit) {
2443
3480
  this.delegate.visitCompleted(visit);
3481
+ delete this.currentVisit;
2444
3482
  }
2445
3483
  locationWithActionIsSamePage(location, action) {
2446
3484
  const anchor = getAnchor(location);
@@ -2457,35 +3495,27 @@ class Navigator {
2457
3495
  get restorationIdentifier() {
2458
3496
  return this.history.restorationIdentifier;
2459
3497
  }
2460
- getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
- return getVisitAction(submitter, formElement) || "advance";
3498
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
3499
+ const {submitter: submitter, formElement: formElement} = formSubmission;
3500
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
3501
+ }
3502
+ #getDefaultAction(fetchResponse) {
3503
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
3504
+ return sameLocationRedirect ? "replace" : "advance";
2462
3505
  }
2463
3506
  }
2464
3507
 
2465
- var PageStage;
2466
-
2467
- (function(PageStage) {
2468
- PageStage[PageStage["initial"] = 0] = "initial";
2469
- PageStage[PageStage["loading"] = 1] = "loading";
2470
- PageStage[PageStage["interactive"] = 2] = "interactive";
2471
- PageStage[PageStage["complete"] = 3] = "complete";
2472
- })(PageStage || (PageStage = {}));
3508
+ const PageStage = {
3509
+ initial: 0,
3510
+ loading: 1,
3511
+ interactive: 2,
3512
+ complete: 3
3513
+ };
2473
3514
 
2474
3515
  class PageObserver {
3516
+ stage=PageStage.initial;
3517
+ started=false;
2475
3518
  constructor(delegate) {
2476
- this.stage = PageStage.initial;
2477
- this.started = false;
2478
- this.interpretReadyState = () => {
2479
- const {readyState: readyState} = this;
2480
- if (readyState == "interactive") {
2481
- this.pageIsInteractive();
2482
- } else if (readyState == "complete") {
2483
- this.pageIsComplete();
2484
- }
2485
- };
2486
- this.pageWillUnload = () => {
2487
- this.delegate.pageWillUnload();
2488
- };
2489
3519
  this.delegate = delegate;
2490
3520
  }
2491
3521
  start() {
@@ -2505,6 +3535,14 @@ class PageObserver {
2505
3535
  this.started = false;
2506
3536
  }
2507
3537
  }
3538
+ interpretReadyState=() => {
3539
+ const {readyState: readyState} = this;
3540
+ if (readyState == "interactive") {
3541
+ this.pageIsInteractive();
3542
+ } else if (readyState == "complete") {
3543
+ this.pageIsComplete();
3544
+ }
3545
+ };
2508
3546
  pageIsInteractive() {
2509
3547
  if (this.stage == PageStage.loading) {
2510
3548
  this.stage = PageStage.interactive;
@@ -2518,20 +3556,17 @@ class PageObserver {
2518
3556
  this.delegate.pageLoaded();
2519
3557
  }
2520
3558
  }
3559
+ pageWillUnload=() => {
3560
+ this.delegate.pageWillUnload();
3561
+ };
2521
3562
  get readyState() {
2522
3563
  return document.readyState;
2523
3564
  }
2524
3565
  }
2525
3566
 
2526
3567
  class ScrollObserver {
3568
+ started=false;
2527
3569
  constructor(delegate) {
2528
- this.started = false;
2529
- this.onScroll = () => {
2530
- this.updatePosition({
2531
- x: window.pageXOffset,
2532
- y: window.pageYOffset
2533
- });
2534
- };
2535
3570
  this.delegate = delegate;
2536
3571
  }
2537
3572
  start() {
@@ -2547,6 +3582,12 @@ class ScrollObserver {
2547
3582
  this.started = false;
2548
3583
  }
2549
3584
  }
3585
+ onScroll=() => {
3586
+ this.updatePosition({
3587
+ x: window.pageXOffset,
3588
+ y: window.pageYOffset
3589
+ });
3590
+ };
2550
3591
  updatePosition(position) {
2551
3592
  this.delegate.scrollPositionChanged(position);
2552
3593
  }
@@ -2554,7 +3595,13 @@ class ScrollObserver {
2554
3595
 
2555
3596
  class StreamMessageRenderer {
2556
3597
  render({fragment: fragment}) {
2557
- Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
3598
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
3599
+ withAutofocusFromFragment(fragment, (() => {
3600
+ withPreservedFocus((() => {
3601
+ document.documentElement.appendChild(fragment);
3602
+ }));
3603
+ }));
3604
+ }));
2558
3605
  }
2559
3606
  enteringBardo(currentPermanentElement, newPermanentElement) {
2560
3607
  newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
@@ -2577,33 +3624,67 @@ function getPermanentElementMapForFragment(fragment) {
2577
3624
  return permanentElementMap;
2578
3625
  }
2579
3626
 
3627
+ async function withAutofocusFromFragment(fragment, callback) {
3628
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
3629
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
3630
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
3631
+ let willAutofocusId = null;
3632
+ if (elementWithAutofocus) {
3633
+ if (elementWithAutofocus.id) {
3634
+ willAutofocusId = elementWithAutofocus.id;
3635
+ } else {
3636
+ willAutofocusId = generatedID;
3637
+ }
3638
+ elementWithAutofocus.id = willAutofocusId;
3639
+ }
3640
+ callback();
3641
+ await nextRepaint();
3642
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
3643
+ if (hasNoActiveElement && willAutofocusId) {
3644
+ const elementToAutofocus = document.getElementById(willAutofocusId);
3645
+ if (elementIsFocusable(elementToAutofocus)) {
3646
+ elementToAutofocus.focus();
3647
+ }
3648
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
3649
+ elementToAutofocus.removeAttribute("id");
3650
+ }
3651
+ }
3652
+ }
3653
+
3654
+ async function withPreservedFocus(callback) {
3655
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
3656
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
3657
+ if (restoreFocusTo) {
3658
+ const elementToFocus = document.getElementById(restoreFocusTo);
3659
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
3660
+ elementToFocus.focus();
3661
+ }
3662
+ }
3663
+ }
3664
+
3665
+ function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
3666
+ for (const streamElement of nodeListOfStreamElements) {
3667
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
3668
+ if (elementWithAutofocus) return elementWithAutofocus;
3669
+ }
3670
+ return null;
3671
+ }
3672
+
2580
3673
  class StreamObserver {
3674
+ sources=new Set;
3675
+ #started=false;
2581
3676
  constructor(delegate) {
2582
- this.sources = new Set;
2583
- this.started = false;
2584
- this.inspectFetchResponse = event => {
2585
- const response = fetchResponseFromEvent(event);
2586
- if (response && fetchResponseIsStream(response)) {
2587
- event.preventDefault();
2588
- this.receiveMessageResponse(response);
2589
- }
2590
- };
2591
- this.receiveMessageEvent = event => {
2592
- if (this.started && typeof event.data == "string") {
2593
- this.receiveMessageHTML(event.data);
2594
- }
2595
- };
2596
3677
  this.delegate = delegate;
2597
3678
  }
2598
3679
  start() {
2599
- if (!this.started) {
2600
- this.started = true;
3680
+ if (!this.#started) {
3681
+ this.#started = true;
2601
3682
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2602
3683
  }
2603
3684
  }
2604
3685
  stop() {
2605
- if (this.started) {
2606
- this.started = false;
3686
+ if (this.#started) {
3687
+ this.#started = false;
2607
3688
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2608
3689
  }
2609
3690
  }
@@ -2622,6 +3703,18 @@ class StreamObserver {
2622
3703
  streamSourceIsConnected(source) {
2623
3704
  return this.sources.has(source);
2624
3705
  }
3706
+ inspectFetchResponse=event => {
3707
+ const response = fetchResponseFromEvent(event);
3708
+ if (response && fetchResponseIsStream(response)) {
3709
+ event.preventDefault();
3710
+ this.receiveMessageResponse(response);
3711
+ }
3712
+ };
3713
+ receiveMessageEvent=event => {
3714
+ if (this.#started && typeof event.data == "string") {
3715
+ this.receiveMessageHTML(event.data);
3716
+ }
3717
+ };
2625
3718
  async receiveMessageResponse(response) {
2626
3719
  const html = await response.responseHTML;
2627
3720
  if (html) {
@@ -2634,16 +3727,14 @@ class StreamObserver {
2634
3727
  }
2635
3728
 
2636
3729
  function fetchResponseFromEvent(event) {
2637
- var _a;
2638
- const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
3730
+ const fetchResponse = event.detail?.fetchResponse;
2639
3731
  if (fetchResponse instanceof FetchResponse) {
2640
3732
  return fetchResponse;
2641
3733
  }
2642
3734
  }
2643
3735
 
2644
3736
  function fetchResponseIsStream(response) {
2645
- var _a;
2646
- const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
3737
+ const contentType = response.contentType ?? "";
2647
3738
  return contentType.startsWith(StreamMessage.contentType);
2648
3739
  }
2649
3740
 
@@ -2702,6 +3793,7 @@ class PageRenderer extends Renderer {
2702
3793
  }
2703
3794
  }
2704
3795
  async prepareToRender() {
3796
+ this.#setLanguage();
2705
3797
  await this.mergeHead();
2706
3798
  }
2707
3799
  async render() {
@@ -2724,12 +3816,24 @@ class PageRenderer extends Renderer {
2724
3816
  get newElement() {
2725
3817
  return this.newSnapshot.element;
2726
3818
  }
3819
+ #setLanguage() {
3820
+ const {documentElement: documentElement} = this.currentSnapshot;
3821
+ const {lang: lang} = this.newSnapshot;
3822
+ if (lang) {
3823
+ documentElement.setAttribute("lang", lang);
3824
+ } else {
3825
+ documentElement.removeAttribute("lang");
3826
+ }
3827
+ }
2727
3828
  async mergeHead() {
2728
3829
  const mergedHeadElements = this.mergeProvisionalElements();
2729
3830
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2730
3831
  this.copyNewHeadScriptElements();
2731
3832
  await mergedHeadElements;
2732
3833
  await newStylesheetElements;
3834
+ if (this.willRender) {
3835
+ this.removeUnusedDynamicStylesheetElements();
3836
+ }
2733
3837
  }
2734
3838
  async replaceBody() {
2735
3839
  await this.preservingPermanentElements((async () => {
@@ -2753,6 +3857,11 @@ class PageRenderer extends Renderer {
2753
3857
  document.head.appendChild(activateScriptElement(element));
2754
3858
  }
2755
3859
  }
3860
+ removeUnusedDynamicStylesheetElements() {
3861
+ for (const element of this.unusedDynamicStylesheetElements) {
3862
+ document.head.removeChild(element);
3863
+ }
3864
+ }
2756
3865
  async mergeProvisionalElements() {
2757
3866
  const newHeadElements = [ ...this.newHeadProvisionalElements ];
2758
3867
  for (const element of this.currentHeadProvisionalElements) {
@@ -2805,6 +3914,12 @@ class PageRenderer extends Renderer {
2805
3914
  async assignNewBody() {
2806
3915
  await this.renderElement(this.currentElement, this.newElement);
2807
3916
  }
3917
+ get unusedDynamicStylesheetElements() {
3918
+ return this.oldHeadStylesheetElements.filter((element => element.getAttribute("data-turbo-track") === "dynamic"));
3919
+ }
3920
+ get oldHeadStylesheetElements() {
3921
+ return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot);
3922
+ }
2808
3923
  get newHeadStylesheetElements() {
2809
3924
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2810
3925
  }
@@ -2822,10 +3937,42 @@ class PageRenderer extends Renderer {
2822
3937
  }
2823
3938
  }
2824
3939
 
3940
+ class MorphingPageRenderer extends PageRenderer {
3941
+ static renderElement(currentElement, newElement) {
3942
+ morphElements(currentElement, newElement, {
3943
+ callbacks: {
3944
+ beforeNodeMorphed: element => !canRefreshFrame(element)
3945
+ }
3946
+ });
3947
+ for (const frame of currentElement.querySelectorAll("turbo-frame")) {
3948
+ if (canRefreshFrame(frame)) frame.reload();
3949
+ }
3950
+ dispatch("turbo:morph", {
3951
+ detail: {
3952
+ currentElement: currentElement,
3953
+ newElement: newElement
3954
+ }
3955
+ });
3956
+ }
3957
+ async preservingPermanentElements(callback) {
3958
+ return await callback();
3959
+ }
3960
+ get renderMethod() {
3961
+ return "morph";
3962
+ }
3963
+ get shouldAutofocus() {
3964
+ return false;
3965
+ }
3966
+ }
3967
+
3968
+ function canRefreshFrame(frame) {
3969
+ return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
3970
+ }
3971
+
2825
3972
  class SnapshotCache {
3973
+ keys=[];
3974
+ snapshots={};
2826
3975
  constructor(size) {
2827
- this.keys = [];
2828
- this.snapshots = {};
2829
3976
  this.size = size;
2830
3977
  }
2831
3978
  has(location) {
@@ -2867,24 +4014,26 @@ class SnapshotCache {
2867
4014
  }
2868
4015
 
2869
4016
  class PageView extends View {
2870
- constructor() {
2871
- super(...arguments);
2872
- this.snapshotCache = new SnapshotCache(10);
2873
- this.lastRenderedLocation = new URL(location.href);
2874
- this.forceReloaded = false;
4017
+ snapshotCache=new SnapshotCache(10);
4018
+ lastRenderedLocation=new URL(location.href);
4019
+ forceReloaded=false;
4020
+ shouldTransitionTo(newSnapshot) {
4021
+ return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
2875
4022
  }
2876
4023
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
2877
- const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
4024
+ const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
4025
+ const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
4026
+ const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
2878
4027
  if (!renderer.shouldRender) {
2879
4028
  this.forceReloaded = true;
2880
4029
  } else {
2881
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
4030
+ visit?.changeHistory();
2882
4031
  }
2883
4032
  return this.render(renderer);
2884
4033
  }
2885
4034
  renderError(snapshot, visit) {
2886
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2887
- const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
4035
+ visit?.changeHistory();
4036
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2888
4037
  return this.render(renderer);
2889
4038
  }
2890
4039
  clearSnapshotCache() {
@@ -2903,31 +4052,38 @@ class PageView extends View {
2903
4052
  getCachedSnapshotForLocation(location) {
2904
4053
  return this.snapshotCache.get(location);
2905
4054
  }
4055
+ isPageRefresh(visit) {
4056
+ return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
4057
+ }
4058
+ shouldPreserveScrollPosition(visit) {
4059
+ return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
4060
+ }
2906
4061
  get snapshot() {
2907
4062
  return PageSnapshot.fromElement(this.element);
2908
4063
  }
2909
4064
  }
2910
4065
 
2911
4066
  class Preloader {
2912
- constructor(delegate) {
2913
- this.selector = "a[data-turbo-preload]";
4067
+ selector="a[data-turbo-preload]";
4068
+ constructor(delegate, snapshotCache) {
2914
4069
  this.delegate = delegate;
2915
- }
2916
- get snapshotCache() {
2917
- return this.delegate.navigator.view.snapshotCache;
4070
+ this.snapshotCache = snapshotCache;
2918
4071
  }
2919
4072
  start() {
2920
4073
  if (document.readyState === "loading") {
2921
- return document.addEventListener("DOMContentLoaded", (() => {
2922
- this.preloadOnLoadLinksForView(document.body);
2923
- }));
4074
+ document.addEventListener("DOMContentLoaded", this.#preloadAll);
2924
4075
  } else {
2925
4076
  this.preloadOnLoadLinksForView(document.body);
2926
4077
  }
2927
4078
  }
4079
+ stop() {
4080
+ document.removeEventListener("DOMContentLoaded", this.#preloadAll);
4081
+ }
2928
4082
  preloadOnLoadLinksForView(element) {
2929
4083
  for (const link of element.querySelectorAll(this.selector)) {
2930
- this.preloadURL(link);
4084
+ if (this.delegate.shouldPreloadLink(link)) {
4085
+ this.preloadURL(link);
4086
+ }
2931
4087
  }
2932
4088
  }
2933
4089
  async preloadURL(link) {
@@ -2935,46 +4091,80 @@ class Preloader {
2935
4091
  if (this.snapshotCache.has(location)) {
2936
4092
  return;
2937
4093
  }
4094
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
4095
+ await fetchRequest.perform();
4096
+ }
4097
+ prepareRequest(fetchRequest) {
4098
+ fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
4099
+ }
4100
+ async requestSucceededWithResponse(fetchRequest, fetchResponse) {
2938
4101
  try {
2939
- const response = await fetch(location.toString(), {
2940
- headers: {
2941
- "VND.PREFETCH": "true",
2942
- Accept: "text/html"
2943
- }
2944
- });
2945
- const responseText = await response.text();
2946
- const snapshot = PageSnapshot.fromHTMLString(responseText);
2947
- this.snapshotCache.put(location, snapshot);
4102
+ const responseHTML = await fetchResponse.responseHTML;
4103
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
4104
+ this.snapshotCache.put(fetchRequest.url, snapshot);
2948
4105
  } catch (_) {}
2949
4106
  }
4107
+ requestStarted(fetchRequest) {}
4108
+ requestErrored(fetchRequest) {}
4109
+ requestFinished(fetchRequest) {}
4110
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
4111
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
4112
+ #preloadAll=() => {
4113
+ this.preloadOnLoadLinksForView(document.body);
4114
+ };
4115
+ }
4116
+
4117
+ class Cache {
4118
+ constructor(session) {
4119
+ this.session = session;
4120
+ }
4121
+ clear() {
4122
+ this.session.clearCache();
4123
+ }
4124
+ resetCacheControl() {
4125
+ this.#setCacheControl("");
4126
+ }
4127
+ exemptPageFromCache() {
4128
+ this.#setCacheControl("no-cache");
4129
+ }
4130
+ exemptPageFromPreview() {
4131
+ this.#setCacheControl("no-preview");
4132
+ }
4133
+ #setCacheControl(value) {
4134
+ setMetaContent("turbo-cache-control", value);
4135
+ }
2950
4136
  }
2951
4137
 
2952
4138
  class Session {
2953
- constructor() {
2954
- this.navigator = new Navigator(this);
2955
- this.history = new History(this);
2956
- this.preloader = new Preloader(this);
2957
- this.view = new PageView(this, document.documentElement);
2958
- this.adapter = new BrowserAdapter(this);
2959
- this.pageObserver = new PageObserver(this);
2960
- this.cacheObserver = new CacheObserver;
2961
- this.linkClickObserver = new LinkClickObserver(this, window);
2962
- this.formSubmitObserver = new FormSubmitObserver(this, document);
2963
- this.scrollObserver = new ScrollObserver(this);
2964
- this.streamObserver = new StreamObserver(this);
2965
- this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2966
- this.frameRedirector = new FrameRedirector(this, document.documentElement);
2967
- this.streamMessageRenderer = new StreamMessageRenderer;
2968
- this.drive = true;
2969
- this.enabled = true;
2970
- this.progressBarDelay = 500;
2971
- this.started = false;
2972
- this.formMode = "on";
4139
+ navigator=new Navigator(this);
4140
+ history=new History(this);
4141
+ view=new PageView(this, document.documentElement);
4142
+ adapter=new BrowserAdapter(this);
4143
+ pageObserver=new PageObserver(this);
4144
+ cacheObserver=new CacheObserver;
4145
+ linkPrefetchObserver=new LinkPrefetchObserver(this, document);
4146
+ linkClickObserver=new LinkClickObserver(this, window);
4147
+ formSubmitObserver=new FormSubmitObserver(this, document);
4148
+ scrollObserver=new ScrollObserver(this);
4149
+ streamObserver=new StreamObserver(this);
4150
+ formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
4151
+ frameRedirector=new FrameRedirector(this, document.documentElement);
4152
+ streamMessageRenderer=new StreamMessageRenderer;
4153
+ cache=new Cache(this);
4154
+ enabled=true;
4155
+ started=false;
4156
+ #pageRefreshDebouncePeriod=150;
4157
+ constructor(recentRequests) {
4158
+ this.recentRequests = recentRequests;
4159
+ this.preloader = new Preloader(this, this.view.snapshotCache);
4160
+ this.debouncedRefresh = this.refresh;
4161
+ this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
2973
4162
  }
2974
4163
  start() {
2975
4164
  if (!this.started) {
2976
4165
  this.pageObserver.start();
2977
4166
  this.cacheObserver.start();
4167
+ this.linkPrefetchObserver.start();
2978
4168
  this.formLinkClickObserver.start();
2979
4169
  this.linkClickObserver.start();
2980
4170
  this.formSubmitObserver.start();
@@ -2994,6 +4184,7 @@ class Session {
2994
4184
  if (this.started) {
2995
4185
  this.pageObserver.stop();
2996
4186
  this.cacheObserver.stop();
4187
+ this.linkPrefetchObserver.stop();
2997
4188
  this.formLinkClickObserver.stop();
2998
4189
  this.linkClickObserver.stop();
2999
4190
  this.formSubmitObserver.stop();
@@ -3001,6 +4192,7 @@ class Session {
3001
4192
  this.streamObserver.stop();
3002
4193
  this.frameRedirector.stop();
3003
4194
  this.history.stop();
4195
+ this.preloader.stop();
3004
4196
  this.started = false;
3005
4197
  }
3006
4198
  }
@@ -3010,12 +4202,22 @@ class Session {
3010
4202
  visit(location, options = {}) {
3011
4203
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
3012
4204
  if (frameElement instanceof FrameElement) {
4205
+ const action = options.action || getVisitAction(frameElement);
4206
+ frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
3013
4207
  frameElement.src = location.toString();
3014
- frameElement.loaded;
3015
4208
  } else {
3016
4209
  this.navigator.proposeVisit(expandURL(location), options);
3017
4210
  }
3018
4211
  }
4212
+ refresh(url, requestId) {
4213
+ const isRecentRequest = requestId && this.recentRequests.has(requestId);
4214
+ if (!isRecentRequest && !this.navigator.currentVisit) {
4215
+ this.visit(url, {
4216
+ action: "replace",
4217
+ shouldCacheSnapshot: false
4218
+ });
4219
+ }
4220
+ }
3019
4221
  connectStreamSource(source) {
3020
4222
  this.streamObserver.connectStreamSource(source);
3021
4223
  }
@@ -3029,10 +4231,26 @@ class Session {
3029
4231
  this.view.clearSnapshotCache();
3030
4232
  }
3031
4233
  setProgressBarDelay(delay) {
4234
+ console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`");
3032
4235
  this.progressBarDelay = delay;
3033
4236
  }
3034
- setFormMode(mode) {
3035
- this.formMode = mode;
4237
+ set progressBarDelay(delay) {
4238
+ config.drive.progressBarDelay = delay;
4239
+ }
4240
+ get progressBarDelay() {
4241
+ return config.drive.progressBarDelay;
4242
+ }
4243
+ set drive(value) {
4244
+ config.drive.enabled = value;
4245
+ }
4246
+ get drive() {
4247
+ return config.drive.enabled;
4248
+ }
4249
+ set formMode(value) {
4250
+ config.forms.mode = value;
4251
+ }
4252
+ get formMode() {
4253
+ return config.forms.mode;
3036
4254
  }
3037
4255
  get location() {
3038
4256
  return this.history.location;
@@ -3040,11 +4258,31 @@ class Session {
3040
4258
  get restorationIdentifier() {
3041
4259
  return this.history.restorationIdentifier;
3042
4260
  }
3043
- historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
4261
+ get pageRefreshDebouncePeriod() {
4262
+ return this.#pageRefreshDebouncePeriod;
4263
+ }
4264
+ set pageRefreshDebouncePeriod(value) {
4265
+ this.refresh = debounce(this.debouncedRefresh.bind(this), value);
4266
+ this.#pageRefreshDebouncePeriod = value;
4267
+ }
4268
+ shouldPreloadLink(element) {
4269
+ const isUnsafe = element.hasAttribute("data-turbo-method");
4270
+ const isStream = element.hasAttribute("data-turbo-stream");
4271
+ const frameTarget = element.getAttribute("data-turbo-frame");
4272
+ const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
4273
+ if (isUnsafe || isStream || frame instanceof FrameElement) {
4274
+ return false;
4275
+ } else {
4276
+ const location = new URL(element.href);
4277
+ return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
4278
+ }
4279
+ }
4280
+ historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
3044
4281
  if (this.enabled) {
3045
4282
  this.navigator.startVisit(location, restorationIdentifier, {
3046
4283
  action: "restore",
3047
- historyChanged: true
4284
+ historyChanged: true,
4285
+ direction: direction
3048
4286
  });
3049
4287
  } else {
3050
4288
  this.adapter.pageInvalidated({
@@ -3061,6 +4299,9 @@ class Session {
3061
4299
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3062
4300
  }
3063
4301
  submittedFormLinkToLocation() {}
4302
+ canPrefetchRequestToLocation(link, location) {
4303
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4304
+ }
3064
4305
  willFollowLinkToLocation(link, location, event) {
3065
4306
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
3066
4307
  }
@@ -3082,6 +4323,7 @@ class Session {
3082
4323
  visitStarted(visit) {
3083
4324
  if (!visit.acceptsStreamResponse) {
3084
4325
  markAsBusy(document.documentElement);
4326
+ this.view.markVisitDirection(visit.direction);
3085
4327
  }
3086
4328
  extendURLWithDeprecatedProperties(visit.location);
3087
4329
  if (!visit.silent) {
@@ -3089,6 +4331,7 @@ class Session {
3089
4331
  }
3090
4332
  }
3091
4333
  visitCompleted(visit) {
4334
+ this.view.unmarkVisitDirection();
3092
4335
  clearBusyState(document.documentElement);
3093
4336
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3094
4337
  }
@@ -3099,7 +4342,7 @@ class Session {
3099
4342
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
3100
4343
  }
3101
4344
  willSubmitForm(form, submitter) {
3102
- const action = getAction(form, submitter);
4345
+ const action = getAction$1(form, submitter);
3103
4346
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3104
4347
  }
3105
4348
  formSubmitted(form, submitter) {
@@ -3119,8 +4362,7 @@ class Session {
3119
4362
  this.renderStreamMessage(message);
3120
4363
  }
3121
4364
  viewWillCacheSnapshot() {
3122
- var _a;
3123
- if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
4365
+ if (!this.navigator.currentVisit?.silent) {
3124
4366
  this.notifyApplicationBeforeCachingSnapshot();
3125
4367
  }
3126
4368
  }
@@ -3132,9 +4374,9 @@ class Session {
3132
4374
  }
3133
4375
  return !defaultPrevented;
3134
4376
  }
3135
- viewRenderedSnapshot(_snapshot, _isPreview) {
4377
+ viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
3136
4378
  this.view.lastRenderedLocation = this.history.location;
3137
- this.notifyApplicationAfterRender();
4379
+ this.notifyApplicationAfterRender(renderMethod);
3138
4380
  }
3139
4381
  preloadOnLoadLinksForView(element) {
3140
4382
  this.preloader.preloadOnLoadLinksForView(element);
@@ -3187,14 +4429,19 @@ class Session {
3187
4429
  }
3188
4430
  notifyApplicationBeforeRender(newBody, options) {
3189
4431
  return dispatch("turbo:before-render", {
3190
- detail: Object.assign({
3191
- newBody: newBody
3192
- }, options),
4432
+ detail: {
4433
+ newBody: newBody,
4434
+ ...options
4435
+ },
3193
4436
  cancelable: true
3194
4437
  });
3195
4438
  }
3196
- notifyApplicationAfterRender() {
3197
- return dispatch("turbo:render");
4439
+ notifyApplicationAfterRender(renderMethod) {
4440
+ return dispatch("turbo:render", {
4441
+ detail: {
4442
+ renderMethod: renderMethod
4443
+ }
4444
+ });
3198
4445
  }
3199
4446
  notifyApplicationAfterPageLoad(timing = {}) {
3200
4447
  return dispatch("turbo:load", {
@@ -3225,11 +4472,11 @@ class Session {
3225
4472
  });
3226
4473
  }
3227
4474
  submissionIsNavigatable(form, submitter) {
3228
- if (this.formMode == "off") {
4475
+ if (config.forms.mode == "off") {
3229
4476
  return false;
3230
4477
  } else {
3231
4478
  const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3232
- if (this.formMode == "optin") {
4479
+ if (config.forms.mode == "optin") {
3233
4480
  return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3234
4481
  } else {
3235
4482
  return submitterIsNavigatable && this.elementIsNavigatable(form);
@@ -3239,7 +4486,7 @@ class Session {
3239
4486
  elementIsNavigatable(element) {
3240
4487
  const container = findClosestRecursively(element, "[data-turbo]");
3241
4488
  const withinFrame = findClosestRecursively(element, "turbo-frame");
3242
- if (this.drive || withinFrame) {
4489
+ if (config.drive.enabled || withinFrame) {
3243
4490
  if (container) {
3244
4491
  return container.getAttribute("data-turbo") != "false";
3245
4492
  } else {
@@ -3273,67 +4520,9 @@ const deprecatedLocationPropertyDescriptors = {
3273
4520
  }
3274
4521
  };
3275
4522
 
3276
- class Cache {
3277
- constructor(session) {
3278
- this.session = session;
3279
- }
3280
- clear() {
3281
- this.session.clearCache();
3282
- }
3283
- resetCacheControl() {
3284
- this.setCacheControl("");
3285
- }
3286
- exemptPageFromCache() {
3287
- this.setCacheControl("no-cache");
3288
- }
3289
- exemptPageFromPreview() {
3290
- this.setCacheControl("no-preview");
3291
- }
3292
- setCacheControl(value) {
3293
- setMetaContent("turbo-cache-control", value);
3294
- }
3295
- }
3296
-
3297
- const StreamActions = {
3298
- after() {
3299
- this.targetElements.forEach((e => {
3300
- var _a;
3301
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3302
- }));
3303
- },
3304
- append() {
3305
- this.removeDuplicateTargetChildren();
3306
- this.targetElements.forEach((e => e.append(this.templateContent)));
3307
- },
3308
- before() {
3309
- this.targetElements.forEach((e => {
3310
- var _a;
3311
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3312
- }));
3313
- },
3314
- prepend() {
3315
- this.removeDuplicateTargetChildren();
3316
- this.targetElements.forEach((e => e.prepend(this.templateContent)));
3317
- },
3318
- remove() {
3319
- this.targetElements.forEach((e => e.remove()));
3320
- },
3321
- replace() {
3322
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3323
- },
3324
- update() {
3325
- this.targetElements.forEach((targetElement => {
3326
- targetElement.innerHTML = "";
3327
- targetElement.append(this.templateContent);
3328
- }));
3329
- }
3330
- };
3331
-
3332
- const session = new Session;
3333
-
3334
- const cache = new Cache(session);
4523
+ const session = new Session(recentRequests);
3335
4524
 
3336
- const {navigator: navigator$1} = session;
4525
+ const {cache: cache, navigator: navigator$1} = session;
3337
4526
 
3338
4527
  function start() {
3339
4528
  session.start();
@@ -3365,15 +4554,18 @@ function clearCache() {
3365
4554
  }
3366
4555
 
3367
4556
  function setProgressBarDelay(delay) {
3368
- session.setProgressBarDelay(delay);
4557
+ console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
4558
+ config.drive.progressBarDelay = delay;
3369
4559
  }
3370
4560
 
3371
4561
  function setConfirmMethod(confirmMethod) {
3372
- FormSubmission.confirmMethod = confirmMethod;
4562
+ console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
4563
+ config.forms.confirm = confirmMethod;
3373
4564
  }
3374
4565
 
3375
4566
  function setFormMode(mode) {
3376
- session.setFormMode(mode);
4567
+ console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
4568
+ config.forms.mode = mode;
3377
4569
  }
3378
4570
 
3379
4571
  var Turbo = Object.freeze({
@@ -3384,6 +4576,8 @@ var Turbo = Object.freeze({
3384
4576
  PageRenderer: PageRenderer,
3385
4577
  PageSnapshot: PageSnapshot,
3386
4578
  FrameRenderer: FrameRenderer,
4579
+ fetch: fetchWithTurboHeaders,
4580
+ config: config,
3387
4581
  start: start,
3388
4582
  registerAdapter: registerAdapter,
3389
4583
  visit: visit,
@@ -3393,28 +4587,21 @@ var Turbo = Object.freeze({
3393
4587
  clearCache: clearCache,
3394
4588
  setProgressBarDelay: setProgressBarDelay,
3395
4589
  setConfirmMethod: setConfirmMethod,
3396
- setFormMode: setFormMode,
3397
- StreamActions: StreamActions
4590
+ setFormMode: setFormMode
3398
4591
  });
3399
4592
 
3400
4593
  class TurboFrameMissingError extends Error {}
3401
4594
 
3402
4595
  class FrameController {
4596
+ fetchResponseLoaded=_fetchResponse => Promise.resolve();
4597
+ #currentFetchRequest=null;
4598
+ #resolveVisitPromise=() => {};
4599
+ #connected=false;
4600
+ #hasBeenLoaded=false;
4601
+ #ignoredAttributes=new Set;
4602
+ #shouldMorphFrame=false;
4603
+ action=null;
3403
4604
  constructor(element) {
3404
- this.fetchResponseLoaded = _fetchResponse => {};
3405
- this.currentFetchRequest = null;
3406
- this.resolveVisitPromise = () => {};
3407
- this.connected = false;
3408
- this.hasBeenLoaded = false;
3409
- this.ignoredAttributes = new Set;
3410
- this.action = null;
3411
- this.visitCachedSnapshot = ({element: element}) => {
3412
- const frame = element.querySelector("#" + this.element.id);
3413
- if (frame && this.previousFrameElement) {
3414
- frame.replaceChildren(...this.previousFrameElement.children);
3415
- }
3416
- delete this.previousFrameElement;
3417
- };
3418
4605
  this.element = element;
3419
4606
  this.view = new FrameView(this, this.element);
3420
4607
  this.appearanceObserver = new AppearanceObserver(this, this.element);
@@ -3424,12 +4611,12 @@ class FrameController {
3424
4611
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3425
4612
  }
3426
4613
  connect() {
3427
- if (!this.connected) {
3428
- this.connected = true;
4614
+ if (!this.#connected) {
4615
+ this.#connected = true;
3429
4616
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3430
4617
  this.appearanceObserver.start();
3431
4618
  } else {
3432
- this.loadSourceURL();
4619
+ this.#loadSourceURL();
3433
4620
  }
3434
4621
  this.formLinkClickObserver.start();
3435
4622
  this.linkInterceptor.start();
@@ -3437,8 +4624,8 @@ class FrameController {
3437
4624
  }
3438
4625
  }
3439
4626
  disconnect() {
3440
- if (this.connected) {
3441
- this.connected = false;
4627
+ if (this.#connected) {
4628
+ this.#connected = false;
3442
4629
  this.appearanceObserver.stop();
3443
4630
  this.formLinkClickObserver.stop();
3444
4631
  this.linkInterceptor.stop();
@@ -3447,45 +4634,40 @@ class FrameController {
3447
4634
  }
3448
4635
  disabledChanged() {
3449
4636
  if (this.loadingStyle == FrameLoadingStyle.eager) {
3450
- this.loadSourceURL();
4637
+ this.#loadSourceURL();
3451
4638
  }
3452
4639
  }
3453
4640
  sourceURLChanged() {
3454
- if (this.isIgnoringChangesTo("src")) return;
4641
+ if (this.#isIgnoringChangesTo("src")) return;
3455
4642
  if (this.element.isConnected) {
3456
4643
  this.complete = false;
3457
4644
  }
3458
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3459
- this.loadSourceURL();
4645
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
4646
+ this.#loadSourceURL();
3460
4647
  }
3461
4648
  }
3462
4649
  sourceURLReloaded() {
3463
- const {src: src} = this.element;
3464
- this.ignoringChangesToAttribute("complete", (() => {
3465
- this.element.removeAttribute("complete");
3466
- }));
4650
+ const {refresh: refresh, src: src} = this.element;
4651
+ this.#shouldMorphFrame = src && refresh === "morph";
4652
+ this.element.removeAttribute("complete");
3467
4653
  this.element.src = null;
3468
4654
  this.element.src = src;
3469
4655
  return this.element.loaded;
3470
4656
  }
3471
- completeChanged() {
3472
- if (this.isIgnoringChangesTo("complete")) return;
3473
- this.loadSourceURL();
3474
- }
3475
4657
  loadingStyleChanged() {
3476
4658
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3477
4659
  this.appearanceObserver.start();
3478
4660
  } else {
3479
4661
  this.appearanceObserver.stop();
3480
- this.loadSourceURL();
4662
+ this.#loadSourceURL();
3481
4663
  }
3482
4664
  }
3483
- async loadSourceURL() {
4665
+ async #loadSourceURL() {
3484
4666
  if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3485
- this.element.loaded = this.visit(expandURL(this.sourceURL));
4667
+ this.element.loaded = this.#visit(expandURL(this.sourceURL));
3486
4668
  this.appearanceObserver.stop();
3487
4669
  await this.element.loaded;
3488
- this.hasBeenLoaded = true;
4670
+ this.#hasBeenLoaded = true;
3489
4671
  }
3490
4672
  }
3491
4673
  async loadResponse(fetchResponse) {
@@ -3498,34 +4680,35 @@ class FrameController {
3498
4680
  const document = parseHTMLDocument(html);
3499
4681
  const pageSnapshot = PageSnapshot.fromDocument(document);
3500
4682
  if (pageSnapshot.isVisitable) {
3501
- await this.loadFrameResponse(fetchResponse, document);
4683
+ await this.#loadFrameResponse(fetchResponse, document);
3502
4684
  } else {
3503
- await this.handleUnvisitableFrameResponse(fetchResponse);
4685
+ await this.#handleUnvisitableFrameResponse(fetchResponse);
3504
4686
  }
3505
4687
  }
3506
4688
  } finally {
3507
- this.fetchResponseLoaded = () => {};
4689
+ this.#shouldMorphFrame = false;
4690
+ this.fetchResponseLoaded = () => Promise.resolve();
3508
4691
  }
3509
4692
  }
3510
4693
  elementAppearedInViewport(element) {
3511
- this.proposeVisitIfNavigatedWithAction(element, element);
3512
- this.loadSourceURL();
4694
+ this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
4695
+ this.#loadSourceURL();
3513
4696
  }
3514
4697
  willSubmitFormLinkToLocation(link) {
3515
- return this.shouldInterceptNavigation(link);
4698
+ return this.#shouldInterceptNavigation(link);
3516
4699
  }
3517
4700
  submittedFormLinkToLocation(link, _location, form) {
3518
- const frame = this.findFrameElement(link);
4701
+ const frame = this.#findFrameElement(link);
3519
4702
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3520
4703
  }
3521
4704
  shouldInterceptLinkClick(element, _location, _event) {
3522
- return this.shouldInterceptNavigation(element);
4705
+ return this.#shouldInterceptNavigation(element);
3523
4706
  }
3524
4707
  linkClickIntercepted(element, location) {
3525
- this.navigateFrame(element, location);
4708
+ this.#navigateFrame(element, location);
3526
4709
  }
3527
4710
  willSubmitForm(element, submitter) {
3528
- return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
4711
+ return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
3529
4712
  }
3530
4713
  formSubmitted(element, submitter) {
3531
4714
  if (this.formSubmission) {
@@ -3537,9 +4720,8 @@ class FrameController {
3537
4720
  this.formSubmission.start();
3538
4721
  }
3539
4722
  prepareRequest(request) {
3540
- var _a;
3541
4723
  request.headers["Turbo-Frame"] = this.id;
3542
- if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
4724
+ if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
3543
4725
  request.acceptResponseType(StreamMessage.contentType);
3544
4726
  }
3545
4727
  }
@@ -3547,29 +4729,29 @@ class FrameController {
3547
4729
  markAsBusy(this.element);
3548
4730
  }
3549
4731
  requestPreventedHandlingResponse(_request, _response) {
3550
- this.resolveVisitPromise();
4732
+ this.#resolveVisitPromise();
3551
4733
  }
3552
4734
  async requestSucceededWithResponse(request, response) {
3553
4735
  await this.loadResponse(response);
3554
- this.resolveVisitPromise();
4736
+ this.#resolveVisitPromise();
3555
4737
  }
3556
4738
  async requestFailedWithResponse(request, response) {
3557
4739
  await this.loadResponse(response);
3558
- this.resolveVisitPromise();
4740
+ this.#resolveVisitPromise();
3559
4741
  }
3560
4742
  requestErrored(request, error) {
3561
4743
  console.error(error);
3562
- this.resolveVisitPromise();
4744
+ this.#resolveVisitPromise();
3563
4745
  }
3564
4746
  requestFinished(_request) {
3565
4747
  clearBusyState(this.element);
3566
4748
  }
3567
4749
  formSubmissionStarted({formElement: formElement}) {
3568
- markAsBusy(formElement, this.findFrameElement(formElement));
4750
+ markAsBusy(formElement, this.#findFrameElement(formElement));
3569
4751
  }
3570
4752
  formSubmissionSucceededWithResponse(formSubmission, response) {
3571
- const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3572
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
4753
+ const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
4754
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
3573
4755
  frame.delegate.loadResponse(response);
3574
4756
  if (!formSubmission.isSafe) {
3575
4757
  session.clearCache();
@@ -3583,14 +4765,15 @@ class FrameController {
3583
4765
  console.error(error);
3584
4766
  }
3585
4767
  formSubmissionFinished({formElement: formElement}) {
3586
- clearBusyState(formElement, this.findFrameElement(formElement));
4768
+ clearBusyState(formElement, this.#findFrameElement(formElement));
3587
4769
  }
3588
4770
  allowsImmediateRender({element: newFrame}, options) {
3589
4771
  const event = dispatch("turbo:before-frame-render", {
3590
4772
  target: this.element,
3591
- detail: Object.assign({
3592
- newFrame: newFrame
3593
- }, options),
4773
+ detail: {
4774
+ newFrame: newFrame,
4775
+ ...options
4776
+ },
3594
4777
  cancelable: true
3595
4778
  });
3596
4779
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
@@ -3599,7 +4782,7 @@ class FrameController {
3599
4782
  }
3600
4783
  return !defaultPrevented;
3601
4784
  }
3602
- viewRenderedSnapshot(_snapshot, _isPreview) {}
4785
+ viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
3603
4786
  preloadOnLoadLinksForView(element) {
3604
4787
  session.preloadOnLoadLinksForView(element);
3605
4788
  }
@@ -3607,52 +4790,59 @@ class FrameController {
3607
4790
  willRenderFrame(currentElement, _newElement) {
3608
4791
  this.previousFrameElement = currentElement.cloneNode(true);
3609
4792
  }
3610
- async loadFrameResponse(fetchResponse, document) {
4793
+ visitCachedSnapshot=({element: element}) => {
4794
+ const frame = element.querySelector("#" + this.element.id);
4795
+ if (frame && this.previousFrameElement) {
4796
+ frame.replaceChildren(...this.previousFrameElement.children);
4797
+ }
4798
+ delete this.previousFrameElement;
4799
+ };
4800
+ async #loadFrameResponse(fetchResponse, document) {
3611
4801
  const newFrameElement = await this.extractForeignFrameElement(document.body);
4802
+ const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;
3612
4803
  if (newFrameElement) {
3613
4804
  const snapshot = new Snapshot(newFrameElement);
3614
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
4805
+ const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);
3615
4806
  if (this.view.renderPromise) await this.view.renderPromise;
3616
4807
  this.changeHistory();
3617
4808
  await this.view.render(renderer);
3618
4809
  this.complete = true;
3619
4810
  session.frameRendered(fetchResponse, this.element);
3620
4811
  session.frameLoaded(this.element);
3621
- this.fetchResponseLoaded(fetchResponse);
3622
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
- this.handleFrameMissingFromResponse(fetchResponse);
4812
+ await this.fetchResponseLoaded(fetchResponse);
4813
+ } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
4814
+ this.#handleFrameMissingFromResponse(fetchResponse);
3624
4815
  }
3625
4816
  }
3626
- async visit(url) {
3627
- var _a;
4817
+ async #visit(url) {
3628
4818
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3629
- (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3630
- this.currentFetchRequest = request;
4819
+ this.#currentFetchRequest?.cancel();
4820
+ this.#currentFetchRequest = request;
3631
4821
  return new Promise((resolve => {
3632
- this.resolveVisitPromise = () => {
3633
- this.resolveVisitPromise = () => {};
3634
- this.currentFetchRequest = null;
4822
+ this.#resolveVisitPromise = () => {
4823
+ this.#resolveVisitPromise = () => {};
4824
+ this.#currentFetchRequest = null;
3635
4825
  resolve();
3636
4826
  };
3637
4827
  request.perform();
3638
4828
  }));
3639
4829
  }
3640
- navigateFrame(element, url, submitter) {
3641
- const frame = this.findFrameElement(element, submitter);
3642
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3643
- this.withCurrentNavigationElement(element, (() => {
4830
+ #navigateFrame(element, url, submitter) {
4831
+ const frame = this.#findFrameElement(element, submitter);
4832
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
4833
+ this.#withCurrentNavigationElement(element, (() => {
3644
4834
  frame.src = url;
3645
4835
  }));
3646
4836
  }
3647
- proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3648
- this.action = getVisitAction(submitter, element, frame);
4837
+ proposeVisitIfNavigatedWithAction(frame, action = null) {
4838
+ this.action = action;
3649
4839
  if (this.action) {
3650
4840
  const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3651
4841
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3652
- frame.delegate.fetchResponseLoaded = fetchResponse => {
4842
+ frame.delegate.fetchResponseLoaded = async fetchResponse => {
3653
4843
  if (frame.src) {
3654
4844
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
3655
- const responseHTML = frame.ownerDocument.documentElement.outerHTML;
4845
+ const responseHTML = await fetchResponse.responseHTML;
3656
4846
  const response = {
3657
4847
  statusCode: statusCode,
3658
4848
  redirected: redirected,
@@ -3678,16 +4868,16 @@ class FrameController {
3678
4868
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3679
4869
  }
3680
4870
  }
3681
- async handleUnvisitableFrameResponse(fetchResponse) {
4871
+ async #handleUnvisitableFrameResponse(fetchResponse) {
3682
4872
  console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3683
- await this.visitResponse(fetchResponse.response);
4873
+ await this.#visitResponse(fetchResponse.response);
3684
4874
  }
3685
- willHandleFrameMissingFromResponse(fetchResponse) {
4875
+ #willHandleFrameMissingFromResponse(fetchResponse) {
3686
4876
  this.element.setAttribute("complete", "");
3687
4877
  const response = fetchResponse.response;
3688
- const visit = async (url, options = {}) => {
4878
+ const visit = async (url, options) => {
3689
4879
  if (url instanceof Response) {
3690
- this.visitResponse(url);
4880
+ this.#visitResponse(url);
3691
4881
  } else {
3692
4882
  session.visit(url, options);
3693
4883
  }
@@ -3702,15 +4892,15 @@ class FrameController {
3702
4892
  });
3703
4893
  return !event.defaultPrevented;
3704
4894
  }
3705
- handleFrameMissingFromResponse(fetchResponse) {
4895
+ #handleFrameMissingFromResponse(fetchResponse) {
3706
4896
  this.view.missing();
3707
- this.throwFrameMissingError(fetchResponse);
4897
+ this.#throwFrameMissingError(fetchResponse);
3708
4898
  }
3709
- throwFrameMissingError(fetchResponse) {
4899
+ #throwFrameMissingError(fetchResponse) {
3710
4900
  const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
3711
4901
  throw new TurboFrameMissingError(message);
3712
4902
  }
3713
- async visitResponse(response) {
4903
+ async #visitResponse(response) {
3714
4904
  const wrapped = new FetchResponse(response);
3715
4905
  const responseHTML = await wrapped.responseHTML;
3716
4906
  const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
@@ -3722,10 +4912,9 @@ class FrameController {
3722
4912
  }
3723
4913
  });
3724
4914
  }
3725
- findFrameElement(element, submitter) {
3726
- var _a;
4915
+ #findFrameElement(element, submitter) {
3727
4916
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3728
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
4917
+ return getFrameElementById(id) ?? this.element;
3729
4918
  }
3730
4919
  async extractForeignFrameElement(container) {
3731
4920
  let element;
@@ -3746,13 +4935,13 @@ class FrameController {
3746
4935
  }
3747
4936
  return null;
3748
4937
  }
3749
- formActionIsVisitable(form, submitter) {
3750
- const action = getAction(form, submitter);
4938
+ #formActionIsVisitable(form, submitter) {
4939
+ const action = getAction$1(form, submitter);
3751
4940
  return locationIsVisitable(expandURL(action), this.rootLocation);
3752
4941
  }
3753
- shouldInterceptNavigation(element, submitter) {
4942
+ #shouldInterceptNavigation(element, submitter) {
3754
4943
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3755
- if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
4944
+ if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
3756
4945
  return false;
3757
4946
  }
3758
4947
  if (!this.enabled || id == "_top") {
@@ -3784,46 +4973,43 @@ class FrameController {
3784
4973
  }
3785
4974
  }
3786
4975
  set sourceURL(sourceURL) {
3787
- this.ignoringChangesToAttribute("src", (() => {
3788
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
4976
+ this.#ignoringChangesToAttribute("src", (() => {
4977
+ this.element.src = sourceURL ?? null;
3789
4978
  }));
3790
4979
  }
3791
4980
  get loadingStyle() {
3792
4981
  return this.element.loading;
3793
4982
  }
3794
4983
  get isLoading() {
3795
- return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
4984
+ return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
3796
4985
  }
3797
4986
  get complete() {
3798
4987
  return this.element.hasAttribute("complete");
3799
4988
  }
3800
4989
  set complete(value) {
3801
- this.ignoringChangesToAttribute("complete", (() => {
3802
- if (value) {
3803
- this.element.setAttribute("complete", "");
3804
- } else {
3805
- this.element.removeAttribute("complete");
3806
- }
3807
- }));
4990
+ if (value) {
4991
+ this.element.setAttribute("complete", "");
4992
+ } else {
4993
+ this.element.removeAttribute("complete");
4994
+ }
3808
4995
  }
3809
4996
  get isActive() {
3810
- return this.element.isActive && this.connected;
4997
+ return this.element.isActive && this.#connected;
3811
4998
  }
3812
4999
  get rootLocation() {
3813
- var _a;
3814
5000
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3815
- const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
5001
+ const root = meta?.content ?? "/";
3816
5002
  return expandURL(root);
3817
5003
  }
3818
- isIgnoringChangesTo(attributeName) {
3819
- return this.ignoredAttributes.has(attributeName);
5004
+ #isIgnoringChangesTo(attributeName) {
5005
+ return this.#ignoredAttributes.has(attributeName);
3820
5006
  }
3821
- ignoringChangesToAttribute(attributeName, callback) {
3822
- this.ignoredAttributes.add(attributeName);
5007
+ #ignoringChangesToAttribute(attributeName, callback) {
5008
+ this.#ignoredAttributes.add(attributeName);
3823
5009
  callback();
3824
- this.ignoredAttributes.delete(attributeName);
5010
+ this.#ignoredAttributes.delete(attributeName);
3825
5011
  }
3826
- withCurrentNavigationElement(element, callback) {
5012
+ #withCurrentNavigationElement(element, callback) {
3827
5013
  this.currentNavigationElement = element;
3828
5014
  callback();
3829
5015
  delete this.currentNavigationElement;
@@ -3856,6 +5042,50 @@ function activateElement(element, currentURL) {
3856
5042
  }
3857
5043
  }
3858
5044
 
5045
+ const StreamActions = {
5046
+ after() {
5047
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
5048
+ },
5049
+ append() {
5050
+ this.removeDuplicateTargetChildren();
5051
+ this.targetElements.forEach((e => e.append(this.templateContent)));
5052
+ },
5053
+ before() {
5054
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
5055
+ },
5056
+ prepend() {
5057
+ this.removeDuplicateTargetChildren();
5058
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
5059
+ },
5060
+ remove() {
5061
+ this.targetElements.forEach((e => e.remove()));
5062
+ },
5063
+ replace() {
5064
+ const method = this.getAttribute("method");
5065
+ this.targetElements.forEach((targetElement => {
5066
+ if (method === "morph") {
5067
+ morphElements(targetElement, this.templateContent);
5068
+ } else {
5069
+ targetElement.replaceWith(this.templateContent);
5070
+ }
5071
+ }));
5072
+ },
5073
+ update() {
5074
+ const method = this.getAttribute("method");
5075
+ this.targetElements.forEach((targetElement => {
5076
+ if (method === "morph") {
5077
+ morphChildren(targetElement, this.templateContent);
5078
+ } else {
5079
+ targetElement.innerHTML = "";
5080
+ targetElement.append(this.templateContent);
5081
+ }
5082
+ }));
5083
+ },
5084
+ refresh() {
5085
+ session.refresh(this.baseURI, this.requestId);
5086
+ }
5087
+ };
5088
+
3859
5089
  class StreamElement extends HTMLElement {
3860
5090
  static async renderElement(newElement) {
3861
5091
  await newElement.performAction();
@@ -3870,11 +5100,10 @@ class StreamElement extends HTMLElement {
3870
5100
  }
3871
5101
  }
3872
5102
  async render() {
3873
- var _a;
3874
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
5103
+ return this.renderPromise ??= (async () => {
3875
5104
  const event = this.beforeRenderEvent;
3876
5105
  if (this.dispatchEvent(event)) {
3877
- await nextAnimationFrame();
5106
+ await nextRepaint();
3878
5107
  await event.detail.render(this);
3879
5108
  }
3880
5109
  })();
@@ -3882,15 +5111,14 @@ class StreamElement extends HTMLElement {
3882
5111
  disconnect() {
3883
5112
  try {
3884
5113
  this.remove();
3885
- } catch (_a) {}
5114
+ } catch {}
3886
5115
  }
3887
5116
  removeDuplicateTargetChildren() {
3888
5117
  this.duplicateChildren.forEach((c => c.remove()));
3889
5118
  }
3890
5119
  get duplicateChildren() {
3891
- var _a;
3892
5120
  const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3893
- const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
5121
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
3894
5122
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3895
5123
  }
3896
5124
  get performAction() {
@@ -3899,9 +5127,9 @@ class StreamElement extends HTMLElement {
3899
5127
  if (actionFunction) {
3900
5128
  return actionFunction;
3901
5129
  }
3902
- this.raise("unknown action");
5130
+ this.#raise("unknown action");
3903
5131
  }
3904
- this.raise("action attribute is missing");
5132
+ this.#raise("action attribute is missing");
3905
5133
  }
3906
5134
  get targetElements() {
3907
5135
  if (this.target) {
@@ -3909,7 +5137,7 @@ class StreamElement extends HTMLElement {
3909
5137
  } else if (this.targets) {
3910
5138
  return this.targetElementsByQuery;
3911
5139
  } else {
3912
- this.raise("target or targets attribute is missing");
5140
+ this.#raise("target or targets attribute is missing");
3913
5141
  }
3914
5142
  }
3915
5143
  get templateContent() {
@@ -3923,7 +5151,7 @@ class StreamElement extends HTMLElement {
3923
5151
  } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3924
5152
  return this.firstElementChild;
3925
5153
  }
3926
- this.raise("first child element must be a <template> element");
5154
+ this.#raise("first child element must be a <template> element");
3927
5155
  }
3928
5156
  get action() {
3929
5157
  return this.getAttribute("action");
@@ -3934,12 +5162,14 @@ class StreamElement extends HTMLElement {
3934
5162
  get targets() {
3935
5163
  return this.getAttribute("targets");
3936
5164
  }
3937
- raise(message) {
5165
+ get requestId() {
5166
+ return this.getAttribute("request-id");
5167
+ }
5168
+ #raise(message) {
3938
5169
  throw new Error(`${this.description}: ${message}`);
3939
5170
  }
3940
5171
  get description() {
3941
- var _a, _b;
3942
- return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
5172
+ return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
3943
5173
  }
3944
5174
  get beforeRenderEvent() {
3945
5175
  return new CustomEvent("turbo:before-stream-render", {
@@ -3952,8 +5182,7 @@ class StreamElement extends HTMLElement {
3952
5182
  });
3953
5183
  }
3954
5184
  get targetElementsById() {
3955
- var _a;
3956
- const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
5185
+ const element = this.ownerDocument?.getElementById(this.target);
3957
5186
  if (element !== null) {
3958
5187
  return [ element ];
3959
5188
  } else {
@@ -3961,8 +5190,7 @@ class StreamElement extends HTMLElement {
3961
5190
  }
3962
5191
  }
3963
5192
  get targetElementsByQuery() {
3964
- var _a;
3965
- const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
5193
+ const elements = this.ownerDocument?.querySelectorAll(this.targets);
3966
5194
  if (elements.length !== 0) {
3967
5195
  return Array.prototype.slice.call(elements);
3968
5196
  } else {
@@ -3972,16 +5200,14 @@ class StreamElement extends HTMLElement {
3972
5200
  }
3973
5201
 
3974
5202
  class StreamSourceElement extends HTMLElement {
3975
- constructor() {
3976
- super(...arguments);
3977
- this.streamSource = null;
3978
- }
5203
+ streamSource=null;
3979
5204
  connectedCallback() {
3980
5205
  this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3981
5206
  connectStreamSource(this.streamSource);
3982
5207
  }
3983
5208
  disconnectedCallback() {
3984
5209
  if (this.streamSource) {
5210
+ this.streamSource.close();
3985
5211
  disconnectStreamSource(this.streamSource);
3986
5212
  }
3987
5213
  }
@@ -4026,16 +5252,21 @@ if (customElements.get("turbo-stream-source") === undefined) {
4026
5252
  }
4027
5253
  })();
4028
5254
 
4029
- window.Turbo = Turbo;
5255
+ window.Turbo = {
5256
+ ...Turbo,
5257
+ StreamActions: StreamActions
5258
+ };
4030
5259
 
4031
5260
  start();
4032
5261
 
4033
- var turbo_es2017Esm = Object.freeze({
5262
+ var Turbo$1 = Object.freeze({
4034
5263
  __proto__: null,
5264
+ FetchEnctype: FetchEnctype,
5265
+ FetchMethod: FetchMethod,
5266
+ FetchRequest: FetchRequest,
5267
+ FetchResponse: FetchResponse,
4035
5268
  FrameElement: FrameElement,
4036
- get FrameLoadingStyle() {
4037
- return FrameLoadingStyle;
4038
- },
5269
+ FrameLoadingStyle: FrameLoadingStyle,
4039
5270
  FrameRenderer: FrameRenderer,
4040
5271
  PageRenderer: PageRenderer,
4041
5272
  PageSnapshot: PageSnapshot,
@@ -4044,8 +5275,13 @@ var turbo_es2017Esm = Object.freeze({
4044
5275
  StreamSourceElement: StreamSourceElement,
4045
5276
  cache: cache,
4046
5277
  clearCache: clearCache,
5278
+ config: config,
4047
5279
  connectStreamSource: connectStreamSource,
4048
5280
  disconnectStreamSource: disconnectStreamSource,
5281
+ fetch: fetchWithTurboHeaders,
5282
+ fetchEnctypeFromString: fetchEnctypeFromString,
5283
+ fetchMethodFromString: fetchMethodFromString,
5284
+ isSafe: isSafe,
4049
5285
  navigator: navigator$1,
4050
5286
  registerAdapter: registerAdapter,
4051
5287
  renderStreamMessage: renderStreamMessage,
@@ -4060,14 +5296,14 @@ var turbo_es2017Esm = Object.freeze({
4060
5296
  let consumer;
4061
5297
 
4062
5298
  async function getConsumer() {
4063
- return consumer || setConsumer(createConsumer().then(setConsumer));
5299
+ return consumer || setConsumer(createConsumer$1().then(setConsumer));
4064
5300
  }
4065
5301
 
4066
5302
  function setConsumer(newConsumer) {
4067
5303
  return consumer = newConsumer;
4068
5304
  }
4069
5305
 
4070
- async function createConsumer() {
5306
+ async function createConsumer$1() {
4071
5307
  const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
4072
5308
  return index;
4073
5309
  }));
@@ -4083,7 +5319,7 @@ var cable = Object.freeze({
4083
5319
  __proto__: null,
4084
5320
  getConsumer: getConsumer,
4085
5321
  setConsumer: setConsumer,
4086
- createConsumer: createConsumer,
5322
+ createConsumer: createConsumer$1,
4087
5323
  subscribeTo: subscribeTo
4088
5324
  });
4089
5325
 
@@ -4101,6 +5337,7 @@ function walk(obj) {
4101
5337
  }
4102
5338
 
4103
5339
  class TurboCableStreamSourceElement extends HTMLElement {
5340
+ static observedAttributes=[ "channel", "signed-stream-name" ];
4104
5341
  async connectedCallback() {
4105
5342
  connectStreamSource(this);
4106
5343
  this.subscription = await subscribeTo(this.channel, {
@@ -4112,6 +5349,13 @@ class TurboCableStreamSourceElement extends HTMLElement {
4112
5349
  disconnectedCallback() {
4113
5350
  disconnectStreamSource(this);
4114
5351
  if (this.subscription) this.subscription.unsubscribe();
5352
+ this.subscriptionDisconnected();
5353
+ }
5354
+ attributeChangedCallback() {
5355
+ if (this.subscription) {
5356
+ this.disconnectedCallback();
5357
+ this.connectedCallback();
5358
+ }
4115
5359
  }
4116
5360
  dispatchMessageEvent(data) {
4117
5361
  const event = new MessageEvent("message", {
@@ -4193,6 +5437,8 @@ function isBodyInit(body) {
4193
5437
  return body instanceof FormData || body instanceof URLSearchParams;
4194
5438
  }
4195
5439
 
5440
+ window.Turbo = Turbo$1;
5441
+
4196
5442
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4197
5443
 
4198
5444
  var adapters = {
@@ -4309,6 +5555,8 @@ ConnectionMonitor.staleThreshold = 6;
4309
5555
 
4310
5556
  ConnectionMonitor.reconnectionBackoffRate = .15;
4311
5557
 
5558
+ var ConnectionMonitor$1 = ConnectionMonitor;
5559
+
4312
5560
  var INTERNAL = {
4313
5561
  message_types: {
4314
5562
  welcome: "welcome",
@@ -4320,7 +5568,8 @@ var INTERNAL = {
4320
5568
  disconnect_reasons: {
4321
5569
  unauthorized: "unauthorized",
4322
5570
  invalid_request: "invalid_request",
4323
- server_restart: "server_restart"
5571
+ server_restart: "server_restart",
5572
+ remote: "remote"
4324
5573
  },
4325
5574
  default_mount_path: "/cable",
4326
5575
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -4337,7 +5586,7 @@ class Connection {
4337
5586
  this.open = this.open.bind(this);
4338
5587
  this.consumer = consumer;
4339
5588
  this.subscriptions = this.consumer.subscriptions;
4340
- this.monitor = new ConnectionMonitor(this);
5589
+ this.monitor = new ConnectionMonitor$1(this);
4341
5590
  this.disconnected = true;
4342
5591
  }
4343
5592
  send(data) {
@@ -4353,11 +5602,12 @@ class Connection {
4353
5602
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
4354
5603
  return false;
4355
5604
  } else {
4356
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
5605
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
5606
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
4357
5607
  if (this.webSocket) {
4358
5608
  this.uninstallEventHandlers();
4359
5609
  }
4360
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
5610
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
4361
5611
  this.installEventHandlers();
4362
5612
  this.monitor.start();
4363
5613
  return true;
@@ -4369,7 +5619,7 @@ class Connection {
4369
5619
  if (!allowReconnect) {
4370
5620
  this.monitor.stop();
4371
5621
  }
4372
- if (this.isActive()) {
5622
+ if (this.isOpen()) {
4373
5623
  return this.webSocket.close();
4374
5624
  }
4375
5625
  }
@@ -4399,6 +5649,9 @@ class Connection {
4399
5649
  isActive() {
4400
5650
  return this.isState("open", "connecting");
4401
5651
  }
5652
+ triedToReconnect() {
5653
+ return this.monitor.reconnectAttempts > 0;
5654
+ }
4402
5655
  isProtocolSupported() {
4403
5656
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
4404
5657
  }
@@ -4438,6 +5691,9 @@ Connection.prototype.events = {
4438
5691
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
4439
5692
  switch (type) {
4440
5693
  case message_types.welcome:
5694
+ if (this.triedToReconnect()) {
5695
+ this.reconnectAttempted = true;
5696
+ }
4441
5697
  this.monitor.recordConnect();
4442
5698
  return this.subscriptions.reload();
4443
5699
 
@@ -4452,7 +5708,16 @@ Connection.prototype.events = {
4452
5708
 
4453
5709
  case message_types.confirmation:
4454
5710
  this.subscriptions.confirmSubscription(identifier);
4455
- return this.subscriptions.notify(identifier, "connected");
5711
+ if (this.reconnectAttempted) {
5712
+ this.reconnectAttempted = false;
5713
+ return this.subscriptions.notify(identifier, "connected", {
5714
+ reconnected: true
5715
+ });
5716
+ } else {
5717
+ return this.subscriptions.notify(identifier, "connected", {
5718
+ reconnected: false
5719
+ });
5720
+ }
4456
5721
 
4457
5722
  case message_types.rejection:
4458
5723
  return this.subscriptions.reject(identifier);
@@ -4487,6 +5752,8 @@ Connection.prototype.events = {
4487
5752
  }
4488
5753
  };
4489
5754
 
5755
+ var Connection$1 = Connection;
5756
+
4490
5757
  const extend = function(object, properties) {
4491
5758
  if (properties != null) {
4492
5759
  for (let key in properties) {
@@ -4556,10 +5823,12 @@ class SubscriptionGuarantor {
4556
5823
  }
4557
5824
  }
4558
5825
 
5826
+ var SubscriptionGuarantor$1 = SubscriptionGuarantor;
5827
+
4559
5828
  class Subscriptions {
4560
5829
  constructor(consumer) {
4561
5830
  this.consumer = consumer;
4562
- this.guarantor = new SubscriptionGuarantor(this);
5831
+ this.guarantor = new SubscriptionGuarantor$1(this);
4563
5832
  this.subscriptions = [];
4564
5833
  }
4565
5834
  create(channelName, mixin) {
@@ -4636,7 +5905,8 @@ class Consumer {
4636
5905
  constructor(url) {
4637
5906
  this._url = url;
4638
5907
  this.subscriptions = new Subscriptions(this);
4639
- this.connection = new Connection(this);
5908
+ this.connection = new Connection$1(this);
5909
+ this.subprotocols = [];
4640
5910
  }
4641
5911
  get url() {
4642
5912
  return createWebSocketURL(this._url);
@@ -4657,6 +5927,9 @@ class Consumer {
4657
5927
  return this.connection.open();
4658
5928
  }
4659
5929
  }
5930
+ addSubProtocol(subprotocol) {
5931
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
5932
+ }
4660
5933
  }
4661
5934
 
4662
5935
  function createWebSocketURL(url) {
@@ -4674,7 +5947,7 @@ function createWebSocketURL(url) {
4674
5947
  }
4675
5948
  }
4676
5949
 
4677
- function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
5950
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
4678
5951
  return new Consumer(url);
4679
5952
  }
4680
5953
 
@@ -4687,18 +5960,18 @@ function getConfig(name) {
4687
5960
 
4688
5961
  var index = Object.freeze({
4689
5962
  __proto__: null,
4690
- Connection: Connection,
4691
- ConnectionMonitor: ConnectionMonitor,
5963
+ Connection: Connection$1,
5964
+ ConnectionMonitor: ConnectionMonitor$1,
4692
5965
  Consumer: Consumer,
4693
5966
  INTERNAL: INTERNAL,
4694
5967
  Subscription: Subscription,
4695
5968
  Subscriptions: Subscriptions,
4696
- SubscriptionGuarantor: SubscriptionGuarantor,
5969
+ SubscriptionGuarantor: SubscriptionGuarantor$1,
4697
5970
  adapters: adapters,
4698
5971
  createWebSocketURL: createWebSocketURL,
4699
5972
  logger: logger,
4700
- createConsumer: createConsumer$1,
5973
+ createConsumer: createConsumer,
4701
5974
  getConfig: getConfig
4702
5975
  });
4703
5976
 
4704
- export { turbo_es2017Esm as Turbo, cable };
5977
+ export { Turbo$1 as Turbo, cable };