turbo-rails 2.0.6 → 2.0.13

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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
- Turbo 8.0.5
3
- Copyright © 2024 37signals LLC
2
+ Turbo 8.0.13
3
+ Copyright © 2025 37signals LLC
4
4
  */
5
5
  (function(prototype) {
6
6
  if (typeof prototype.requestSubmit == "function") return;
@@ -116,6 +116,9 @@ class FrameElement extends HTMLElement {
116
116
  this.removeAttribute("refresh");
117
117
  }
118
118
  }
119
+ get shouldReloadWithMorph() {
120
+ return this.src && this.refresh === "morph";
121
+ }
119
122
  get loading() {
120
123
  return frameLoadingStyleFromString(this.getAttribute("loading") || "");
121
124
  }
@@ -167,122 +170,18 @@ function frameLoadingStyleFromString(style) {
167
170
  }
168
171
  }
169
172
 
170
- function expandURL(locatable) {
171
- return new URL(locatable.toString(), document.baseURI);
172
- }
173
-
174
- function getAnchor(url) {
175
- let anchorMatch;
176
- if (url.hash) {
177
- return url.hash.slice(1);
178
- } else if (anchorMatch = url.href.match(/#(.*)$/)) {
179
- return anchorMatch[1];
180
- }
181
- }
182
-
183
- function getAction$1(form, submitter) {
184
- const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
185
- return expandURL(action);
186
- }
187
-
188
- function getExtension(url) {
189
- return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
190
- }
191
-
192
- function isHTML(url) {
193
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
194
- }
195
-
196
- function isPrefixedBy(baseURL, url) {
197
- const prefix = getPrefix(url);
198
- return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
199
- }
200
-
201
- function locationIsVisitable(location, rootLocation) {
202
- return isPrefixedBy(location, rootLocation) && isHTML(location);
203
- }
204
-
205
- function getRequestURL(url) {
206
- const anchor = getAnchor(url);
207
- return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
208
- }
209
-
210
- function toCacheKey(url) {
211
- return getRequestURL(url);
212
- }
213
-
214
- function urlsAreEqual(left, right) {
215
- return expandURL(left).href == expandURL(right).href;
216
- }
217
-
218
- function getPathComponents(url) {
219
- return url.pathname.split("/").slice(1);
220
- }
221
-
222
- function getLastPathComponent(url) {
223
- return getPathComponents(url).slice(-1)[0];
224
- }
225
-
226
- function getPrefix(url) {
227
- return addTrailingSlash(url.origin + url.pathname);
228
- }
229
-
230
- function addTrailingSlash(value) {
231
- return value.endsWith("/") ? value : value + "/";
232
- }
233
-
234
- class FetchResponse {
235
- constructor(response) {
236
- this.response = response;
237
- }
238
- get succeeded() {
239
- return this.response.ok;
240
- }
241
- get failed() {
242
- return !this.succeeded;
243
- }
244
- get clientError() {
245
- return this.statusCode >= 400 && this.statusCode <= 499;
246
- }
247
- get serverError() {
248
- return this.statusCode >= 500 && this.statusCode <= 599;
249
- }
250
- get redirected() {
251
- return this.response.redirected;
252
- }
253
- get location() {
254
- return expandURL(this.response.url);
255
- }
256
- get isHTML() {
257
- return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
258
- }
259
- get statusCode() {
260
- return this.response.status;
261
- }
262
- get contentType() {
263
- return this.header("Content-Type");
264
- }
265
- get responseText() {
266
- return this.response.clone().text();
267
- }
268
- get responseHTML() {
269
- if (this.isHTML) {
270
- return this.response.clone().text();
271
- } else {
272
- return Promise.resolve(undefined);
273
- }
274
- }
275
- header(name) {
276
- return this.response.headers.get(name);
277
- }
278
- }
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
+ };
279
178
 
280
179
  function activateScriptElement(element) {
281
180
  if (element.getAttribute("data-turbo-eval") == "false") {
282
181
  return element;
283
182
  } else {
284
183
  const createdScriptElement = document.createElement("script");
285
- const cspNonce = getMetaContent("csp-nonce");
184
+ const cspNonce = getCspNonce();
286
185
  if (cspNonce) {
287
186
  createdScriptElement.nonce = cspNonce;
288
187
  }
@@ -320,6 +219,11 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
320
219
  return event;
321
220
  }
322
221
 
222
+ function cancelEvent(event) {
223
+ event.preventDefault();
224
+ event.stopImmediatePropagation();
225
+ }
226
+
323
227
  function nextRepaint() {
324
228
  if (document.visibilityState === "hidden") {
325
229
  return nextEventLoopTick();
@@ -449,6 +353,14 @@ function getMetaContent(name) {
449
353
  return element && element.content;
450
354
  }
451
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
+
452
364
  function setMetaContent(name, content) {
453
365
  let element = getMetaElement(name);
454
366
  if (!element) {
@@ -513,6 +425,152 @@ function debounce(fn, delay) {
513
425
  };
514
426
  }
515
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;
455
+ }
456
+ }
457
+
458
+ const forms = new Config({
459
+ mode: "on",
460
+ submitter: "disabled"
461
+ });
462
+
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
+
516
574
  class LimitedSet extends Set {
517
575
  constructor(maxSize) {
518
576
  super();
@@ -861,7 +919,7 @@ const FormSubmissionState = {
861
919
 
862
920
  class FormSubmission {
863
921
  state=FormSubmissionState.initialized;
864
- static confirmMethod(message, _element, _submitter) {
922
+ static confirmMethod(message) {
865
923
  return Promise.resolve(confirm(message));
866
924
  }
867
925
  constructor(delegate, formElement, submitter, mustRedirect = false) {
@@ -903,7 +961,8 @@ class FormSubmission {
903
961
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
904
962
  const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
905
963
  if (typeof confirmationMessage === "string") {
906
- 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);
907
966
  if (!answer) {
908
967
  return;
909
968
  }
@@ -934,7 +993,7 @@ class FormSubmission {
934
993
  }
935
994
  requestStarted(_request) {
936
995
  this.state = FormSubmissionState.waiting;
937
- this.submitter?.setAttribute("disabled", "");
996
+ if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);
938
997
  this.setSubmitsWith();
939
998
  markAsBusy(this.formElement);
940
999
  dispatch("turbo:submit-start", {
@@ -986,7 +1045,7 @@ class FormSubmission {
986
1045
  }
987
1046
  requestFinished(_request) {
988
1047
  this.state = FormSubmissionState.stopped;
989
- this.submitter?.removeAttribute("disabled");
1048
+ if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);
990
1049
  this.resetSubmitterText();
991
1050
  clearBusyState(this.formElement);
992
1051
  dispatch("turbo:submit-end", {
@@ -1480,12 +1539,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
1480
1539
 
1481
1540
  class Renderer {
1482
1541
  #activeElement=null;
1483
- constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1542
+ static renderElement(currentElement, newElement) {}
1543
+ constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1484
1544
  this.currentSnapshot = currentSnapshot;
1485
1545
  this.newSnapshot = newSnapshot;
1486
1546
  this.isPreview = isPreview;
1487
1547
  this.willRender = willRender;
1488
- this.renderElement = renderElement;
1548
+ this.renderElement = this.constructor.renderElement;
1489
1549
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1490
1550
  resolve: resolve,
1491
1551
  reject: reject
@@ -1626,28 +1686,693 @@ function readScrollBehavior(value, defaultValue) {
1626
1686
  }
1627
1687
  }
1628
1688
 
1629
- class ProgressBar {
1630
- static animationDuration=300;
1631
- static get defaultCSS() {
1632
- return unindent`
1633
- .turbo-progress-bar {
1634
- position: fixed;
1635
- display: block;
1636
- top: 0;
1637
- left: 0;
1638
- height: 3px;
1639
- background: #0076ff;
1640
- z-index: 2147483647;
1641
- transition:
1642
- width ${ProgressBar.animationDuration}ms ease-out,
1643
- opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
1644
- transform: translate3d(0, 0, 0);
1645
- }
1646
- `;
1647
- }
1648
- hiding=false;
1649
- value=0;
1650
- visible=false;
1689
+ var Idiomorph = function() {
1690
+ const noOp = () => {};
1691
+ const 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: elt => elt.getAttribute("im-preserve") === "true",
1705
+ shouldReAppend: elt => elt.getAttribute("im-re-append") === "true",
1706
+ shouldRemove: noOp,
1707
+ afterHeadMorphed: noOp
1708
+ },
1709
+ restoreFocus: true
1710
+ };
1711
+ function morph(oldNode, newContent, config = {}) {
1712
+ oldNode = normalizeElement(oldNode);
1713
+ const newNode = normalizeParent(newContent);
1714
+ const ctx = createMorphContext(oldNode, newNode, config);
1715
+ const morphedNodes = saveAndRestoreFocus(ctx, (() => withHeadBlocking(ctx, oldNode, newNode, (ctx => {
1716
+ if (ctx.morphStyle === "innerHTML") {
1717
+ morphChildren(ctx, oldNode, newNode);
1718
+ return Array.from(oldNode.childNodes);
1719
+ } else {
1720
+ return morphOuterHTML(ctx, oldNode, newNode);
1721
+ }
1722
+ }))));
1723
+ ctx.pantry.remove();
1724
+ return morphedNodes;
1725
+ }
1726
+ function morphOuterHTML(ctx, oldNode, newNode) {
1727
+ const oldParent = normalizeParent(oldNode);
1728
+ let childNodes = Array.from(oldParent.childNodes);
1729
+ const index = childNodes.indexOf(oldNode);
1730
+ const rightMargin = childNodes.length - (index + 1);
1731
+ morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
1732
+ childNodes = Array.from(oldParent.childNodes);
1733
+ return childNodes.slice(index, childNodes.length - rightMargin);
1734
+ }
1735
+ function saveAndRestoreFocus(ctx, fn) {
1736
+ if (!ctx.config.restoreFocus) return fn();
1737
+ let activeElement = document.activeElement;
1738
+ if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) {
1739
+ return fn();
1740
+ }
1741
+ const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
1742
+ const results = fn();
1743
+ if (activeElementId && activeElementId !== document.activeElement?.id) {
1744
+ activeElement = ctx.target.querySelector(`#${activeElementId}`);
1745
+ activeElement?.focus();
1746
+ }
1747
+ if (activeElement && !activeElement.selectionEnd && selectionEnd) {
1748
+ activeElement.setSelectionRange(selectionStart, selectionEnd);
1749
+ }
1750
+ return results;
1751
+ }
1752
+ const morphChildren = function() {
1753
+ function morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) {
1754
+ if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
1755
+ oldParent = oldParent.content;
1756
+ newParent = newParent.content;
1757
+ }
1758
+ insertionPoint ||= oldParent.firstChild;
1759
+ for (const newChild of newParent.childNodes) {
1760
+ if (insertionPoint && insertionPoint != endPoint) {
1761
+ const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint);
1762
+ if (bestMatch) {
1763
+ if (bestMatch !== insertionPoint) {
1764
+ removeNodesBetween(ctx, insertionPoint, bestMatch);
1765
+ }
1766
+ morphNode(bestMatch, newChild, ctx);
1767
+ insertionPoint = bestMatch.nextSibling;
1768
+ continue;
1769
+ }
1770
+ }
1771
+ if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
1772
+ const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx);
1773
+ morphNode(movedChild, newChild, ctx);
1774
+ insertionPoint = movedChild.nextSibling;
1775
+ continue;
1776
+ }
1777
+ const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
1778
+ if (insertedNode) {
1779
+ insertionPoint = insertedNode.nextSibling;
1780
+ }
1781
+ }
1782
+ while (insertionPoint && insertionPoint != endPoint) {
1783
+ const tempNode = insertionPoint;
1784
+ insertionPoint = insertionPoint.nextSibling;
1785
+ removeNode(ctx, tempNode);
1786
+ }
1787
+ }
1788
+ function createNode(oldParent, newChild, insertionPoint, ctx) {
1789
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
1790
+ if (ctx.idMap.has(newChild)) {
1791
+ const newEmptyChild = document.createElement(newChild.tagName);
1792
+ oldParent.insertBefore(newEmptyChild, insertionPoint);
1793
+ morphNode(newEmptyChild, newChild, ctx);
1794
+ ctx.callbacks.afterNodeAdded(newEmptyChild);
1795
+ return newEmptyChild;
1796
+ } else {
1797
+ const newClonedChild = document.importNode(newChild, true);
1798
+ oldParent.insertBefore(newClonedChild, insertionPoint);
1799
+ ctx.callbacks.afterNodeAdded(newClonedChild);
1800
+ return newClonedChild;
1801
+ }
1802
+ }
1803
+ const findBestMatch = function() {
1804
+ function findBestMatch(ctx, node, startPoint, endPoint) {
1805
+ let softMatch = null;
1806
+ let nextSibling = node.nextSibling;
1807
+ let siblingSoftMatchCount = 0;
1808
+ let cursor = startPoint;
1809
+ while (cursor && cursor != endPoint) {
1810
+ if (isSoftMatch(cursor, node)) {
1811
+ if (isIdSetMatch(ctx, cursor, node)) {
1812
+ return cursor;
1813
+ }
1814
+ if (softMatch === null) {
1815
+ if (!ctx.idMap.has(cursor)) {
1816
+ softMatch = cursor;
1817
+ }
1818
+ }
1819
+ }
1820
+ if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
1821
+ siblingSoftMatchCount++;
1822
+ nextSibling = nextSibling.nextSibling;
1823
+ if (siblingSoftMatchCount >= 2) {
1824
+ softMatch = undefined;
1825
+ }
1826
+ }
1827
+ if (cursor.contains(document.activeElement)) break;
1828
+ cursor = cursor.nextSibling;
1829
+ }
1830
+ return softMatch || null;
1831
+ }
1832
+ function isIdSetMatch(ctx, oldNode, newNode) {
1833
+ let oldSet = ctx.idMap.get(oldNode);
1834
+ let newSet = ctx.idMap.get(newNode);
1835
+ if (!newSet || !oldSet) return false;
1836
+ for (const id of oldSet) {
1837
+ if (newSet.has(id)) {
1838
+ return true;
1839
+ }
1840
+ }
1841
+ return false;
1842
+ }
1843
+ function isSoftMatch(oldNode, newNode) {
1844
+ const oldElt = oldNode;
1845
+ const newElt = newNode;
1846
+ return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
1847
+ }
1848
+ return findBestMatch;
1849
+ }();
1850
+ function removeNode(ctx, node) {
1851
+ if (ctx.idMap.has(node)) {
1852
+ moveBefore(ctx.pantry, node, null);
1853
+ } else {
1854
+ if (ctx.callbacks.beforeNodeRemoved(node) === false) return;
1855
+ node.parentNode?.removeChild(node);
1856
+ ctx.callbacks.afterNodeRemoved(node);
1857
+ }
1858
+ }
1859
+ function removeNodesBetween(ctx, startInclusive, endExclusive) {
1860
+ let cursor = startInclusive;
1861
+ while (cursor && cursor !== endExclusive) {
1862
+ let tempNode = cursor;
1863
+ cursor = cursor.nextSibling;
1864
+ removeNode(ctx, tempNode);
1865
+ }
1866
+ return cursor;
1867
+ }
1868
+ function moveBeforeById(parentNode, id, after, ctx) {
1869
+ const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`);
1870
+ removeElementFromAncestorsIdMaps(target, ctx);
1871
+ moveBefore(parentNode, target, after);
1872
+ return target;
1873
+ }
1874
+ function removeElementFromAncestorsIdMaps(element, ctx) {
1875
+ const id = element.id;
1876
+ while (element = element.parentNode) {
1877
+ let idSet = ctx.idMap.get(element);
1878
+ if (idSet) {
1879
+ idSet.delete(id);
1880
+ if (!idSet.size) {
1881
+ ctx.idMap.delete(element);
1882
+ }
1883
+ }
1884
+ }
1885
+ }
1886
+ function moveBefore(parentNode, element, after) {
1887
+ if (parentNode.moveBefore) {
1888
+ try {
1889
+ parentNode.moveBefore(element, after);
1890
+ } catch (e) {
1891
+ parentNode.insertBefore(element, after);
1892
+ }
1893
+ } else {
1894
+ parentNode.insertBefore(element, after);
1895
+ }
1896
+ }
1897
+ return morphChildren;
1898
+ }();
1899
+ const morphNode = function() {
1900
+ function morphNode(oldNode, newContent, ctx) {
1901
+ if (ctx.ignoreActive && oldNode === document.activeElement) {
1902
+ return null;
1903
+ }
1904
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {
1905
+ return oldNode;
1906
+ }
1907
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
1908
+ handleHeadElement(oldNode, newContent, ctx);
1909
+ } else {
1910
+ morphAttributes(oldNode, newContent, ctx);
1911
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
1912
+ morphChildren(ctx, oldNode, newContent);
1913
+ }
1914
+ }
1915
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
1916
+ return oldNode;
1917
+ }
1918
+ function morphAttributes(oldNode, newNode, ctx) {
1919
+ let type = newNode.nodeType;
1920
+ if (type === 1) {
1921
+ const oldElt = oldNode;
1922
+ const newElt = newNode;
1923
+ const oldAttributes = oldElt.attributes;
1924
+ const newAttributes = newElt.attributes;
1925
+ for (const newAttribute of newAttributes) {
1926
+ if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
1927
+ continue;
1928
+ }
1929
+ if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
1930
+ oldElt.setAttribute(newAttribute.name, newAttribute.value);
1931
+ }
1932
+ }
1933
+ for (let i = oldAttributes.length - 1; 0 <= i; i--) {
1934
+ const oldAttribute = oldAttributes[i];
1935
+ if (!oldAttribute) continue;
1936
+ if (!newElt.hasAttribute(oldAttribute.name)) {
1937
+ if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) {
1938
+ continue;
1939
+ }
1940
+ oldElt.removeAttribute(oldAttribute.name);
1941
+ }
1942
+ }
1943
+ if (!ignoreValueOfActiveElement(oldElt, ctx)) {
1944
+ syncInputValue(oldElt, newElt, ctx);
1945
+ }
1946
+ }
1947
+ if (type === 8 || type === 3) {
1948
+ if (oldNode.nodeValue !== newNode.nodeValue) {
1949
+ oldNode.nodeValue = newNode.nodeValue;
1950
+ }
1951
+ }
1952
+ }
1953
+ function syncInputValue(oldElement, newElement, ctx) {
1954
+ if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") {
1955
+ let newValue = newElement.value;
1956
+ let oldValue = oldElement.value;
1957
+ syncBooleanAttribute(oldElement, newElement, "checked", ctx);
1958
+ syncBooleanAttribute(oldElement, newElement, "disabled", ctx);
1959
+ if (!newElement.hasAttribute("value")) {
1960
+ if (!ignoreAttribute("value", oldElement, "remove", ctx)) {
1961
+ oldElement.value = "";
1962
+ oldElement.removeAttribute("value");
1963
+ }
1964
+ } else if (oldValue !== newValue) {
1965
+ if (!ignoreAttribute("value", oldElement, "update", ctx)) {
1966
+ oldElement.setAttribute("value", newValue);
1967
+ oldElement.value = newValue;
1968
+ }
1969
+ }
1970
+ } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) {
1971
+ syncBooleanAttribute(oldElement, newElement, "selected", ctx);
1972
+ } else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
1973
+ let newValue = newElement.value;
1974
+ let oldValue = oldElement.value;
1975
+ if (ignoreAttribute("value", oldElement, "update", ctx)) {
1976
+ return;
1977
+ }
1978
+ if (newValue !== oldValue) {
1979
+ oldElement.value = newValue;
1980
+ }
1981
+ if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
1982
+ oldElement.firstChild.nodeValue = newValue;
1983
+ }
1984
+ }
1985
+ }
1986
+ function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {
1987
+ const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName];
1988
+ if (newLiveValue !== oldLiveValue) {
1989
+ const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
1990
+ if (!ignoreUpdate) {
1991
+ oldElement[attributeName] = newElement[attributeName];
1992
+ }
1993
+ if (newLiveValue) {
1994
+ if (!ignoreUpdate) {
1995
+ oldElement.setAttribute(attributeName, "");
1996
+ }
1997
+ } else {
1998
+ if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) {
1999
+ oldElement.removeAttribute(attributeName);
2000
+ }
2001
+ }
2002
+ }
2003
+ }
2004
+ function ignoreAttribute(attr, element, updateType, ctx) {
2005
+ if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
2006
+ return true;
2007
+ }
2008
+ return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false;
2009
+ }
2010
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
2011
+ return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
2012
+ }
2013
+ return morphNode;
2014
+ }();
2015
+ function withHeadBlocking(ctx, oldNode, newNode, callback) {
2016
+ if (ctx.head.block) {
2017
+ const oldHead = oldNode.querySelector("head");
2018
+ const newHead = newNode.querySelector("head");
2019
+ if (oldHead && newHead) {
2020
+ const promises = handleHeadElement(oldHead, newHead, ctx);
2021
+ return Promise.all(promises).then((() => {
2022
+ const newCtx = Object.assign(ctx, {
2023
+ head: {
2024
+ block: false,
2025
+ ignore: true
2026
+ }
2027
+ });
2028
+ return callback(newCtx);
2029
+ }));
2030
+ }
2031
+ }
2032
+ return callback(ctx);
2033
+ }
2034
+ function handleHeadElement(oldHead, newHead, ctx) {
2035
+ let added = [];
2036
+ let removed = [];
2037
+ let preserved = [];
2038
+ let nodesToAppend = [];
2039
+ let srcToNewHeadNodes = new Map;
2040
+ for (const newHeadChild of newHead.children) {
2041
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
2042
+ }
2043
+ for (const currentHeadElt of oldHead.children) {
2044
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
2045
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
2046
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
2047
+ if (inNewContent || isPreserved) {
2048
+ if (isReAppended) {
2049
+ removed.push(currentHeadElt);
2050
+ } else {
2051
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
2052
+ preserved.push(currentHeadElt);
2053
+ }
2054
+ } else {
2055
+ if (ctx.head.style === "append") {
2056
+ if (isReAppended) {
2057
+ removed.push(currentHeadElt);
2058
+ nodesToAppend.push(currentHeadElt);
2059
+ }
2060
+ } else {
2061
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
2062
+ removed.push(currentHeadElt);
2063
+ }
2064
+ }
2065
+ }
2066
+ }
2067
+ nodesToAppend.push(...srcToNewHeadNodes.values());
2068
+ let promises = [];
2069
+ for (const newNode of nodesToAppend) {
2070
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
2071
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
2072
+ if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
2073
+ let resolve;
2074
+ let promise = new Promise((function(_resolve) {
2075
+ resolve = _resolve;
2076
+ }));
2077
+ newElt.addEventListener("load", (function() {
2078
+ resolve();
2079
+ }));
2080
+ promises.push(promise);
2081
+ }
2082
+ oldHead.appendChild(newElt);
2083
+ ctx.callbacks.afterNodeAdded(newElt);
2084
+ added.push(newElt);
2085
+ }
2086
+ }
2087
+ for (const removedElement of removed) {
2088
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
2089
+ oldHead.removeChild(removedElement);
2090
+ ctx.callbacks.afterNodeRemoved(removedElement);
2091
+ }
2092
+ }
2093
+ ctx.head.afterHeadMorphed(oldHead, {
2094
+ added: added,
2095
+ kept: preserved,
2096
+ removed: removed
2097
+ });
2098
+ return promises;
2099
+ }
2100
+ const createMorphContext = function() {
2101
+ function createMorphContext(oldNode, newContent, config) {
2102
+ const {persistentIds: persistentIds, idMap: idMap} = createIdMaps(oldNode, newContent);
2103
+ const mergedConfig = mergeDefaults(config);
2104
+ const morphStyle = mergedConfig.morphStyle || "outerHTML";
2105
+ if (![ "innerHTML", "outerHTML" ].includes(morphStyle)) {
2106
+ throw `Do not understand how to morph style ${morphStyle}`;
2107
+ }
2108
+ return {
2109
+ target: oldNode,
2110
+ newContent: newContent,
2111
+ config: mergedConfig,
2112
+ morphStyle: morphStyle,
2113
+ ignoreActive: mergedConfig.ignoreActive,
2114
+ ignoreActiveValue: mergedConfig.ignoreActiveValue,
2115
+ restoreFocus: mergedConfig.restoreFocus,
2116
+ idMap: idMap,
2117
+ persistentIds: persistentIds,
2118
+ pantry: createPantry(),
2119
+ callbacks: mergedConfig.callbacks,
2120
+ head: mergedConfig.head
2121
+ };
2122
+ }
2123
+ function mergeDefaults(config) {
2124
+ let finalConfig = Object.assign({}, defaults);
2125
+ Object.assign(finalConfig, config);
2126
+ finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
2127
+ finalConfig.head = Object.assign({}, defaults.head, config.head);
2128
+ return finalConfig;
2129
+ }
2130
+ function createPantry() {
2131
+ const pantry = document.createElement("div");
2132
+ pantry.hidden = true;
2133
+ document.body.insertAdjacentElement("afterend", pantry);
2134
+ return pantry;
2135
+ }
2136
+ function findIdElements(root) {
2137
+ let elements = Array.from(root.querySelectorAll("[id]"));
2138
+ if (root.id) {
2139
+ elements.push(root);
2140
+ }
2141
+ return elements;
2142
+ }
2143
+ function populateIdMapWithTree(idMap, persistentIds, root, elements) {
2144
+ for (const elt of elements) {
2145
+ if (persistentIds.has(elt.id)) {
2146
+ let current = elt;
2147
+ while (current) {
2148
+ let idSet = idMap.get(current);
2149
+ if (idSet == null) {
2150
+ idSet = new Set;
2151
+ idMap.set(current, idSet);
2152
+ }
2153
+ idSet.add(elt.id);
2154
+ if (current === root) break;
2155
+ current = current.parentElement;
2156
+ }
2157
+ }
2158
+ }
2159
+ }
2160
+ function createIdMaps(oldContent, newContent) {
2161
+ const oldIdElements = findIdElements(oldContent);
2162
+ const newIdElements = findIdElements(newContent);
2163
+ const persistentIds = createPersistentIds(oldIdElements, newIdElements);
2164
+ let idMap = new Map;
2165
+ populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
2166
+ const newRoot = newContent.__idiomorphRoot || newContent;
2167
+ populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
2168
+ return {
2169
+ persistentIds: persistentIds,
2170
+ idMap: idMap
2171
+ };
2172
+ }
2173
+ function createPersistentIds(oldIdElements, newIdElements) {
2174
+ let duplicateIds = new Set;
2175
+ let oldIdTagNameMap = new Map;
2176
+ for (const {id: id, tagName: tagName} of oldIdElements) {
2177
+ if (oldIdTagNameMap.has(id)) {
2178
+ duplicateIds.add(id);
2179
+ } else {
2180
+ oldIdTagNameMap.set(id, tagName);
2181
+ }
2182
+ }
2183
+ let persistentIds = new Set;
2184
+ for (const {id: id, tagName: tagName} of newIdElements) {
2185
+ if (persistentIds.has(id)) {
2186
+ duplicateIds.add(id);
2187
+ } else if (oldIdTagNameMap.get(id) === tagName) {
2188
+ persistentIds.add(id);
2189
+ }
2190
+ }
2191
+ for (const id of duplicateIds) {
2192
+ persistentIds.delete(id);
2193
+ }
2194
+ return persistentIds;
2195
+ }
2196
+ return createMorphContext;
2197
+ }();
2198
+ const {normalizeElement: normalizeElement, normalizeParent: normalizeParent} = function() {
2199
+ const generatedByIdiomorph = new WeakSet;
2200
+ function normalizeElement(content) {
2201
+ if (content instanceof Document) {
2202
+ return content.documentElement;
2203
+ } else {
2204
+ return content;
2205
+ }
2206
+ }
2207
+ function normalizeParent(newContent) {
2208
+ if (newContent == null) {
2209
+ return document.createElement("div");
2210
+ } else if (typeof newContent === "string") {
2211
+ return normalizeParent(parseContent(newContent));
2212
+ } else if (generatedByIdiomorph.has(newContent)) {
2213
+ return newContent;
2214
+ } else if (newContent instanceof Node) {
2215
+ if (newContent.parentNode) {
2216
+ return createDuckTypedParent(newContent);
2217
+ } else {
2218
+ const dummyParent = document.createElement("div");
2219
+ dummyParent.append(newContent);
2220
+ return dummyParent;
2221
+ }
2222
+ } else {
2223
+ const dummyParent = document.createElement("div");
2224
+ for (const elt of [ ...newContent ]) {
2225
+ dummyParent.append(elt);
2226
+ }
2227
+ return dummyParent;
2228
+ }
2229
+ }
2230
+ function createDuckTypedParent(newContent) {
2231
+ return {
2232
+ childNodes: [ newContent ],
2233
+ querySelectorAll: s => {
2234
+ const elements = newContent.querySelectorAll(s);
2235
+ return newContent.matches(s) ? [ newContent, ...elements ] : elements;
2236
+ },
2237
+ insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
2238
+ moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
2239
+ get __idiomorphRoot() {
2240
+ return newContent;
2241
+ }
2242
+ };
2243
+ }
2244
+ function parseContent(newContent) {
2245
+ let parser = new DOMParser;
2246
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
2247
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
2248
+ let content = parser.parseFromString(newContent, "text/html");
2249
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
2250
+ generatedByIdiomorph.add(content);
2251
+ return content;
2252
+ } else {
2253
+ let htmlElement = content.firstChild;
2254
+ if (htmlElement) {
2255
+ generatedByIdiomorph.add(htmlElement);
2256
+ }
2257
+ return htmlElement;
2258
+ }
2259
+ } else {
2260
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
2261
+ let content = responseDoc.body.querySelector("template").content;
2262
+ generatedByIdiomorph.add(content);
2263
+ return content;
2264
+ }
2265
+ }
2266
+ return {
2267
+ normalizeElement: normalizeElement,
2268
+ normalizeParent: normalizeParent
2269
+ };
2270
+ }();
2271
+ return {
2272
+ morph: morph,
2273
+ defaults: defaults
2274
+ };
2275
+ }();
2276
+
2277
+ function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
2278
+ Idiomorph.morph(currentElement, newElement, {
2279
+ ...options,
2280
+ callbacks: new DefaultIdiomorphCallbacks(callbacks)
2281
+ });
2282
+ }
2283
+
2284
+ function morphChildren(currentElement, newElement) {
2285
+ morphElements(currentElement, newElement.childNodes, {
2286
+ morphStyle: "innerHTML"
2287
+ });
2288
+ }
2289
+
2290
+ class DefaultIdiomorphCallbacks {
2291
+ #beforeNodeMorphed;
2292
+ constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
2293
+ this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
2294
+ }
2295
+ beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
2296
+ beforeNodeMorphed=(currentElement, newElement) => {
2297
+ if (currentElement instanceof Element) {
2298
+ if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
2299
+ const event = dispatch("turbo:before-morph-element", {
2300
+ cancelable: true,
2301
+ target: currentElement,
2302
+ detail: {
2303
+ currentElement: currentElement,
2304
+ newElement: newElement
2305
+ }
2306
+ });
2307
+ return !event.defaultPrevented;
2308
+ } else {
2309
+ return false;
2310
+ }
2311
+ }
2312
+ };
2313
+ beforeAttributeUpdated=(attributeName, target, mutationType) => {
2314
+ const event = dispatch("turbo:before-morph-attribute", {
2315
+ cancelable: true,
2316
+ target: target,
2317
+ detail: {
2318
+ attributeName: attributeName,
2319
+ mutationType: mutationType
2320
+ }
2321
+ });
2322
+ return !event.defaultPrevented;
2323
+ };
2324
+ beforeNodeRemoved=node => this.beforeNodeMorphed(node);
2325
+ afterNodeMorphed=(currentElement, newElement) => {
2326
+ if (currentElement instanceof Element) {
2327
+ dispatch("turbo:morph-element", {
2328
+ target: currentElement,
2329
+ detail: {
2330
+ currentElement: currentElement,
2331
+ newElement: newElement
2332
+ }
2333
+ });
2334
+ }
2335
+ };
2336
+ }
2337
+
2338
+ class MorphingFrameRenderer extends FrameRenderer {
2339
+ static renderElement(currentElement, newElement) {
2340
+ dispatch("turbo:before-frame-morph", {
2341
+ target: currentElement,
2342
+ detail: {
2343
+ currentElement: currentElement,
2344
+ newElement: newElement
2345
+ }
2346
+ });
2347
+ morphChildren(currentElement, newElement);
2348
+ }
2349
+ async preservingPermanentElements(callback) {
2350
+ return await callback();
2351
+ }
2352
+ }
2353
+
2354
+ class ProgressBar {
2355
+ static animationDuration=300;
2356
+ static get defaultCSS() {
2357
+ return unindent`
2358
+ .turbo-progress-bar {
2359
+ position: fixed;
2360
+ display: block;
2361
+ top: 0;
2362
+ left: 0;
2363
+ height: 3px;
2364
+ background: #0076ff;
2365
+ z-index: 2147483647;
2366
+ transition:
2367
+ width ${ProgressBar.animationDuration}ms ease-out,
2368
+ opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
2369
+ transform: translate3d(0, 0, 0);
2370
+ }
2371
+ `;
2372
+ }
2373
+ hiding=false;
2374
+ value=0;
2375
+ visible=false;
1651
2376
  constructor() {
1652
2377
  this.stylesheetElement = this.createStylesheetElement();
1653
2378
  this.progressElement = this.createProgressElement();
@@ -1715,8 +2440,9 @@ class ProgressBar {
1715
2440
  const element = document.createElement("style");
1716
2441
  element.type = "text/css";
1717
2442
  element.textContent = ProgressBar.defaultCSS;
1718
- if (this.cspNonce) {
1719
- element.nonce = this.cspNonce;
2443
+ const cspNonce = getCspNonce();
2444
+ if (cspNonce) {
2445
+ element.nonce = cspNonce;
1720
2446
  }
1721
2447
  return element;
1722
2448
  }
@@ -1725,9 +2451,6 @@ class ProgressBar {
1725
2451
  element.className = "turbo-progress-bar";
1726
2452
  return element;
1727
2453
  }
1728
- get cspNonce() {
1729
- return getMetaContent("csp-nonce");
1730
- }
1731
2454
  }
1732
2455
 
1733
2456
  class HeadSnapshot extends Snapshot {
@@ -2230,16 +2953,6 @@ class Visit {
2230
2953
  ...this.timingMetrics
2231
2954
  };
2232
2955
  }
2233
- getHistoryMethodForAction(action) {
2234
- switch (action) {
2235
- case "replace":
2236
- return history.replaceState;
2237
-
2238
- case "advance":
2239
- case "restore":
2240
- return history.pushState;
2241
- }
2242
- }
2243
2956
  hasPreloadedResponse() {
2244
2957
  return typeof this.response == "object";
2245
2958
  }
@@ -2260,7 +2973,9 @@ class Visit {
2260
2973
  }
2261
2974
  async render(callback) {
2262
2975
  this.cancelRender();
2263
- this.frame = await nextRepaint();
2976
+ await new Promise((resolve => {
2977
+ this.frame = document.visibilityState === "hidden" ? setTimeout((() => resolve()), 0) : requestAnimationFrame((() => resolve()));
2978
+ }));
2264
2979
  await callback();
2265
2980
  delete this.frame;
2266
2981
  }
@@ -2340,6 +3055,9 @@ class BrowserAdapter {
2340
3055
  this.hideVisitProgressBar();
2341
3056
  }
2342
3057
  visitRendered(_visit) {}
3058
+ linkPrefetchingIsEnabledForLocation(location) {
3059
+ return true;
3060
+ }
2343
3061
  formSubmissionStarted(_formSubmission) {
2344
3062
  this.progressBar.setValue(0);
2345
3063
  this.showFormProgressBarAfterDelay();
@@ -2796,913 +3514,305 @@ class Navigator {
2796
3514
  this.adapter.formSubmissionFinished(formSubmission);
2797
3515
  }
2798
3516
  }
2799
- visitStarted(visit) {
2800
- this.delegate.visitStarted(visit);
2801
- }
2802
- visitCompleted(visit) {
2803
- this.delegate.visitCompleted(visit);
2804
- delete this.currentVisit;
2805
- }
2806
- locationWithActionIsSamePage(location, action) {
2807
- const anchor = getAnchor(location);
2808
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2809
- const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2810
- return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2811
- }
2812
- visitScrolledToSamePageLocation(oldURL, newURL) {
2813
- this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
2814
- }
2815
- get location() {
2816
- return this.history.location;
2817
- }
2818
- get restorationIdentifier() {
2819
- return this.history.restorationIdentifier;
2820
- }
2821
- #getActionForFormSubmission(formSubmission, fetchResponse) {
2822
- const {submitter: submitter, formElement: formElement} = formSubmission;
2823
- return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
2824
- }
2825
- #getDefaultAction(fetchResponse) {
2826
- const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
2827
- return sameLocationRedirect ? "replace" : "advance";
2828
- }
2829
- }
2830
-
2831
- const PageStage = {
2832
- initial: 0,
2833
- loading: 1,
2834
- interactive: 2,
2835
- complete: 3
2836
- };
2837
-
2838
- class PageObserver {
2839
- stage=PageStage.initial;
2840
- started=false;
2841
- constructor(delegate) {
2842
- this.delegate = delegate;
2843
- }
2844
- start() {
2845
- if (!this.started) {
2846
- if (this.stage == PageStage.initial) {
2847
- this.stage = PageStage.loading;
2848
- }
2849
- document.addEventListener("readystatechange", this.interpretReadyState, false);
2850
- addEventListener("pagehide", this.pageWillUnload, false);
2851
- this.started = true;
2852
- }
2853
- }
2854
- stop() {
2855
- if (this.started) {
2856
- document.removeEventListener("readystatechange", this.interpretReadyState, false);
2857
- removeEventListener("pagehide", this.pageWillUnload, false);
2858
- this.started = false;
2859
- }
2860
- }
2861
- interpretReadyState=() => {
2862
- const {readyState: readyState} = this;
2863
- if (readyState == "interactive") {
2864
- this.pageIsInteractive();
2865
- } else if (readyState == "complete") {
2866
- this.pageIsComplete();
2867
- }
2868
- };
2869
- pageIsInteractive() {
2870
- if (this.stage == PageStage.loading) {
2871
- this.stage = PageStage.interactive;
2872
- this.delegate.pageBecameInteractive();
2873
- }
2874
- }
2875
- pageIsComplete() {
2876
- this.pageIsInteractive();
2877
- if (this.stage == PageStage.interactive) {
2878
- this.stage = PageStage.complete;
2879
- this.delegate.pageLoaded();
2880
- }
2881
- }
2882
- pageWillUnload=() => {
2883
- this.delegate.pageWillUnload();
2884
- };
2885
- get readyState() {
2886
- return document.readyState;
2887
- }
2888
- }
2889
-
2890
- class ScrollObserver {
2891
- started=false;
2892
- constructor(delegate) {
2893
- this.delegate = delegate;
2894
- }
2895
- start() {
2896
- if (!this.started) {
2897
- addEventListener("scroll", this.onScroll, false);
2898
- this.onScroll();
2899
- this.started = true;
2900
- }
2901
- }
2902
- stop() {
2903
- if (this.started) {
2904
- removeEventListener("scroll", this.onScroll, false);
2905
- this.started = false;
2906
- }
2907
- }
2908
- onScroll=() => {
2909
- this.updatePosition({
2910
- x: window.pageXOffset,
2911
- y: window.pageYOffset
2912
- });
2913
- };
2914
- updatePosition(position) {
2915
- this.delegate.scrollPositionChanged(position);
2916
- }
2917
- }
2918
-
2919
- class StreamMessageRenderer {
2920
- render({fragment: fragment}) {
2921
- Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
2922
- withAutofocusFromFragment(fragment, (() => {
2923
- withPreservedFocus((() => {
2924
- document.documentElement.appendChild(fragment);
2925
- }));
2926
- }));
2927
- }));
2928
- }
2929
- enteringBardo(currentPermanentElement, newPermanentElement) {
2930
- newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2931
- }
2932
- leavingBardo() {}
2933
- }
2934
-
2935
- function getPermanentElementMapForFragment(fragment) {
2936
- const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2937
- const permanentElementMap = {};
2938
- for (const permanentElementInDocument of permanentElementsInDocument) {
2939
- const {id: id} = permanentElementInDocument;
2940
- for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2941
- const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2942
- if (elementInStream) {
2943
- permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
2944
- }
2945
- }
2946
- }
2947
- return permanentElementMap;
2948
- }
2949
-
2950
- async function withAutofocusFromFragment(fragment, callback) {
2951
- const generatedID = `turbo-stream-autofocus-${uuid()}`;
2952
- const turboStreams = fragment.querySelectorAll("turbo-stream");
2953
- const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
2954
- let willAutofocusId = null;
2955
- if (elementWithAutofocus) {
2956
- if (elementWithAutofocus.id) {
2957
- willAutofocusId = elementWithAutofocus.id;
2958
- } else {
2959
- willAutofocusId = generatedID;
2960
- }
2961
- elementWithAutofocus.id = willAutofocusId;
2962
- }
2963
- callback();
2964
- await nextRepaint();
2965
- const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
2966
- if (hasNoActiveElement && willAutofocusId) {
2967
- const elementToAutofocus = document.getElementById(willAutofocusId);
2968
- if (elementIsFocusable(elementToAutofocus)) {
2969
- elementToAutofocus.focus();
2970
- }
2971
- if (elementToAutofocus && elementToAutofocus.id == generatedID) {
2972
- elementToAutofocus.removeAttribute("id");
2973
- }
2974
- }
2975
- }
2976
-
2977
- async function withPreservedFocus(callback) {
2978
- const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
2979
- const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
2980
- if (restoreFocusTo) {
2981
- const elementToFocus = document.getElementById(restoreFocusTo);
2982
- if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
2983
- elementToFocus.focus();
2984
- }
2985
- }
2986
- }
2987
-
2988
- function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
2989
- for (const streamElement of nodeListOfStreamElements) {
2990
- const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
2991
- if (elementWithAutofocus) return elementWithAutofocus;
2992
- }
2993
- return null;
2994
- }
2995
-
2996
- class StreamObserver {
2997
- sources=new Set;
2998
- #started=false;
2999
- constructor(delegate) {
3000
- this.delegate = delegate;
3001
- }
3002
- start() {
3003
- if (!this.#started) {
3004
- this.#started = true;
3005
- addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
3006
- }
3007
- }
3008
- stop() {
3009
- if (this.#started) {
3010
- this.#started = false;
3011
- removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
3012
- }
3013
- }
3014
- connectStreamSource(source) {
3015
- if (!this.streamSourceIsConnected(source)) {
3016
- this.sources.add(source);
3017
- source.addEventListener("message", this.receiveMessageEvent, false);
3018
- }
3019
- }
3020
- disconnectStreamSource(source) {
3021
- if (this.streamSourceIsConnected(source)) {
3022
- this.sources.delete(source);
3023
- source.removeEventListener("message", this.receiveMessageEvent, false);
3024
- }
3025
- }
3026
- streamSourceIsConnected(source) {
3027
- return this.sources.has(source);
3028
- }
3029
- inspectFetchResponse=event => {
3030
- const response = fetchResponseFromEvent(event);
3031
- if (response && fetchResponseIsStream(response)) {
3032
- event.preventDefault();
3033
- this.receiveMessageResponse(response);
3034
- }
3035
- };
3036
- receiveMessageEvent=event => {
3037
- if (this.#started && typeof event.data == "string") {
3038
- this.receiveMessageHTML(event.data);
3039
- }
3040
- };
3041
- async receiveMessageResponse(response) {
3042
- const html = await response.responseHTML;
3043
- if (html) {
3044
- this.receiveMessageHTML(html);
3517
+ linkPrefetchingIsEnabledForLocation(location) {
3518
+ if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") {
3519
+ return this.adapter.linkPrefetchingIsEnabledForLocation(location);
3045
3520
  }
3521
+ return true;
3046
3522
  }
3047
- receiveMessageHTML(html) {
3048
- this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
3523
+ visitStarted(visit) {
3524
+ this.delegate.visitStarted(visit);
3049
3525
  }
3050
- }
3051
-
3052
- function fetchResponseFromEvent(event) {
3053
- const fetchResponse = event.detail?.fetchResponse;
3054
- if (fetchResponse instanceof FetchResponse) {
3055
- return fetchResponse;
3526
+ visitCompleted(visit) {
3527
+ this.delegate.visitCompleted(visit);
3528
+ delete this.currentVisit;
3056
3529
  }
3057
- }
3058
-
3059
- function fetchResponseIsStream(response) {
3060
- const contentType = response.contentType ?? "";
3061
- return contentType.startsWith(StreamMessage.contentType);
3062
- }
3063
-
3064
- class ErrorRenderer extends Renderer {
3065
- static renderElement(currentElement, newElement) {
3066
- const {documentElement: documentElement, body: body} = document;
3067
- documentElement.replaceChild(newElement, body);
3530
+ locationWithActionIsSamePage(location, action) {
3531
+ const anchor = getAnchor(location);
3532
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
3533
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
3534
+ return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
3068
3535
  }
3069
- async render() {
3070
- this.replaceHeadAndBody();
3071
- this.activateScriptElements();
3536
+ visitScrolledToSamePageLocation(oldURL, newURL) {
3537
+ this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
3072
3538
  }
3073
- replaceHeadAndBody() {
3074
- const {documentElement: documentElement, head: head} = document;
3075
- documentElement.replaceChild(this.newHead, head);
3076
- this.renderElement(this.currentElement, this.newElement);
3539
+ get location() {
3540
+ return this.history.location;
3077
3541
  }
3078
- activateScriptElements() {
3079
- for (const replaceableElement of this.scriptElements) {
3080
- const parentNode = replaceableElement.parentNode;
3081
- if (parentNode) {
3082
- const element = activateScriptElement(replaceableElement);
3083
- parentNode.replaceChild(element, replaceableElement);
3084
- }
3085
- }
3542
+ get restorationIdentifier() {
3543
+ return this.history.restorationIdentifier;
3086
3544
  }
3087
- get newHead() {
3088
- return this.newSnapshot.headSnapshot.element;
3545
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
3546
+ const {submitter: submitter, formElement: formElement} = formSubmission;
3547
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
3089
3548
  }
3090
- get scriptElements() {
3091
- return document.documentElement.querySelectorAll("script");
3549
+ #getDefaultAction(fetchResponse) {
3550
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
3551
+ return sameLocationRedirect ? "replace" : "advance";
3092
3552
  }
3093
3553
  }
3094
3554
 
3095
- var Idiomorph = function() {
3096
- let EMPTY_SET = new Set;
3097
- let defaults = {
3098
- morphStyle: "outerHTML",
3099
- callbacks: {
3100
- beforeNodeAdded: noOp,
3101
- afterNodeAdded: noOp,
3102
- beforeNodeMorphed: noOp,
3103
- afterNodeMorphed: noOp,
3104
- beforeNodeRemoved: noOp,
3105
- afterNodeRemoved: noOp,
3106
- beforeAttributeUpdated: noOp
3107
- },
3108
- head: {
3109
- style: "merge",
3110
- shouldPreserve: function(elt) {
3111
- return elt.getAttribute("im-preserve") === "true";
3112
- },
3113
- shouldReAppend: function(elt) {
3114
- return elt.getAttribute("im-re-append") === "true";
3115
- },
3116
- shouldRemove: noOp,
3117
- afterHeadMorphed: noOp
3118
- }
3119
- };
3120
- function morph(oldNode, newContent, config = {}) {
3121
- if (oldNode instanceof Document) {
3122
- oldNode = oldNode.documentElement;
3123
- }
3124
- if (typeof newContent === "string") {
3125
- newContent = parseContent(newContent);
3126
- }
3127
- let normalizedContent = normalizeContent(newContent);
3128
- let ctx = createMorphContext(oldNode, normalizedContent, config);
3129
- return morphNormalizedContent(oldNode, normalizedContent, ctx);
3555
+ const PageStage = {
3556
+ initial: 0,
3557
+ loading: 1,
3558
+ interactive: 2,
3559
+ complete: 3
3560
+ };
3561
+
3562
+ class PageObserver {
3563
+ stage=PageStage.initial;
3564
+ started=false;
3565
+ constructor(delegate) {
3566
+ this.delegate = delegate;
3130
3567
  }
3131
- function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
3132
- if (ctx.head.block) {
3133
- let oldHead = oldNode.querySelector("head");
3134
- let newHead = normalizedNewContent.querySelector("head");
3135
- if (oldHead && newHead) {
3136
- let promises = handleHeadElement(newHead, oldHead, ctx);
3137
- Promise.all(promises).then((function() {
3138
- morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
3139
- head: {
3140
- block: false,
3141
- ignore: true
3142
- }
3143
- }));
3144
- }));
3145
- return;
3146
- }
3147
- }
3148
- if (ctx.morphStyle === "innerHTML") {
3149
- morphChildren(normalizedNewContent, oldNode, ctx);
3150
- return oldNode.children;
3151
- } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
3152
- let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
3153
- let previousSibling = bestMatch?.previousSibling;
3154
- let nextSibling = bestMatch?.nextSibling;
3155
- let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
3156
- if (bestMatch) {
3157
- return insertSiblings(previousSibling, morphedNode, nextSibling);
3158
- } else {
3159
- return [];
3568
+ start() {
3569
+ if (!this.started) {
3570
+ if (this.stage == PageStage.initial) {
3571
+ this.stage = PageStage.loading;
3160
3572
  }
3161
- } else {
3162
- throw "Do not understand how to morph style " + ctx.morphStyle;
3573
+ document.addEventListener("readystatechange", this.interpretReadyState, false);
3574
+ addEventListener("pagehide", this.pageWillUnload, false);
3575
+ this.started = true;
3163
3576
  }
3164
3577
  }
3165
- function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
3166
- return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
3167
- }
3168
- function morphOldNodeTo(oldNode, newContent, ctx) {
3169
- if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
3170
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3171
- oldNode.remove();
3172
- ctx.callbacks.afterNodeRemoved(oldNode);
3173
- return null;
3174
- } else if (!isSoftMatch(oldNode, newContent)) {
3175
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3176
- if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
3177
- oldNode.parentElement.replaceChild(newContent, oldNode);
3178
- ctx.callbacks.afterNodeAdded(newContent);
3179
- ctx.callbacks.afterNodeRemoved(oldNode);
3180
- return newContent;
3181
- } else {
3182
- if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
3183
- if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
3184
- handleHeadElement(newContent, oldNode, ctx);
3185
- } else {
3186
- syncNodeFrom(newContent, oldNode, ctx);
3187
- if (!ignoreValueOfActiveElement(oldNode, ctx)) {
3188
- morphChildren(newContent, oldNode, ctx);
3189
- }
3190
- }
3191
- ctx.callbacks.afterNodeMorphed(oldNode, newContent);
3192
- return oldNode;
3578
+ stop() {
3579
+ if (this.started) {
3580
+ document.removeEventListener("readystatechange", this.interpretReadyState, false);
3581
+ removeEventListener("pagehide", this.pageWillUnload, false);
3582
+ this.started = false;
3193
3583
  }
3194
3584
  }
3195
- function morphChildren(newParent, oldParent, ctx) {
3196
- let nextNewChild = newParent.firstChild;
3197
- let insertionPoint = oldParent.firstChild;
3198
- let newChild;
3199
- while (nextNewChild) {
3200
- newChild = nextNewChild;
3201
- nextNewChild = newChild.nextSibling;
3202
- if (insertionPoint == null) {
3203
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3204
- oldParent.appendChild(newChild);
3205
- ctx.callbacks.afterNodeAdded(newChild);
3206
- removeIdsFromConsideration(ctx, newChild);
3207
- continue;
3208
- }
3209
- if (isIdSetMatch(newChild, insertionPoint, ctx)) {
3210
- morphOldNodeTo(insertionPoint, newChild, ctx);
3211
- insertionPoint = insertionPoint.nextSibling;
3212
- removeIdsFromConsideration(ctx, newChild);
3213
- continue;
3214
- }
3215
- let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3216
- if (idSetMatch) {
3217
- insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
3218
- morphOldNodeTo(idSetMatch, newChild, ctx);
3219
- removeIdsFromConsideration(ctx, newChild);
3220
- continue;
3221
- }
3222
- let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3223
- if (softMatch) {
3224
- insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
3225
- morphOldNodeTo(softMatch, newChild, ctx);
3226
- removeIdsFromConsideration(ctx, newChild);
3227
- continue;
3228
- }
3229
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3230
- oldParent.insertBefore(newChild, insertionPoint);
3231
- ctx.callbacks.afterNodeAdded(newChild);
3232
- removeIdsFromConsideration(ctx, newChild);
3585
+ interpretReadyState=() => {
3586
+ const {readyState: readyState} = this;
3587
+ if (readyState == "interactive") {
3588
+ this.pageIsInteractive();
3589
+ } else if (readyState == "complete") {
3590
+ this.pageIsComplete();
3233
3591
  }
3234
- while (insertionPoint !== null) {
3235
- let tempNode = insertionPoint;
3236
- insertionPoint = insertionPoint.nextSibling;
3237
- removeNode(tempNode, ctx);
3592
+ };
3593
+ pageIsInteractive() {
3594
+ if (this.stage == PageStage.loading) {
3595
+ this.stage = PageStage.interactive;
3596
+ this.delegate.pageBecameInteractive();
3238
3597
  }
3239
3598
  }
3240
- function ignoreAttribute(attr, to, updateType, ctx) {
3241
- if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
3242
- return true;
3599
+ pageIsComplete() {
3600
+ this.pageIsInteractive();
3601
+ if (this.stage == PageStage.interactive) {
3602
+ this.stage = PageStage.complete;
3603
+ this.delegate.pageLoaded();
3243
3604
  }
3244
- return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
3245
3605
  }
3246
- function syncNodeFrom(from, to, ctx) {
3247
- let type = from.nodeType;
3248
- if (type === 1) {
3249
- const fromAttributes = from.attributes;
3250
- const toAttributes = to.attributes;
3251
- for (const fromAttribute of fromAttributes) {
3252
- if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
3253
- continue;
3254
- }
3255
- if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
3256
- to.setAttribute(fromAttribute.name, fromAttribute.value);
3257
- }
3258
- }
3259
- for (let i = toAttributes.length - 1; 0 <= i; i--) {
3260
- const toAttribute = toAttributes[i];
3261
- if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
3262
- continue;
3263
- }
3264
- if (!from.hasAttribute(toAttribute.name)) {
3265
- to.removeAttribute(toAttribute.name);
3266
- }
3267
- }
3268
- }
3269
- if (type === 8 || type === 3) {
3270
- if (to.nodeValue !== from.nodeValue) {
3271
- to.nodeValue = from.nodeValue;
3272
- }
3273
- }
3274
- if (!ignoreValueOfActiveElement(to, ctx)) {
3275
- syncInputValue(from, to, ctx);
3276
- }
3606
+ pageWillUnload=() => {
3607
+ this.delegate.pageWillUnload();
3608
+ };
3609
+ get readyState() {
3610
+ return document.readyState;
3277
3611
  }
3278
- function syncBooleanAttribute(from, to, attributeName, ctx) {
3279
- if (from[attributeName] !== to[attributeName]) {
3280
- let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
3281
- if (!ignoreUpdate) {
3282
- to[attributeName] = from[attributeName];
3283
- }
3284
- if (from[attributeName]) {
3285
- if (!ignoreUpdate) {
3286
- to.setAttribute(attributeName, from[attributeName]);
3287
- }
3288
- } else {
3289
- if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
3290
- to.removeAttribute(attributeName);
3291
- }
3292
- }
3293
- }
3612
+ }
3613
+
3614
+ class ScrollObserver {
3615
+ started=false;
3616
+ constructor(delegate) {
3617
+ this.delegate = delegate;
3294
3618
  }
3295
- function syncInputValue(from, to, ctx) {
3296
- if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
3297
- let fromValue = from.value;
3298
- let toValue = to.value;
3299
- syncBooleanAttribute(from, to, "checked", ctx);
3300
- syncBooleanAttribute(from, to, "disabled", ctx);
3301
- if (!from.hasAttribute("value")) {
3302
- if (!ignoreAttribute("value", to, "remove", ctx)) {
3303
- to.value = "";
3304
- to.removeAttribute("value");
3305
- }
3306
- } else if (fromValue !== toValue) {
3307
- if (!ignoreAttribute("value", to, "update", ctx)) {
3308
- to.setAttribute("value", fromValue);
3309
- to.value = fromValue;
3310
- }
3311
- }
3312
- } else if (from instanceof HTMLOptionElement) {
3313
- syncBooleanAttribute(from, to, "selected", ctx);
3314
- } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
3315
- let fromValue = from.value;
3316
- let toValue = to.value;
3317
- if (ignoreAttribute("value", to, "update", ctx)) {
3318
- return;
3319
- }
3320
- if (fromValue !== toValue) {
3321
- to.value = fromValue;
3322
- }
3323
- if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
3324
- to.firstChild.nodeValue = fromValue;
3325
- }
3619
+ start() {
3620
+ if (!this.started) {
3621
+ addEventListener("scroll", this.onScroll, false);
3622
+ this.onScroll();
3623
+ this.started = true;
3326
3624
  }
3327
3625
  }
3328
- function handleHeadElement(newHeadTag, currentHead, ctx) {
3329
- let added = [];
3330
- let removed = [];
3331
- let preserved = [];
3332
- let nodesToAppend = [];
3333
- let headMergeStyle = ctx.head.style;
3334
- let srcToNewHeadNodes = new Map;
3335
- for (const newHeadChild of newHeadTag.children) {
3336
- srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
3337
- }
3338
- for (const currentHeadElt of currentHead.children) {
3339
- let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
3340
- let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
3341
- let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
3342
- if (inNewContent || isPreserved) {
3343
- if (isReAppended) {
3344
- removed.push(currentHeadElt);
3345
- } else {
3346
- srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
3347
- preserved.push(currentHeadElt);
3348
- }
3349
- } else {
3350
- if (headMergeStyle === "append") {
3351
- if (isReAppended) {
3352
- removed.push(currentHeadElt);
3353
- nodesToAppend.push(currentHeadElt);
3354
- }
3355
- } else {
3356
- if (ctx.head.shouldRemove(currentHeadElt) !== false) {
3357
- removed.push(currentHeadElt);
3358
- }
3359
- }
3360
- }
3361
- }
3362
- nodesToAppend.push(...srcToNewHeadNodes.values());
3363
- let promises = [];
3364
- for (const newNode of nodesToAppend) {
3365
- let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
3366
- if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
3367
- if (newElt.href || newElt.src) {
3368
- let resolve = null;
3369
- let promise = new Promise((function(_resolve) {
3370
- resolve = _resolve;
3371
- }));
3372
- newElt.addEventListener("load", (function() {
3373
- resolve();
3374
- }));
3375
- promises.push(promise);
3376
- }
3377
- currentHead.appendChild(newElt);
3378
- ctx.callbacks.afterNodeAdded(newElt);
3379
- added.push(newElt);
3380
- }
3381
- }
3382
- for (const removedElement of removed) {
3383
- if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
3384
- currentHead.removeChild(removedElement);
3385
- ctx.callbacks.afterNodeRemoved(removedElement);
3386
- }
3626
+ stop() {
3627
+ if (this.started) {
3628
+ removeEventListener("scroll", this.onScroll, false);
3629
+ this.started = false;
3387
3630
  }
3388
- ctx.head.afterHeadMorphed(currentHead, {
3389
- added: added,
3390
- kept: preserved,
3391
- removed: removed
3392
- });
3393
- return promises;
3394
3631
  }
3395
- function noOp() {}
3396
- function mergeDefaults(config) {
3397
- let finalConfig = {};
3398
- Object.assign(finalConfig, defaults);
3399
- Object.assign(finalConfig, config);
3400
- finalConfig.callbacks = {};
3401
- Object.assign(finalConfig.callbacks, defaults.callbacks);
3402
- Object.assign(finalConfig.callbacks, config.callbacks);
3403
- finalConfig.head = {};
3404
- Object.assign(finalConfig.head, defaults.head);
3405
- Object.assign(finalConfig.head, config.head);
3406
- return finalConfig;
3407
- }
3408
- function createMorphContext(oldNode, newContent, config) {
3409
- config = mergeDefaults(config);
3410
- return {
3411
- target: oldNode,
3412
- newContent: newContent,
3413
- config: config,
3414
- morphStyle: config.morphStyle,
3415
- ignoreActive: config.ignoreActive,
3416
- ignoreActiveValue: config.ignoreActiveValue,
3417
- idMap: createIdMap(oldNode, newContent),
3418
- deadIds: new Set,
3419
- callbacks: config.callbacks,
3420
- head: config.head
3421
- };
3632
+ onScroll=() => {
3633
+ this.updatePosition({
3634
+ x: window.pageXOffset,
3635
+ y: window.pageYOffset
3636
+ });
3637
+ };
3638
+ updatePosition(position) {
3639
+ this.delegate.scrollPositionChanged(position);
3422
3640
  }
3423
- function isIdSetMatch(node1, node2, ctx) {
3424
- if (node1 == null || node2 == null) {
3425
- return false;
3426
- }
3427
- if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
3428
- if (node1.id !== "" && node1.id === node2.id) {
3429
- return true;
3430
- } else {
3431
- return getIdIntersectionCount(ctx, node1, node2) > 0;
3432
- }
3433
- }
3434
- return false;
3641
+ }
3642
+
3643
+ class StreamMessageRenderer {
3644
+ render({fragment: fragment}) {
3645
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
3646
+ withAutofocusFromFragment(fragment, (() => {
3647
+ withPreservedFocus((() => {
3648
+ document.documentElement.appendChild(fragment);
3649
+ }));
3650
+ }));
3651
+ }));
3435
3652
  }
3436
- function isSoftMatch(node1, node2) {
3437
- if (node1 == null || node2 == null) {
3438
- return false;
3439
- }
3440
- return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
3441
- }
3442
- function removeNodesBetween(startInclusive, endExclusive, ctx) {
3443
- while (startInclusive !== endExclusive) {
3444
- let tempNode = startInclusive;
3445
- startInclusive = startInclusive.nextSibling;
3446
- removeNode(tempNode, ctx);
3447
- }
3448
- removeIdsFromConsideration(ctx, endExclusive);
3449
- return endExclusive.nextSibling;
3450
- }
3451
- function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3452
- let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
3453
- let potentialMatch = null;
3454
- if (newChildPotentialIdCount > 0) {
3455
- let potentialMatch = insertionPoint;
3456
- let otherMatchCount = 0;
3457
- while (potentialMatch != null) {
3458
- if (isIdSetMatch(newChild, potentialMatch, ctx)) {
3459
- return potentialMatch;
3460
- }
3461
- otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
3462
- if (otherMatchCount > newChildPotentialIdCount) {
3463
- return null;
3464
- }
3465
- potentialMatch = potentialMatch.nextSibling;
3466
- }
3467
- }
3468
- return potentialMatch;
3653
+ enteringBardo(currentPermanentElement, newPermanentElement) {
3654
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
3469
3655
  }
3470
- function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3471
- let potentialSoftMatch = insertionPoint;
3472
- let nextSibling = newChild.nextSibling;
3473
- let siblingSoftMatchCount = 0;
3474
- while (potentialSoftMatch != null) {
3475
- if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
3476
- return null;
3477
- }
3478
- if (isSoftMatch(newChild, potentialSoftMatch)) {
3479
- return potentialSoftMatch;
3480
- }
3481
- if (isSoftMatch(nextSibling, potentialSoftMatch)) {
3482
- siblingSoftMatchCount++;
3483
- nextSibling = nextSibling.nextSibling;
3484
- if (siblingSoftMatchCount >= 2) {
3485
- return null;
3486
- }
3656
+ leavingBardo() {}
3657
+ }
3658
+
3659
+ function getPermanentElementMapForFragment(fragment) {
3660
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
3661
+ const permanentElementMap = {};
3662
+ for (const permanentElementInDocument of permanentElementsInDocument) {
3663
+ const {id: id} = permanentElementInDocument;
3664
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
3665
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
3666
+ if (elementInStream) {
3667
+ permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
3487
3668
  }
3488
- potentialSoftMatch = potentialSoftMatch.nextSibling;
3489
3669
  }
3490
- return potentialSoftMatch;
3491
3670
  }
3492
- function parseContent(newContent) {
3493
- let parser = new DOMParser;
3494
- let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
3495
- if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
3496
- let content = parser.parseFromString(newContent, "text/html");
3497
- if (contentWithSvgsRemoved.match(/<\/html>/)) {
3498
- content.generatedByIdiomorph = true;
3499
- return content;
3500
- } else {
3501
- let htmlElement = content.firstChild;
3502
- if (htmlElement) {
3503
- htmlElement.generatedByIdiomorph = true;
3504
- return htmlElement;
3505
- } else {
3506
- return null;
3507
- }
3508
- }
3509
- } else {
3510
- let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
3511
- let content = responseDoc.body.querySelector("template").content;
3512
- content.generatedByIdiomorph = true;
3513
- return content;
3514
- }
3515
- }
3516
- function normalizeContent(newContent) {
3517
- if (newContent == null) {
3518
- const dummyParent = document.createElement("div");
3519
- return dummyParent;
3520
- } else if (newContent.generatedByIdiomorph) {
3521
- return newContent;
3522
- } else if (newContent instanceof Node) {
3523
- const dummyParent = document.createElement("div");
3524
- dummyParent.append(newContent);
3525
- return dummyParent;
3671
+ return permanentElementMap;
3672
+ }
3673
+
3674
+ async function withAutofocusFromFragment(fragment, callback) {
3675
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
3676
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
3677
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
3678
+ let willAutofocusId = null;
3679
+ if (elementWithAutofocus) {
3680
+ if (elementWithAutofocus.id) {
3681
+ willAutofocusId = elementWithAutofocus.id;
3526
3682
  } else {
3527
- const dummyParent = document.createElement("div");
3528
- for (const elt of [ ...newContent ]) {
3529
- dummyParent.append(elt);
3530
- }
3531
- return dummyParent;
3683
+ willAutofocusId = generatedID;
3532
3684
  }
3685
+ elementWithAutofocus.id = willAutofocusId;
3533
3686
  }
3534
- function insertSiblings(previousSibling, morphedNode, nextSibling) {
3535
- let stack = [];
3536
- let added = [];
3537
- while (previousSibling != null) {
3538
- stack.push(previousSibling);
3539
- previousSibling = previousSibling.previousSibling;
3540
- }
3541
- while (stack.length > 0) {
3542
- let node = stack.pop();
3543
- added.push(node);
3544
- morphedNode.parentElement.insertBefore(node, morphedNode);
3545
- }
3546
- added.push(morphedNode);
3547
- while (nextSibling != null) {
3548
- stack.push(nextSibling);
3549
- added.push(nextSibling);
3550
- nextSibling = nextSibling.nextSibling;
3551
- }
3552
- while (stack.length > 0) {
3553
- morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
3554
- }
3555
- return added;
3556
- }
3557
- function findBestNodeMatch(newContent, oldNode, ctx) {
3558
- let currentElement;
3559
- currentElement = newContent.firstChild;
3560
- let bestElement = currentElement;
3561
- let score = 0;
3562
- while (currentElement) {
3563
- let newScore = scoreElement(currentElement, oldNode, ctx);
3564
- if (newScore > score) {
3565
- bestElement = currentElement;
3566
- score = newScore;
3567
- }
3568
- currentElement = currentElement.nextSibling;
3687
+ callback();
3688
+ await nextRepaint();
3689
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
3690
+ if (hasNoActiveElement && willAutofocusId) {
3691
+ const elementToAutofocus = document.getElementById(willAutofocusId);
3692
+ if (elementIsFocusable(elementToAutofocus)) {
3693
+ elementToAutofocus.focus();
3694
+ }
3695
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
3696
+ elementToAutofocus.removeAttribute("id");
3569
3697
  }
3570
- return bestElement;
3571
3698
  }
3572
- function scoreElement(node1, node2, ctx) {
3573
- if (isSoftMatch(node1, node2)) {
3574
- return .5 + getIdIntersectionCount(ctx, node1, node2);
3699
+ }
3700
+
3701
+ async function withPreservedFocus(callback) {
3702
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
3703
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
3704
+ if (restoreFocusTo) {
3705
+ const elementToFocus = document.getElementById(restoreFocusTo);
3706
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
3707
+ elementToFocus.focus();
3575
3708
  }
3576
- return 0;
3577
3709
  }
3578
- function removeNode(tempNode, ctx) {
3579
- removeIdsFromConsideration(ctx, tempNode);
3580
- if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
3581
- tempNode.remove();
3582
- ctx.callbacks.afterNodeRemoved(tempNode);
3710
+ }
3711
+
3712
+ function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
3713
+ for (const streamElement of nodeListOfStreamElements) {
3714
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
3715
+ if (elementWithAutofocus) return elementWithAutofocus;
3583
3716
  }
3584
- function isIdInConsideration(ctx, id) {
3585
- return !ctx.deadIds.has(id);
3717
+ return null;
3718
+ }
3719
+
3720
+ class StreamObserver {
3721
+ sources=new Set;
3722
+ #started=false;
3723
+ constructor(delegate) {
3724
+ this.delegate = delegate;
3586
3725
  }
3587
- function idIsWithinNode(ctx, id, targetNode) {
3588
- let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
3589
- return idSet.has(id);
3726
+ start() {
3727
+ if (!this.#started) {
3728
+ this.#started = true;
3729
+ addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
3730
+ }
3590
3731
  }
3591
- function removeIdsFromConsideration(ctx, node) {
3592
- let idSet = ctx.idMap.get(node) || EMPTY_SET;
3593
- for (const id of idSet) {
3594
- ctx.deadIds.add(id);
3732
+ stop() {
3733
+ if (this.#started) {
3734
+ this.#started = false;
3735
+ removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
3595
3736
  }
3596
3737
  }
3597
- function getIdIntersectionCount(ctx, node1, node2) {
3598
- let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
3599
- let matchCount = 0;
3600
- for (const id of sourceSet) {
3601
- if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
3602
- ++matchCount;
3603
- }
3738
+ connectStreamSource(source) {
3739
+ if (!this.streamSourceIsConnected(source)) {
3740
+ this.sources.add(source);
3741
+ source.addEventListener("message", this.receiveMessageEvent, false);
3604
3742
  }
3605
- return matchCount;
3606
- }
3607
- function populateIdMapForNode(node, idMap) {
3608
- let nodeParent = node.parentElement;
3609
- let idElements = node.querySelectorAll("[id]");
3610
- for (const elt of idElements) {
3611
- let current = elt;
3612
- while (current !== nodeParent && current != null) {
3613
- let idSet = idMap.get(current);
3614
- if (idSet == null) {
3615
- idSet = new Set;
3616
- idMap.set(current, idSet);
3617
- }
3618
- idSet.add(elt.id);
3619
- current = current.parentElement;
3620
- }
3743
+ }
3744
+ disconnectStreamSource(source) {
3745
+ if (this.streamSourceIsConnected(source)) {
3746
+ this.sources.delete(source);
3747
+ source.removeEventListener("message", this.receiveMessageEvent, false);
3621
3748
  }
3622
3749
  }
3623
- function createIdMap(oldContent, newContent) {
3624
- let idMap = new Map;
3625
- populateIdMapForNode(oldContent, idMap);
3626
- populateIdMapForNode(newContent, idMap);
3627
- return idMap;
3750
+ streamSourceIsConnected(source) {
3751
+ return this.sources.has(source);
3628
3752
  }
3629
- return {
3630
- morph: morph,
3631
- defaults: defaults
3753
+ inspectFetchResponse=event => {
3754
+ const response = fetchResponseFromEvent(event);
3755
+ if (response && fetchResponseIsStream(response)) {
3756
+ event.preventDefault();
3757
+ this.receiveMessageResponse(response);
3758
+ }
3632
3759
  };
3633
- }();
3634
-
3635
- function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
3636
- Idiomorph.morph(currentElement, newElement, {
3637
- ...options,
3638
- callbacks: new DefaultIdiomorphCallbacks(callbacks)
3639
- });
3760
+ receiveMessageEvent=event => {
3761
+ if (this.#started && typeof event.data == "string") {
3762
+ this.receiveMessageHTML(event.data);
3763
+ }
3764
+ };
3765
+ async receiveMessageResponse(response) {
3766
+ const html = await response.responseHTML;
3767
+ if (html) {
3768
+ this.receiveMessageHTML(html);
3769
+ }
3770
+ }
3771
+ receiveMessageHTML(html) {
3772
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
3773
+ }
3640
3774
  }
3641
3775
 
3642
- function morphChildren(currentElement, newElement) {
3643
- morphElements(currentElement, newElement.children, {
3644
- morphStyle: "innerHTML"
3645
- });
3776
+ function fetchResponseFromEvent(event) {
3777
+ const fetchResponse = event.detail?.fetchResponse;
3778
+ if (fetchResponse instanceof FetchResponse) {
3779
+ return fetchResponse;
3780
+ }
3646
3781
  }
3647
3782
 
3648
- class DefaultIdiomorphCallbacks {
3649
- #beforeNodeMorphed;
3650
- constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
3651
- this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
3652
- }
3653
- beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
3654
- beforeNodeMorphed=(currentElement, newElement) => {
3655
- if (currentElement instanceof Element) {
3656
- if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
3657
- const event = dispatch("turbo:before-morph-element", {
3658
- cancelable: true,
3659
- target: currentElement,
3660
- detail: {
3661
- currentElement: currentElement,
3662
- newElement: newElement
3663
- }
3664
- });
3665
- return !event.defaultPrevented;
3666
- } else {
3667
- return false;
3668
- }
3669
- }
3670
- };
3671
- beforeAttributeUpdated=(attributeName, target, mutationType) => {
3672
- const event = dispatch("turbo:before-morph-attribute", {
3673
- cancelable: true,
3674
- target: target,
3675
- detail: {
3676
- attributeName: attributeName,
3677
- mutationType: mutationType
3678
- }
3679
- });
3680
- return !event.defaultPrevented;
3681
- };
3682
- beforeNodeRemoved=node => this.beforeNodeMorphed(node);
3683
- afterNodeMorphed=(currentElement, newElement) => {
3684
- if (currentElement instanceof Element) {
3685
- dispatch("turbo:morph-element", {
3686
- target: currentElement,
3687
- detail: {
3688
- currentElement: currentElement,
3689
- newElement: newElement
3690
- }
3691
- });
3692
- }
3693
- };
3783
+ function fetchResponseIsStream(response) {
3784
+ const contentType = response.contentType ?? "";
3785
+ return contentType.startsWith(StreamMessage.contentType);
3694
3786
  }
3695
3787
 
3696
- class MorphingFrameRenderer extends FrameRenderer {
3788
+ class ErrorRenderer extends Renderer {
3697
3789
  static renderElement(currentElement, newElement) {
3698
- dispatch("turbo:before-frame-morph", {
3699
- target: currentElement,
3700
- detail: {
3701
- currentElement: currentElement,
3702
- newElement: newElement
3790
+ const {documentElement: documentElement, body: body} = document;
3791
+ documentElement.replaceChild(newElement, body);
3792
+ }
3793
+ async render() {
3794
+ this.replaceHeadAndBody();
3795
+ this.activateScriptElements();
3796
+ }
3797
+ replaceHeadAndBody() {
3798
+ const {documentElement: documentElement, head: head} = document;
3799
+ documentElement.replaceChild(this.newHead, head);
3800
+ this.renderElement(this.currentElement, this.newElement);
3801
+ }
3802
+ activateScriptElements() {
3803
+ for (const replaceableElement of this.scriptElements) {
3804
+ const parentNode = replaceableElement.parentNode;
3805
+ if (parentNode) {
3806
+ const element = activateScriptElement(replaceableElement);
3807
+ parentNode.replaceChild(element, replaceableElement);
3703
3808
  }
3704
- });
3705
- morphChildren(currentElement, newElement);
3809
+ }
3810
+ }
3811
+ get newHead() {
3812
+ return this.newSnapshot.headSnapshot.element;
3813
+ }
3814
+ get scriptElements() {
3815
+ return document.documentElement.querySelectorAll("script");
3706
3816
  }
3707
3817
  }
3708
3818
 
@@ -3882,7 +3992,7 @@ class MorphingPageRenderer extends PageRenderer {
3882
3992
  }
3883
3993
  });
3884
3994
  for (const frame of currentElement.querySelectorAll("turbo-frame")) {
3885
- if (canRefreshFrame(frame)) refreshFrame(frame);
3995
+ if (canRefreshFrame(frame)) frame.reload();
3886
3996
  }
3887
3997
  dispatch("turbo:morph", {
3888
3998
  detail: {
@@ -3906,15 +4016,6 @@ function canRefreshFrame(frame) {
3906
4016
  return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
3907
4017
  }
3908
4018
 
3909
- function refreshFrame(frame) {
3910
- frame.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
3911
- detail.render = MorphingFrameRenderer.renderElement;
3912
- }), {
3913
- once: true
3914
- });
3915
- frame.reload();
3916
- }
3917
-
3918
4019
  class SnapshotCache {
3919
4020
  keys=[];
3920
4021
  snapshots={};
@@ -3969,7 +4070,7 @@ class PageView extends View {
3969
4070
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
3970
4071
  const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
3971
4072
  const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
3972
- const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
4073
+ const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
3973
4074
  if (!renderer.shouldRender) {
3974
4075
  this.forceReloaded = true;
3975
4076
  } else {
@@ -3979,7 +4080,7 @@ class PageView extends View {
3979
4080
  }
3980
4081
  renderError(snapshot, visit) {
3981
4082
  visit?.changeHistory();
3982
- const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
4083
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
3983
4084
  return this.render(renderer);
3984
4085
  }
3985
4086
  clearSnapshotCache() {
@@ -4097,11 +4198,8 @@ class Session {
4097
4198
  frameRedirector=new FrameRedirector(this, document.documentElement);
4098
4199
  streamMessageRenderer=new StreamMessageRenderer;
4099
4200
  cache=new Cache(this);
4100
- drive=true;
4101
4201
  enabled=true;
4102
- progressBarDelay=500;
4103
4202
  started=false;
4104
- formMode="on";
4105
4203
  #pageRefreshDebouncePeriod=150;
4106
4204
  constructor(recentRequests) {
4107
4205
  this.recentRequests = recentRequests;
@@ -4160,7 +4258,8 @@ class Session {
4160
4258
  }
4161
4259
  refresh(url, requestId) {
4162
4260
  const isRecentRequest = requestId && this.recentRequests.has(requestId);
4163
- if (!isRecentRequest && !this.navigator.currentVisit) {
4261
+ const isCurrentUrl = url === document.baseURI;
4262
+ if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
4164
4263
  this.visit(url, {
4165
4264
  action: "replace",
4166
4265
  shouldCacheSnapshot: false
@@ -4180,10 +4279,26 @@ class Session {
4180
4279
  this.view.clearSnapshotCache();
4181
4280
  }
4182
4281
  setProgressBarDelay(delay) {
4282
+ 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.`");
4183
4283
  this.progressBarDelay = delay;
4184
4284
  }
4185
- setFormMode(mode) {
4186
- this.formMode = mode;
4285
+ set progressBarDelay(delay) {
4286
+ config.drive.progressBarDelay = delay;
4287
+ }
4288
+ get progressBarDelay() {
4289
+ return config.drive.progressBarDelay;
4290
+ }
4291
+ set drive(value) {
4292
+ config.drive.enabled = value;
4293
+ }
4294
+ get drive() {
4295
+ return config.drive.enabled;
4296
+ }
4297
+ set formMode(value) {
4298
+ config.forms.mode = value;
4299
+ }
4300
+ get formMode() {
4301
+ return config.forms.mode;
4187
4302
  }
4188
4303
  get location() {
4189
4304
  return this.history.location;
@@ -4233,7 +4348,7 @@ class Session {
4233
4348
  }
4234
4349
  submittedFormLinkToLocation() {}
4235
4350
  canPrefetchRequestToLocation(link, location) {
4236
- return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4351
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location);
4237
4352
  }
4238
4353
  willFollowLinkToLocation(link, location, event) {
4239
4354
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
@@ -4405,11 +4520,11 @@ class Session {
4405
4520
  });
4406
4521
  }
4407
4522
  submissionIsNavigatable(form, submitter) {
4408
- if (this.formMode == "off") {
4523
+ if (config.forms.mode == "off") {
4409
4524
  return false;
4410
4525
  } else {
4411
4526
  const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
4412
- if (this.formMode == "optin") {
4527
+ if (config.forms.mode == "optin") {
4413
4528
  return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
4414
4529
  } else {
4415
4530
  return submitterIsNavigatable && this.elementIsNavigatable(form);
@@ -4419,7 +4534,7 @@ class Session {
4419
4534
  elementIsNavigatable(element) {
4420
4535
  const container = findClosestRecursively(element, "[data-turbo]");
4421
4536
  const withinFrame = findClosestRecursively(element, "turbo-frame");
4422
- if (this.drive || withinFrame) {
4537
+ if (config.drive.enabled || withinFrame) {
4423
4538
  if (container) {
4424
4539
  return container.getAttribute("data-turbo") != "false";
4425
4540
  } else {
@@ -4487,15 +4602,18 @@ function clearCache() {
4487
4602
  }
4488
4603
 
4489
4604
  function setProgressBarDelay(delay) {
4490
- session.setProgressBarDelay(delay);
4605
+ 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.`");
4606
+ config.drive.progressBarDelay = delay;
4491
4607
  }
4492
4608
 
4493
4609
  function setConfirmMethod(confirmMethod) {
4494
- FormSubmission.confirmMethod = confirmMethod;
4610
+ 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.`");
4611
+ config.forms.confirm = confirmMethod;
4495
4612
  }
4496
4613
 
4497
4614
  function setFormMode(mode) {
4498
- session.setFormMode(mode);
4615
+ 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.`");
4616
+ config.forms.mode = mode;
4499
4617
  }
4500
4618
 
4501
4619
  var Turbo = Object.freeze({
@@ -4507,6 +4625,7 @@ var Turbo = Object.freeze({
4507
4625
  PageSnapshot: PageSnapshot,
4508
4626
  FrameRenderer: FrameRenderer,
4509
4627
  fetch: fetchWithTurboHeaders,
4628
+ config: config,
4510
4629
  start: start,
4511
4630
  registerAdapter: registerAdapter,
4512
4631
  visit: visit,
@@ -4528,6 +4647,7 @@ class FrameController {
4528
4647
  #connected=false;
4529
4648
  #hasBeenLoaded=false;
4530
4649
  #ignoredAttributes=new Set;
4650
+ #shouldMorphFrame=false;
4531
4651
  action=null;
4532
4652
  constructor(element) {
4533
4653
  this.element = element;
@@ -4575,7 +4695,8 @@ class FrameController {
4575
4695
  }
4576
4696
  }
4577
4697
  sourceURLReloaded() {
4578
- const {src: src} = this.element;
4698
+ const {refresh: refresh, src: src} = this.element;
4699
+ this.#shouldMorphFrame = src && refresh === "morph";
4579
4700
  this.element.removeAttribute("complete");
4580
4701
  this.element.src = null;
4581
4702
  this.element.src = src;
@@ -4613,6 +4734,7 @@ class FrameController {
4613
4734
  }
4614
4735
  }
4615
4736
  } finally {
4737
+ this.#shouldMorphFrame = false;
4616
4738
  this.fetchResponseLoaded = () => Promise.resolve();
4617
4739
  }
4618
4740
  }
@@ -4725,9 +4847,10 @@ class FrameController {
4725
4847
  };
4726
4848
  async #loadFrameResponse(fetchResponse, document) {
4727
4849
  const newFrameElement = await this.extractForeignFrameElement(document.body);
4850
+ const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;
4728
4851
  if (newFrameElement) {
4729
4852
  const snapshot = new Snapshot(newFrameElement);
4730
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
4853
+ const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);
4731
4854
  if (this.view.renderPromise) await this.view.renderPromise;
4732
4855
  this.changeHistory();
4733
4856
  await this.view.render(renderer);
@@ -5042,9 +5165,9 @@ class StreamElement extends HTMLElement {
5042
5165
  this.duplicateChildren.forEach((c => c.remove()));
5043
5166
  }
5044
5167
  get duplicateChildren() {
5045
- const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
5046
- const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
5047
- return existingChildren.filter((c => newChildrenIds.includes(c.id)));
5168
+ const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.getAttribute("id")));
5169
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
5170
+ return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
5048
5171
  }
5049
5172
  get performAction() {
5050
5173
  if (this.action) {
@@ -5200,6 +5323,7 @@ var Turbo$1 = Object.freeze({
5200
5323
  StreamSourceElement: StreamSourceElement,
5201
5324
  cache: cache,
5202
5325
  clearCache: clearCache,
5326
+ config: config,
5203
5327
  connectStreamSource: connectStreamSource,
5204
5328
  disconnectStreamSource: disconnectStreamSource,
5205
5329
  fetch: fetchWithTurboHeaders,
@@ -5261,6 +5385,7 @@ function walk(obj) {
5261
5385
  }
5262
5386
 
5263
5387
  class TurboCableStreamSourceElement extends HTMLElement {
5388
+ static observedAttributes=[ "channel", "signed-stream-name" ];
5264
5389
  async connectedCallback() {
5265
5390
  connectStreamSource(this);
5266
5391
  this.subscription = await subscribeTo(this.channel, {
@@ -5272,6 +5397,13 @@ class TurboCableStreamSourceElement extends HTMLElement {
5272
5397
  disconnectedCallback() {
5273
5398
  disconnectStreamSource(this);
5274
5399
  if (this.subscription) this.subscription.unsubscribe();
5400
+ this.subscriptionDisconnected();
5401
+ }
5402
+ attributeChangedCallback() {
5403
+ if (this.subscription) {
5404
+ this.disconnectedCallback();
5405
+ this.connectedCallback();
5406
+ }
5275
5407
  }
5276
5408
  dispatchMessageEvent(data) {
5277
5409
  const event = new MessageEvent("message", {