turbo-rails 0.5.9 → 0.7.14

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.
@@ -31,6 +31,7 @@ function clickCaptured(event) {
31
31
 
32
32
  (function() {
33
33
  if ("SubmitEvent" in window) return;
34
+ if ("submitter" in Event.prototype) return;
34
35
  addEventListener("click", clickCaptured, true);
35
36
  Object.defineProperty(Event.prototype, "submitter", {
36
37
  get() {
@@ -55,7 +56,7 @@ class FrameElement extends HTMLElement {
55
56
  this.delegate = new FrameElement.delegateConstructor(this);
56
57
  }
57
58
  static get observedAttributes() {
58
- return [ "loading", "src" ];
59
+ return [ "disabled", "loading", "src" ];
59
60
  }
60
61
  connectedCallback() {
61
62
  this.delegate.connect();
@@ -63,11 +64,18 @@ class FrameElement extends HTMLElement {
63
64
  disconnectedCallback() {
64
65
  this.delegate.disconnect();
65
66
  }
67
+ reload() {
68
+ const {src: src} = this;
69
+ this.src = null;
70
+ this.src = src;
71
+ }
66
72
  attributeChangedCallback(name) {
67
73
  if (name == "loading") {
68
74
  this.delegate.loadingStyleChanged();
69
75
  } else if (name == "src") {
70
76
  this.delegate.sourceURLChanged();
77
+ } else {
78
+ this.delegate.disabledChanged();
71
79
  }
72
80
  }
73
81
  get src() {
@@ -133,9 +141,7 @@ function frameLoadingStyleFromString(style) {
133
141
  }
134
142
 
135
143
  function expandURL(locatable) {
136
- const anchor = document.createElement("a");
137
- anchor.href = locatable.toString();
138
- return new URL(anchor.href);
144
+ return new URL(locatable.toString(), document.baseURI);
139
145
  }
140
146
 
141
147
  function getAnchor(url) {
@@ -144,8 +150,6 @@ function getAnchor(url) {
144
150
  return url.hash.slice(1);
145
151
  } else if (anchorMatch = url.href.match(/#(.*)$/)) {
146
152
  return anchorMatch[1];
147
- } else {
148
- return "";
149
153
  }
150
154
  }
151
155
 
@@ -162,13 +166,17 @@ function isPrefixedBy(baseURL, url) {
162
166
  return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
163
167
  }
164
168
 
169
+ function getRequestURL(url) {
170
+ const anchor = getAnchor(url);
171
+ return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
172
+ }
173
+
165
174
  function toCacheKey(url) {
166
- const anchorLength = url.hash.length;
167
- if (anchorLength < 2) {
168
- return url.href;
169
- } else {
170
- return url.href.slice(0, -anchorLength);
171
- }
175
+ return getRequestURL(url);
176
+ }
177
+
178
+ function urlsAreEqual(left, right) {
179
+ return expandURL(left).href == expandURL(right).href;
172
180
  }
173
181
 
174
182
  function getPathComponents(url) {
@@ -219,11 +227,11 @@ class FetchResponse {
219
227
  return this.header("Content-Type");
220
228
  }
221
229
  get responseText() {
222
- return this.response.text();
230
+ return this.response.clone().text();
223
231
  }
224
232
  get responseHTML() {
225
233
  if (this.isHTML) {
226
- return this.response.text();
234
+ return this.response.clone().text();
227
235
  } else {
228
236
  return Promise.resolve(undefined);
229
237
  }
@@ -319,16 +327,19 @@ function fetchMethodFromString(method) {
319
327
  }
320
328
 
321
329
  class FetchRequest {
322
- constructor(delegate, method, location, body = new URLSearchParams) {
330
+ constructor(delegate, method, location, body = new URLSearchParams, target = null) {
323
331
  this.abortController = new AbortController;
332
+ this.resolveRequestPromise = value => {};
324
333
  this.delegate = delegate;
325
334
  this.method = method;
335
+ this.headers = this.defaultHeaders;
326
336
  if (this.isIdempotent) {
327
337
  this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
328
338
  } else {
329
339
  this.body = body;
330
340
  this.url = location;
331
341
  }
342
+ this.target = target;
332
343
  }
333
344
  get location() {
334
345
  return this.url;
@@ -343,19 +354,19 @@ class FetchRequest {
343
354
  this.abortController.abort();
344
355
  }
345
356
  async perform() {
357
+ var _a, _b;
346
358
  const {fetchOptions: fetchOptions} = this;
347
- dispatch("turbo:before-fetch-request", {
348
- detail: {
349
- fetchOptions: fetchOptions
350
- }
351
- });
359
+ (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
360
+ await this.allowRequestToBeIntercepted(fetchOptions);
352
361
  try {
353
362
  this.delegate.requestStarted(this);
354
363
  const response = await fetch(this.url.href, fetchOptions);
355
364
  return await this.receive(response);
356
365
  } catch (error) {
357
- this.delegate.requestErrored(this, error);
358
- throw error;
366
+ if (error.name !== "AbortError") {
367
+ this.delegate.requestErrored(this, error);
368
+ throw error;
369
+ }
359
370
  } finally {
360
371
  this.delegate.requestFinished(this);
361
372
  }
@@ -366,7 +377,8 @@ class FetchRequest {
366
377
  cancelable: true,
367
378
  detail: {
368
379
  fetchResponse: fetchResponse
369
- }
380
+ },
381
+ target: this.target
370
382
  });
371
383
  if (event.defaultPrevented) {
372
384
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
@@ -378,32 +390,40 @@ class FetchRequest {
378
390
  return fetchResponse;
379
391
  }
380
392
  get fetchOptions() {
393
+ var _a;
381
394
  return {
382
395
  method: FetchMethod[this.method].toUpperCase(),
383
396
  credentials: "same-origin",
384
397
  headers: this.headers,
385
398
  redirect: "follow",
386
399
  body: this.body,
387
- signal: this.abortSignal
400
+ signal: this.abortSignal,
401
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
402
+ };
403
+ }
404
+ get defaultHeaders() {
405
+ return {
406
+ Accept: "text/html, application/xhtml+xml"
388
407
  };
389
408
  }
390
409
  get isIdempotent() {
391
410
  return this.method == FetchMethod.get;
392
411
  }
393
- get headers() {
394
- const headers = Object.assign({}, this.defaultHeaders);
395
- if (typeof this.delegate.prepareHeadersForRequest == "function") {
396
- this.delegate.prepareHeadersForRequest(headers, this);
397
- }
398
- return headers;
399
- }
400
412
  get abortSignal() {
401
413
  return this.abortController.signal;
402
414
  }
403
- get defaultHeaders() {
404
- return {
405
- Accept: "text/html, application/xhtml+xml"
406
- };
415
+ async allowRequestToBeIntercepted(fetchOptions) {
416
+ const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
417
+ const event = dispatch("turbo:before-fetch-request", {
418
+ cancelable: true,
419
+ detail: {
420
+ fetchOptions: fetchOptions,
421
+ url: this.url.href,
422
+ resume: this.resolveRequestPromise
423
+ },
424
+ target: this.target
425
+ });
426
+ if (event.defaultPrevented) await requestInterception;
407
427
  }
408
428
  }
409
429
 
@@ -522,7 +542,7 @@ class FormSubmission {
522
542
  this.formElement = formElement;
523
543
  this.submitter = submitter;
524
544
  this.formData = buildFormData(formElement, submitter);
525
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
545
+ this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
526
546
  this.mustRedirect = mustRedirect;
527
547
  }
528
548
  get method() {
@@ -532,7 +552,8 @@ class FormSubmission {
532
552
  }
533
553
  get action() {
534
554
  var _a;
535
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
555
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
556
+ return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
536
557
  }
537
558
  get location() {
538
559
  return expandURL(this.action);
@@ -548,6 +569,9 @@ class FormSubmission {
548
569
  var _a;
549
570
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
550
571
  }
572
+ get isIdempotent() {
573
+ return this.fetchRequest.isIdempotent;
574
+ }
551
575
  get stringFormData() {
552
576
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
553
577
  }
@@ -639,8 +663,8 @@ function buildFormData(formElement, submitter) {
639
663
  const formData = new FormData(formElement);
640
664
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
641
665
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
642
- if (name && formData.get(name) != value) {
643
- formData.append(name, value || "");
666
+ if (name && value != null && formData.get(name) != value) {
667
+ formData.append(name, value);
644
668
  }
645
669
  return formData;
646
670
  }
@@ -676,11 +700,10 @@ class Snapshot {
676
700
  return this.getElementForAnchor(anchor) != null;
677
701
  }
678
702
  getElementForAnchor(anchor) {
679
- try {
680
- return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
681
- } catch (_a) {
682
- return null;
683
- }
703
+ return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;
704
+ }
705
+ get isConnected() {
706
+ return this.element.isConnected;
684
707
  }
685
708
  get firstAutofocusableElement() {
686
709
  return this.element.querySelector("[autofocus]");
@@ -691,8 +714,16 @@ class Snapshot {
691
714
  getPermanentElementById(id) {
692
715
  return this.element.querySelector(`#${id}[data-turbo-permanent]`);
693
716
  }
694
- getPermanentElementsPresentInSnapshot(snapshot) {
695
- return this.permanentElements.filter((({id: id}) => snapshot.getPermanentElementById(id)));
717
+ getPermanentElementMapForSnapshot(snapshot) {
718
+ const permanentElementMap = {};
719
+ for (const currentPermanentElement of this.permanentElements) {
720
+ const {id: id} = currentPermanentElement;
721
+ const newPermanentElement = snapshot.getPermanentElementById(id);
722
+ if (newPermanentElement) {
723
+ permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
724
+ }
725
+ }
726
+ return permanentElementMap;
696
727
  }
697
728
  }
698
729
 
@@ -722,6 +753,8 @@ class FormInterceptor {
722
753
 
723
754
  class View {
724
755
  constructor(delegate, element) {
756
+ this.resolveRenderPromise = value => {};
757
+ this.resolveInterceptionPromise = value => {};
725
758
  this.delegate = delegate;
726
759
  this.element = element;
727
760
  }
@@ -729,6 +762,7 @@ class View {
729
762
  const element = this.snapshot.getElementForAnchor(anchor);
730
763
  if (element) {
731
764
  this.scrollToElement(element);
765
+ this.focusElement(element);
732
766
  } else {
733
767
  this.scrollToPosition({
734
768
  x: 0,
@@ -736,30 +770,52 @@ class View {
736
770
  });
737
771
  }
738
772
  }
773
+ scrollToAnchorFromLocation(location) {
774
+ this.scrollToAnchor(getAnchor(location));
775
+ }
739
776
  scrollToElement(element) {
740
777
  element.scrollIntoView();
741
778
  }
779
+ focusElement(element) {
780
+ if (element instanceof HTMLElement) {
781
+ if (element.hasAttribute("tabindex")) {
782
+ element.focus();
783
+ } else {
784
+ element.setAttribute("tabindex", "-1");
785
+ element.focus();
786
+ element.removeAttribute("tabindex");
787
+ }
788
+ }
789
+ }
742
790
  scrollToPosition({x: x, y: y}) {
743
791
  this.scrollRoot.scrollTo(x, y);
744
792
  }
793
+ scrollToTop() {
794
+ this.scrollToPosition({
795
+ x: 0,
796
+ y: 0
797
+ });
798
+ }
745
799
  get scrollRoot() {
746
800
  return window;
747
801
  }
748
802
  async render(renderer) {
749
- if (this.renderer) {
750
- throw new Error("rendering is already in progress");
751
- }
752
803
  const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
753
804
  if (shouldRender) {
754
805
  try {
806
+ this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
755
807
  this.renderer = renderer;
756
808
  this.prepareToRenderSnapshot(renderer);
757
- this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
809
+ const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
810
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
811
+ if (!immediateRender) await renderInterception;
758
812
  await this.renderSnapshot(renderer);
759
813
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
760
814
  this.finishRenderingSnapshot(renderer);
761
815
  } finally {
762
816
  delete this.renderer;
817
+ this.resolveRenderPromise(undefined);
818
+ delete this.renderPromise;
763
819
  }
764
820
  } else {
765
821
  this.invalidate();
@@ -837,6 +893,56 @@ class LinkInterceptor {
837
893
  }
838
894
  }
839
895
 
896
+ class Bardo {
897
+ constructor(permanentElementMap) {
898
+ this.permanentElementMap = permanentElementMap;
899
+ }
900
+ static preservingPermanentElements(permanentElementMap, callback) {
901
+ const bardo = new this(permanentElementMap);
902
+ bardo.enter();
903
+ callback();
904
+ bardo.leave();
905
+ }
906
+ enter() {
907
+ for (const id in this.permanentElementMap) {
908
+ const [, newPermanentElement] = this.permanentElementMap[id];
909
+ this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
910
+ }
911
+ }
912
+ leave() {
913
+ for (const id in this.permanentElementMap) {
914
+ const [currentPermanentElement] = this.permanentElementMap[id];
915
+ this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
916
+ this.replacePlaceholderWithPermanentElement(currentPermanentElement);
917
+ }
918
+ }
919
+ replaceNewPermanentElementWithPlaceholder(permanentElement) {
920
+ const placeholder = createPlaceholderForPermanentElement(permanentElement);
921
+ permanentElement.replaceWith(placeholder);
922
+ }
923
+ replaceCurrentPermanentElementWithClone(permanentElement) {
924
+ const clone = permanentElement.cloneNode(true);
925
+ permanentElement.replaceWith(clone);
926
+ }
927
+ replacePlaceholderWithPermanentElement(permanentElement) {
928
+ const placeholder = this.getPlaceholderById(permanentElement.id);
929
+ placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
930
+ }
931
+ getPlaceholderById(id) {
932
+ return this.placeholders.find((element => element.content == id));
933
+ }
934
+ get placeholders() {
935
+ return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
936
+ }
937
+ }
938
+
939
+ function createPlaceholderForPermanentElement(permanentElement) {
940
+ const element = document.createElement("meta");
941
+ element.setAttribute("name", "turbo-permanent-placeholder");
942
+ element.setAttribute("content", permanentElement.id);
943
+ return element;
944
+ }
945
+
840
946
  class Renderer {
841
947
  constructor(currentSnapshot, newSnapshot, isPreview) {
842
948
  this.currentSnapshot = currentSnapshot;
@@ -864,6 +970,9 @@ class Renderer {
864
970
  return element;
865
971
  } else {
866
972
  const createdScriptElement = document.createElement("script");
973
+ if (this.cspNonce) {
974
+ createdScriptElement.nonce = this.cspNonce;
975
+ }
867
976
  createdScriptElement.textContent = element.textContent;
868
977
  createdScriptElement.async = false;
869
978
  copyElementAttributes(createdScriptElement, element);
@@ -871,28 +980,29 @@ class Renderer {
871
980
  }
872
981
  }
873
982
  preservingPermanentElements(callback) {
874
- const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
875
- callback();
876
- replacePlaceholderElementsWithClonedPermanentElements(placeholders);
983
+ Bardo.preservingPermanentElements(this.permanentElementMap, callback);
877
984
  }
878
985
  focusFirstAutofocusableElement() {
879
- const element = this.newSnapshot.firstAutofocusableElement;
986
+ const element = this.connectedSnapshot.firstAutofocusableElement;
880
987
  if (elementIsFocusable(element)) {
881
988
  element.focus();
882
989
  }
883
990
  }
991
+ get connectedSnapshot() {
992
+ return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
993
+ }
884
994
  get currentElement() {
885
995
  return this.currentSnapshot.element;
886
996
  }
887
997
  get newElement() {
888
998
  return this.newSnapshot.element;
889
999
  }
890
- }
891
-
892
- function replaceElementWithElement(fromElement, toElement) {
893
- const parentElement = fromElement.parentElement;
894
- if (parentElement) {
895
- return parentElement.replaceChild(toElement, fromElement);
1000
+ get permanentElementMap() {
1001
+ return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1002
+ }
1003
+ get cspNonce() {
1004
+ var _a;
1005
+ return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
896
1006
  }
897
1007
  }
898
1008
 
@@ -902,37 +1012,6 @@ function copyElementAttributes(destinationElement, sourceElement) {
902
1012
  }
903
1013
  }
904
1014
 
905
- function createPlaceholderForPermanentElement(permanentElement) {
906
- const element = document.createElement("meta");
907
- element.setAttribute("name", "turbo-permanent-placeholder");
908
- element.setAttribute("content", permanentElement.id);
909
- return {
910
- element: element,
911
- permanentElement: permanentElement
912
- };
913
- }
914
-
915
- function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
916
- for (const {element: element, permanentElement: permanentElement} of placeholders) {
917
- const clonedElement = permanentElement.cloneNode(true);
918
- replaceElementWithElement(element, clonedElement);
919
- }
920
- }
921
-
922
- function relocatePermanentElements(currentSnapshot, newSnapshot) {
923
- return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce(((placeholders, permanentElement) => {
924
- const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
925
- if (newElement) {
926
- const placeholder = createPlaceholderForPermanentElement(permanentElement);
927
- replaceElementWithElement(permanentElement, placeholder.element);
928
- replaceElementWithElement(newElement, permanentElement);
929
- return [ ...placeholders, placeholder ];
930
- } else {
931
- return placeholders;
932
- }
933
- }), []);
934
- }
935
-
936
1015
  function elementIsFocusable(element) {
937
1016
  return element && typeof element.focus == "function";
938
1017
  }
@@ -949,6 +1028,8 @@ class FrameRenderer extends Renderer {
949
1028
  this.scrollFrameIntoView();
950
1029
  await nextAnimationFrame();
951
1030
  this.focusFirstAutofocusableElement();
1031
+ await nextAnimationFrame();
1032
+ this.activateScriptElements();
952
1033
  }
953
1034
  loadFrameElement() {
954
1035
  var _a;
@@ -975,6 +1056,15 @@ class FrameRenderer extends Renderer {
975
1056
  }
976
1057
  return false;
977
1058
  }
1059
+ activateScriptElements() {
1060
+ for (const inertScriptElement of this.newScriptElements) {
1061
+ const activatedScriptElement = this.createScriptElement(inertScriptElement);
1062
+ inertScriptElement.replaceWith(activatedScriptElement);
1063
+ }
1064
+ }
1065
+ get newScriptElements() {
1066
+ return this.currentElement.querySelectorAll("script");
1067
+ }
978
1068
  }
979
1069
 
980
1070
  function readScrollLogicalPosition(value, defaultValue) {
@@ -985,577 +1075,264 @@ function readScrollLogicalPosition(value, defaultValue) {
985
1075
  }
986
1076
  }
987
1077
 
988
- class FrameController {
989
- constructor(element) {
990
- this.resolveVisitPromise = () => {};
991
- this.element = element;
992
- this.view = new FrameView(this, this.element);
993
- this.appearanceObserver = new AppearanceObserver(this, this.element);
994
- this.linkInterceptor = new LinkInterceptor(this, this.element);
995
- this.formInterceptor = new FormInterceptor(this, this.element);
996
- }
997
- connect() {
998
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
999
- this.appearanceObserver.start();
1000
- }
1001
- this.linkInterceptor.start();
1002
- this.formInterceptor.start();
1003
- }
1004
- disconnect() {
1005
- this.appearanceObserver.stop();
1006
- this.linkInterceptor.stop();
1007
- this.formInterceptor.stop();
1008
- }
1009
- sourceURLChanged() {
1010
- if (this.loadingStyle == FrameLoadingStyle.eager) {
1011
- this.loadSourceURL();
1012
- }
1013
- }
1014
- loadingStyleChanged() {
1015
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
1016
- this.appearanceObserver.start();
1017
- } else {
1018
- this.appearanceObserver.stop();
1019
- this.loadSourceURL();
1020
- }
1078
+ class ProgressBar {
1079
+ constructor() {
1080
+ this.hiding = false;
1081
+ this.value = 0;
1082
+ this.visible = false;
1083
+ this.trickle = () => {
1084
+ this.setValue(this.value + Math.random() / 100);
1085
+ };
1086
+ this.stylesheetElement = this.createStylesheetElement();
1087
+ this.progressElement = this.createProgressElement();
1088
+ this.installStylesheetElement();
1089
+ this.setValue(0);
1021
1090
  }
1022
- async loadSourceURL() {
1023
- if (this.isActive && this.sourceURL && this.sourceURL != this.loadingURL) {
1024
- try {
1025
- this.loadingURL = this.sourceURL;
1026
- this.element.loaded = this.visit(this.sourceURL);
1027
- this.appearanceObserver.stop();
1028
- await this.element.loaded;
1029
- } finally {
1030
- delete this.loadingURL;
1091
+ static get defaultCSS() {
1092
+ return unindent`
1093
+ .turbo-progress-bar {
1094
+ position: fixed;
1095
+ display: block;
1096
+ top: 0;
1097
+ left: 0;
1098
+ height: 3px;
1099
+ background: #0076ff;
1100
+ z-index: 9999;
1101
+ transition:
1102
+ width ${ProgressBar.animationDuration}ms ease-out,
1103
+ opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
1104
+ transform: translate3d(0, 0, 0);
1031
1105
  }
1106
+ `;
1107
+ }
1108
+ show() {
1109
+ if (!this.visible) {
1110
+ this.visible = true;
1111
+ this.installProgressElement();
1112
+ this.startTrickling();
1032
1113
  }
1033
1114
  }
1034
- async loadResponse(response) {
1035
- try {
1036
- const html = await response.responseHTML;
1037
- if (html) {
1038
- const {body: body} = parseHTMLDocument(html);
1039
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
1040
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
1041
- await this.view.render(renderer);
1042
- }
1043
- } catch (error) {
1044
- console.error(error);
1045
- this.view.invalidate();
1115
+ hide() {
1116
+ if (this.visible && !this.hiding) {
1117
+ this.hiding = true;
1118
+ this.fadeProgressElement((() => {
1119
+ this.uninstallProgressElement();
1120
+ this.stopTrickling();
1121
+ this.visible = false;
1122
+ this.hiding = false;
1123
+ }));
1046
1124
  }
1047
1125
  }
1048
- elementAppearedInViewport(element) {
1049
- this.loadSourceURL();
1126
+ setValue(value) {
1127
+ this.value = value;
1128
+ this.refresh();
1050
1129
  }
1051
- shouldInterceptLinkClick(element, url) {
1052
- return this.shouldInterceptNavigation(element);
1130
+ installStylesheetElement() {
1131
+ document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
1053
1132
  }
1054
- linkClickIntercepted(element, url) {
1055
- this.navigateFrame(element, url);
1133
+ installProgressElement() {
1134
+ this.progressElement.style.width = "0";
1135
+ this.progressElement.style.opacity = "1";
1136
+ document.documentElement.insertBefore(this.progressElement, document.body);
1137
+ this.refresh();
1056
1138
  }
1057
- shouldInterceptFormSubmission(element) {
1058
- return this.shouldInterceptNavigation(element);
1139
+ fadeProgressElement(callback) {
1140
+ this.progressElement.style.opacity = "0";
1141
+ setTimeout(callback, ProgressBar.animationDuration * 1.5);
1059
1142
  }
1060
- formSubmissionIntercepted(element, submitter) {
1061
- if (this.formSubmission) {
1062
- this.formSubmission.stop();
1063
- }
1064
- this.formSubmission = new FormSubmission(this, element, submitter);
1065
- if (this.formSubmission.fetchRequest.isIdempotent) {
1066
- this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
1067
- } else {
1068
- this.formSubmission.start();
1143
+ uninstallProgressElement() {
1144
+ if (this.progressElement.parentNode) {
1145
+ document.documentElement.removeChild(this.progressElement);
1069
1146
  }
1070
1147
  }
1071
- prepareHeadersForRequest(headers, request) {
1072
- headers["Turbo-Frame"] = this.id;
1073
- }
1074
- requestStarted(request) {
1075
- this.element.setAttribute("busy", "");
1076
- }
1077
- requestPreventedHandlingResponse(request, response) {
1078
- this.resolveVisitPromise();
1148
+ startTrickling() {
1149
+ if (!this.trickleInterval) {
1150
+ this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
1151
+ }
1079
1152
  }
1080
- async requestSucceededWithResponse(request, response) {
1081
- await this.loadResponse(response);
1082
- this.resolveVisitPromise();
1153
+ stopTrickling() {
1154
+ window.clearInterval(this.trickleInterval);
1155
+ delete this.trickleInterval;
1083
1156
  }
1084
- requestFailedWithResponse(request, response) {
1085
- console.error(response);
1086
- this.resolveVisitPromise();
1157
+ refresh() {
1158
+ requestAnimationFrame((() => {
1159
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1160
+ }));
1087
1161
  }
1088
- requestErrored(request, error) {
1089
- console.error(error);
1090
- this.resolveVisitPromise();
1162
+ createStylesheetElement() {
1163
+ const element = document.createElement("style");
1164
+ element.type = "text/css";
1165
+ element.textContent = ProgressBar.defaultCSS;
1166
+ return element;
1091
1167
  }
1092
- requestFinished(request) {
1093
- this.element.removeAttribute("busy");
1168
+ createProgressElement() {
1169
+ const element = document.createElement("div");
1170
+ element.className = "turbo-progress-bar";
1171
+ return element;
1094
1172
  }
1095
- formSubmissionStarted(formSubmission) {}
1096
- formSubmissionSucceededWithResponse(formSubmission, response) {
1097
- const frame = this.findFrameElement(formSubmission.formElement);
1098
- frame.delegate.loadResponse(response);
1099
- }
1100
- formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
1101
- this.element.delegate.loadResponse(fetchResponse);
1102
- }
1103
- formSubmissionErrored(formSubmission, error) {}
1104
- formSubmissionFinished(formSubmission) {}
1105
- viewWillRenderSnapshot(snapshot, isPreview) {}
1106
- viewRenderedSnapshot(snapshot, isPreview) {}
1107
- viewInvalidated() {}
1108
- async visit(url) {
1109
- const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
1110
- return new Promise((resolve => {
1111
- this.resolveVisitPromise = () => {
1112
- this.resolveVisitPromise = () => {};
1113
- resolve();
1173
+ }
1174
+
1175
+ ProgressBar.animationDuration = 300;
1176
+
1177
+ class HeadSnapshot extends Snapshot {
1178
+ constructor() {
1179
+ super(...arguments);
1180
+ this.detailsByOuterHTML = this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1181
+ const {outerHTML: outerHTML} = element;
1182
+ const details = outerHTML in result ? result[outerHTML] : {
1183
+ type: elementType(element),
1184
+ tracked: elementIsTracked(element),
1185
+ elements: []
1114
1186
  };
1115
- request.perform();
1116
- }));
1117
- }
1118
- navigateFrame(element, url) {
1119
- const frame = this.findFrameElement(element);
1120
- frame.src = url;
1121
- }
1122
- findFrameElement(element) {
1123
- var _a;
1124
- const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
1125
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
1126
- }
1127
- async extractForeignFrameElement(container) {
1128
- let element;
1129
- const id = CSS.escape(this.id);
1130
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
1131
- return element;
1132
- }
1133
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
1134
- await element.loaded;
1135
- return await this.extractForeignFrameElement(element);
1136
- }
1137
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
1138
- return new FrameElement;
1187
+ return Object.assign(Object.assign({}, result), {
1188
+ [outerHTML]: Object.assign(Object.assign({}, details), {
1189
+ elements: [ ...details.elements, element ]
1190
+ })
1191
+ });
1192
+ }), {});
1139
1193
  }
1140
- shouldInterceptNavigation(element) {
1141
- const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
1142
- if (!this.enabled || id == "_top") {
1143
- return false;
1144
- }
1145
- if (id) {
1146
- const frameElement = getFrameElementById(id);
1147
- if (frameElement) {
1148
- return !frameElement.disabled;
1149
- }
1150
- }
1151
- return true;
1194
+ get trackedElementSignature() {
1195
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1152
1196
  }
1153
- get id() {
1154
- return this.element.id;
1197
+ getScriptElementsNotInSnapshot(snapshot) {
1198
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1155
1199
  }
1156
- get enabled() {
1157
- return !this.element.disabled;
1200
+ getStylesheetElementsNotInSnapshot(snapshot) {
1201
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1158
1202
  }
1159
- get sourceURL() {
1160
- return this.element.src;
1203
+ getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1204
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
1161
1205
  }
1162
- get loadingStyle() {
1163
- return this.element.loading;
1206
+ get provisionalElements() {
1207
+ return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1208
+ const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
1209
+ if (type == null && !tracked) {
1210
+ return [ ...result, ...elements ];
1211
+ } else if (elements.length > 1) {
1212
+ return [ ...result, ...elements.slice(1) ];
1213
+ } else {
1214
+ return result;
1215
+ }
1216
+ }), []);
1164
1217
  }
1165
- get isLoading() {
1166
- return this.formSubmission !== undefined || this.loadingURL !== undefined;
1218
+ getMetaValue(name) {
1219
+ const element = this.findMetaElementByName(name);
1220
+ return element ? element.getAttribute("content") : null;
1167
1221
  }
1168
- get isActive() {
1169
- return this.element.isActive;
1222
+ findMetaElementByName(name) {
1223
+ return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1224
+ const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1225
+ return elementIsMetaElementWithName(element, name) ? element : result;
1226
+ }), undefined);
1170
1227
  }
1171
1228
  }
1172
1229
 
1173
- function getFrameElementById(id) {
1174
- if (id != null) {
1175
- const element = document.getElementById(id);
1176
- if (element instanceof FrameElement) {
1177
- return element;
1178
- }
1230
+ function elementType(element) {
1231
+ if (elementIsScript(element)) {
1232
+ return "script";
1233
+ } else if (elementIsStylesheet(element)) {
1234
+ return "stylesheet";
1179
1235
  }
1180
1236
  }
1181
1237
 
1182
- function activateElement(element) {
1183
- if (element && element.ownerDocument !== document) {
1184
- element = document.importNode(element, true);
1185
- }
1186
- if (element instanceof FrameElement) {
1187
- return element;
1188
- }
1238
+ function elementIsTracked(element) {
1239
+ return element.getAttribute("data-turbo-track") == "reload";
1189
1240
  }
1190
1241
 
1191
- const StreamActions = {
1192
- append() {
1193
- var _a;
1194
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
1195
- },
1196
- prepend() {
1197
- var _a;
1198
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
1199
- },
1200
- remove() {
1201
- var _a;
1202
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
1203
- },
1204
- replace() {
1205
- var _a;
1206
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
1207
- },
1208
- update() {
1209
- if (this.targetElement) {
1210
- this.targetElement.innerHTML = "";
1211
- this.targetElement.append(this.templateContent);
1212
- }
1242
+ function elementIsScript(element) {
1243
+ const tagName = element.tagName.toLowerCase();
1244
+ return tagName == "script";
1245
+ }
1246
+
1247
+ function elementIsNoscript(element) {
1248
+ const tagName = element.tagName.toLowerCase();
1249
+ return tagName == "noscript";
1250
+ }
1251
+
1252
+ function elementIsStylesheet(element) {
1253
+ const tagName = element.tagName.toLowerCase();
1254
+ return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
1255
+ }
1256
+
1257
+ function elementIsMetaElementWithName(element, name) {
1258
+ const tagName = element.tagName.toLowerCase();
1259
+ return tagName == "meta" && element.getAttribute("name") == name;
1260
+ }
1261
+
1262
+ function elementWithoutNonce(element) {
1263
+ if (element.hasAttribute("nonce")) {
1264
+ element.setAttribute("nonce", "");
1213
1265
  }
1214
- };
1266
+ return element;
1267
+ }
1215
1268
 
1216
- class StreamElement extends HTMLElement {
1217
- async connectedCallback() {
1218
- try {
1219
- await this.render();
1220
- } catch (error) {
1221
- console.error(error);
1222
- } finally {
1223
- this.disconnect();
1224
- }
1269
+ class PageSnapshot extends Snapshot {
1270
+ constructor(element, headSnapshot) {
1271
+ super(element);
1272
+ this.headSnapshot = headSnapshot;
1225
1273
  }
1226
- async render() {
1227
- var _a;
1228
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
1229
- if (this.dispatchEvent(this.beforeRenderEvent)) {
1230
- await nextAnimationFrame();
1231
- this.performAction();
1232
- }
1233
- })();
1274
+ static fromHTMLString(html = "") {
1275
+ return this.fromDocument(parseHTMLDocument(html));
1234
1276
  }
1235
- disconnect() {
1236
- try {
1237
- this.remove();
1238
- } catch (_a) {}
1277
+ static fromElement(element) {
1278
+ return this.fromDocument(element.ownerDocument);
1239
1279
  }
1240
- get performAction() {
1241
- if (this.action) {
1242
- const actionFunction = StreamActions[this.action];
1243
- if (actionFunction) {
1244
- return actionFunction;
1245
- }
1246
- this.raise("unknown action");
1247
- }
1248
- this.raise("action attribute is missing");
1280
+ static fromDocument({head: head, body: body}) {
1281
+ return new this(body, new HeadSnapshot(head));
1249
1282
  }
1250
- get targetElement() {
1251
- var _a;
1252
- if (this.target) {
1253
- return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
1254
- }
1255
- this.raise("target attribute is missing");
1283
+ clone() {
1284
+ return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1256
1285
  }
1257
- get templateContent() {
1258
- return this.templateElement.content;
1286
+ get headElement() {
1287
+ return this.headSnapshot.element;
1259
1288
  }
1260
- get templateElement() {
1261
- if (this.firstElementChild instanceof HTMLTemplateElement) {
1262
- return this.firstElementChild;
1263
- }
1264
- this.raise("first child element must be a <template> element");
1289
+ get rootLocation() {
1290
+ var _a;
1291
+ const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1292
+ return expandURL(root);
1265
1293
  }
1266
- get action() {
1267
- return this.getAttribute("action");
1294
+ get cacheControlValue() {
1295
+ return this.getSetting("cache-control");
1268
1296
  }
1269
- get target() {
1270
- return this.getAttribute("target");
1297
+ get isPreviewable() {
1298
+ return this.cacheControlValue != "no-preview";
1271
1299
  }
1272
- raise(message) {
1273
- throw new Error(`${this.description}: ${message}`);
1300
+ get isCacheable() {
1301
+ return this.cacheControlValue != "no-cache";
1274
1302
  }
1275
- get description() {
1276
- var _a, _b;
1277
- return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
1303
+ get isVisitable() {
1304
+ return this.getSetting("visit-control") != "reload";
1278
1305
  }
1279
- get beforeRenderEvent() {
1280
- return new CustomEvent("turbo:before-stream-render", {
1281
- bubbles: true,
1282
- cancelable: true
1283
- });
1306
+ getSetting(name) {
1307
+ return this.headSnapshot.getMetaValue(`turbo-${name}`);
1284
1308
  }
1285
1309
  }
1286
1310
 
1287
- FrameElement.delegateConstructor = FrameController;
1311
+ var TimingMetric;
1288
1312
 
1289
- customElements.define("turbo-frame", FrameElement);
1313
+ (function(TimingMetric) {
1314
+ TimingMetric["visitStart"] = "visitStart";
1315
+ TimingMetric["requestStart"] = "requestStart";
1316
+ TimingMetric["requestEnd"] = "requestEnd";
1317
+ TimingMetric["visitEnd"] = "visitEnd";
1318
+ })(TimingMetric || (TimingMetric = {}));
1290
1319
 
1291
- customElements.define("turbo-stream", StreamElement);
1320
+ var VisitState;
1292
1321
 
1293
- (() => {
1294
- let element = document.currentScript;
1295
- if (!element) return;
1296
- if (element.hasAttribute("data-turbo-suppress-warning")) return;
1297
- while (element = element.parentElement) {
1298
- if (element == document.body) {
1299
- return console.warn(unindent`
1300
- You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
1322
+ (function(VisitState) {
1323
+ VisitState["initialized"] = "initialized";
1324
+ VisitState["started"] = "started";
1325
+ VisitState["canceled"] = "canceled";
1326
+ VisitState["failed"] = "failed";
1327
+ VisitState["completed"] = "completed";
1328
+ })(VisitState || (VisitState = {}));
1301
1329
 
1302
- Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
1330
+ const defaultOptions = {
1331
+ action: "advance",
1332
+ historyChanged: false
1333
+ };
1303
1334
 
1304
- For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
1305
-
1306
- ——
1307
- Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
1308
- `, element.outerHTML);
1309
- }
1310
- }
1311
- })();
1312
-
1313
- class ProgressBar {
1314
- constructor() {
1315
- this.hiding = false;
1316
- this.value = 0;
1317
- this.visible = false;
1318
- this.trickle = () => {
1319
- this.setValue(this.value + Math.random() / 100);
1320
- };
1321
- this.stylesheetElement = this.createStylesheetElement();
1322
- this.progressElement = this.createProgressElement();
1323
- this.installStylesheetElement();
1324
- this.setValue(0);
1325
- }
1326
- static get defaultCSS() {
1327
- return unindent`
1328
- .turbo-progress-bar {
1329
- position: fixed;
1330
- display: block;
1331
- top: 0;
1332
- left: 0;
1333
- height: 3px;
1334
- background: #0076ff;
1335
- z-index: 9999;
1336
- transition:
1337
- width ${ProgressBar.animationDuration}ms ease-out,
1338
- opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
1339
- transform: translate3d(0, 0, 0);
1340
- }
1341
- `;
1342
- }
1343
- show() {
1344
- if (!this.visible) {
1345
- this.visible = true;
1346
- this.installProgressElement();
1347
- this.startTrickling();
1348
- }
1349
- }
1350
- hide() {
1351
- if (this.visible && !this.hiding) {
1352
- this.hiding = true;
1353
- this.fadeProgressElement((() => {
1354
- this.uninstallProgressElement();
1355
- this.stopTrickling();
1356
- this.visible = false;
1357
- this.hiding = false;
1358
- }));
1359
- }
1360
- }
1361
- setValue(value) {
1362
- this.value = value;
1363
- this.refresh();
1364
- }
1365
- installStylesheetElement() {
1366
- document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
1367
- }
1368
- installProgressElement() {
1369
- this.progressElement.style.width = "0";
1370
- this.progressElement.style.opacity = "1";
1371
- document.documentElement.insertBefore(this.progressElement, document.body);
1372
- this.refresh();
1373
- }
1374
- fadeProgressElement(callback) {
1375
- this.progressElement.style.opacity = "0";
1376
- setTimeout(callback, ProgressBar.animationDuration * 1.5);
1377
- }
1378
- uninstallProgressElement() {
1379
- if (this.progressElement.parentNode) {
1380
- document.documentElement.removeChild(this.progressElement);
1381
- }
1382
- }
1383
- startTrickling() {
1384
- if (!this.trickleInterval) {
1385
- this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
1386
- }
1387
- }
1388
- stopTrickling() {
1389
- window.clearInterval(this.trickleInterval);
1390
- delete this.trickleInterval;
1391
- }
1392
- refresh() {
1393
- requestAnimationFrame((() => {
1394
- this.progressElement.style.width = `${10 + this.value * 90}%`;
1395
- }));
1396
- }
1397
- createStylesheetElement() {
1398
- const element = document.createElement("style");
1399
- element.type = "text/css";
1400
- element.textContent = ProgressBar.defaultCSS;
1401
- return element;
1402
- }
1403
- createProgressElement() {
1404
- const element = document.createElement("div");
1405
- element.className = "turbo-progress-bar";
1406
- return element;
1407
- }
1408
- }
1409
-
1410
- ProgressBar.animationDuration = 300;
1411
-
1412
- class HeadSnapshot extends Snapshot {
1413
- constructor() {
1414
- super(...arguments);
1415
- this.detailsByOuterHTML = this.children.reduce(((result, element) => {
1416
- const {outerHTML: outerHTML} = element;
1417
- const details = outerHTML in result ? result[outerHTML] : {
1418
- type: elementType(element),
1419
- tracked: elementIsTracked(element),
1420
- elements: []
1421
- };
1422
- return Object.assign(Object.assign({}, result), {
1423
- [outerHTML]: Object.assign(Object.assign({}, details), {
1424
- elements: [ ...details.elements, element ]
1425
- })
1426
- });
1427
- }), {});
1428
- }
1429
- get trackedElementSignature() {
1430
- return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1431
- }
1432
- getScriptElementsNotInSnapshot(snapshot) {
1433
- return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1434
- }
1435
- getStylesheetElementsNotInSnapshot(snapshot) {
1436
- return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1437
- }
1438
- getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1439
- return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
1440
- }
1441
- get provisionalElements() {
1442
- return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1443
- const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
1444
- if (type == null && !tracked) {
1445
- return [ ...result, ...elements ];
1446
- } else if (elements.length > 1) {
1447
- return [ ...result, ...elements.slice(1) ];
1448
- } else {
1449
- return result;
1450
- }
1451
- }), []);
1452
- }
1453
- getMetaValue(name) {
1454
- const element = this.findMetaElementByName(name);
1455
- return element ? element.getAttribute("content") : null;
1456
- }
1457
- findMetaElementByName(name) {
1458
- return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1459
- const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1460
- return elementIsMetaElementWithName(element, name) ? element : result;
1461
- }), undefined);
1462
- }
1463
- }
1464
-
1465
- function elementType(element) {
1466
- if (elementIsScript(element)) {
1467
- return "script";
1468
- } else if (elementIsStylesheet(element)) {
1469
- return "stylesheet";
1470
- }
1471
- }
1472
-
1473
- function elementIsTracked(element) {
1474
- return element.getAttribute("data-turbo-track") == "reload";
1475
- }
1476
-
1477
- function elementIsScript(element) {
1478
- const tagName = element.tagName.toLowerCase();
1479
- return tagName == "script";
1480
- }
1481
-
1482
- function elementIsStylesheet(element) {
1483
- const tagName = element.tagName.toLowerCase();
1484
- return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
1485
- }
1486
-
1487
- function elementIsMetaElementWithName(element, name) {
1488
- const tagName = element.tagName.toLowerCase();
1489
- return tagName == "meta" && element.getAttribute("name") == name;
1490
- }
1491
-
1492
- class PageSnapshot extends Snapshot {
1493
- constructor(element, headSnapshot) {
1494
- super(element);
1495
- this.headSnapshot = headSnapshot;
1496
- }
1497
- static fromHTMLString(html = "") {
1498
- return this.fromDocument(parseHTMLDocument(html));
1499
- }
1500
- static fromElement(element) {
1501
- return this.fromDocument(element.ownerDocument);
1502
- }
1503
- static fromDocument({head: head, body: body}) {
1504
- return new this(body, new HeadSnapshot(head));
1505
- }
1506
- clone() {
1507
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1508
- }
1509
- get headElement() {
1510
- return this.headSnapshot.element;
1511
- }
1512
- get rootLocation() {
1513
- var _a;
1514
- const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1515
- return expandURL(root);
1516
- }
1517
- get cacheControlValue() {
1518
- return this.getSetting("cache-control");
1519
- }
1520
- get isPreviewable() {
1521
- return this.cacheControlValue != "no-preview";
1522
- }
1523
- get isCacheable() {
1524
- return this.cacheControlValue != "no-cache";
1525
- }
1526
- get isVisitable() {
1527
- return this.getSetting("visit-control") != "reload";
1528
- }
1529
- getSetting(name) {
1530
- return this.headSnapshot.getMetaValue(`turbo-${name}`);
1531
- }
1532
- }
1533
-
1534
- var TimingMetric;
1535
-
1536
- (function(TimingMetric) {
1537
- TimingMetric["visitStart"] = "visitStart";
1538
- TimingMetric["requestStart"] = "requestStart";
1539
- TimingMetric["requestEnd"] = "requestEnd";
1540
- TimingMetric["visitEnd"] = "visitEnd";
1541
- })(TimingMetric || (TimingMetric = {}));
1542
-
1543
- var VisitState;
1544
-
1545
- (function(VisitState) {
1546
- VisitState["initialized"] = "initialized";
1547
- VisitState["started"] = "started";
1548
- VisitState["canceled"] = "canceled";
1549
- VisitState["failed"] = "failed";
1550
- VisitState["completed"] = "completed";
1551
- })(VisitState || (VisitState = {}));
1552
-
1553
- const defaultOptions = {
1554
- action: "advance",
1555
- historyChanged: false
1556
- };
1557
-
1558
- var SystemStatusCode;
1335
+ var SystemStatusCode;
1559
1336
 
1560
1337
  (function(SystemStatusCode) {
1561
1338
  SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
@@ -1581,6 +1358,7 @@ class Visit {
1581
1358
  this.referrer = referrer;
1582
1359
  this.snapshotHTML = snapshotHTML;
1583
1360
  this.response = response;
1361
+ this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1584
1362
  }
1585
1363
  get adapter() {
1586
1364
  return this.delegate.adapter;
@@ -1594,6 +1372,9 @@ class Visit {
1594
1372
  get restorationData() {
1595
1373
  return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
1596
1374
  }
1375
+ get silent() {
1376
+ return this.isSamePage;
1377
+ }
1597
1378
  start() {
1598
1379
  if (this.state == VisitState.initialized) {
1599
1380
  this.recordTimingMetric(TimingMetric.visitStart);
@@ -1617,6 +1398,7 @@ class Visit {
1617
1398
  this.state = VisitState.completed;
1618
1399
  this.adapter.visitCompleted(this);
1619
1400
  this.delegate.visitCompleted(this);
1401
+ this.followRedirect();
1620
1402
  }
1621
1403
  }
1622
1404
  fail() {
@@ -1673,6 +1455,7 @@ class Visit {
1673
1455
  const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
1674
1456
  this.render((async () => {
1675
1457
  this.cacheSnapshot();
1458
+ if (this.view.renderPromise) await this.view.renderPromise;
1676
1459
  if (isSuccessful(statusCode) && responseHTML != null) {
1677
1460
  await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1678
1461
  this.adapter.visitRendered(this);
@@ -1707,21 +1490,36 @@ class Visit {
1707
1490
  const isPreview = this.shouldIssueRequest();
1708
1491
  this.render((async () => {
1709
1492
  this.cacheSnapshot();
1710
- await this.view.renderPage(snapshot);
1711
- this.adapter.visitRendered(this);
1712
- if (!isPreview) {
1713
- this.complete();
1493
+ if (this.isSamePage) {
1494
+ this.adapter.visitRendered(this);
1495
+ } else {
1496
+ if (this.view.renderPromise) await this.view.renderPromise;
1497
+ await this.view.renderPage(snapshot, isPreview);
1498
+ this.adapter.visitRendered(this);
1499
+ if (!isPreview) {
1500
+ this.complete();
1501
+ }
1714
1502
  }
1715
1503
  }));
1716
1504
  }
1717
1505
  }
1718
1506
  followRedirect() {
1719
1507
  if (this.redirectedToLocation && !this.followedRedirect) {
1720
- this.location = this.redirectedToLocation;
1721
- this.history.replace(this.redirectedToLocation, this.restorationIdentifier);
1508
+ this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1509
+ action: "replace",
1510
+ response: this.response
1511
+ });
1722
1512
  this.followedRedirect = true;
1723
1513
  }
1724
1514
  }
1515
+ goToSamePageAnchor() {
1516
+ if (this.isSamePage) {
1517
+ this.render((async () => {
1518
+ this.cacheSnapshot();
1519
+ this.adapter.visitRendered(this);
1520
+ }));
1521
+ }
1522
+ }
1725
1523
  requestStarted() {
1726
1524
  this.startRequest();
1727
1525
  }
@@ -1764,9 +1562,12 @@ class Visit {
1764
1562
  performScroll() {
1765
1563
  if (!this.scrolled) {
1766
1564
  if (this.action == "restore") {
1767
- this.scrollToRestoredPosition() || this.scrollToTop();
1565
+ this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1768
1566
  } else {
1769
- this.scrollToAnchor() || this.scrollToTop();
1567
+ this.scrollToAnchor() || this.view.scrollToTop();
1568
+ }
1569
+ if (this.isSamePage) {
1570
+ this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
1770
1571
  }
1771
1572
  this.scrolled = true;
1772
1573
  }
@@ -1779,17 +1580,12 @@ class Visit {
1779
1580
  }
1780
1581
  }
1781
1582
  scrollToAnchor() {
1782
- if (getAnchor(this.location) != null) {
1783
- this.view.scrollToAnchor(getAnchor(this.location));
1583
+ const anchor = getAnchor(this.location);
1584
+ if (anchor != null) {
1585
+ this.view.scrollToAnchor(anchor);
1784
1586
  return true;
1785
1587
  }
1786
1588
  }
1787
- scrollToTop() {
1788
- this.view.scrollToPosition({
1789
- x: 0,
1790
- y: 0
1791
- });
1792
- }
1793
1589
  recordTimingMetric(metric) {
1794
1590
  this.timingMetrics[metric] = (new Date).getTime();
1795
1591
  }
@@ -1810,7 +1606,13 @@ class Visit {
1810
1606
  return typeof this.response == "object";
1811
1607
  }
1812
1608
  shouldIssueRequest() {
1813
- return this.action == "restore" ? !this.hasCachedSnapshot() : true;
1609
+ if (this.isSamePage) {
1610
+ return false;
1611
+ } else if (this.action == "restore") {
1612
+ return !this.hasCachedSnapshot();
1613
+ } else {
1614
+ return true;
1615
+ }
1814
1616
  }
1815
1617
  cacheSnapshot() {
1816
1618
  if (!this.snapshotCached) {
@@ -1823,7 +1625,7 @@ class Visit {
1823
1625
  await new Promise((resolve => {
1824
1626
  this.frame = requestAnimationFrame((() => resolve()));
1825
1627
  }));
1826
- callback();
1628
+ await callback();
1827
1629
  delete this.frame;
1828
1630
  this.performScroll();
1829
1631
  }
@@ -1853,12 +1655,13 @@ class BrowserAdapter {
1853
1655
  visitStarted(visit) {
1854
1656
  visit.issueRequest();
1855
1657
  visit.changeHistory();
1658
+ visit.goToSamePageAnchor();
1856
1659
  visit.loadCachedSnapshot();
1857
1660
  }
1858
1661
  visitRequestStarted(visit) {
1859
1662
  this.progressBar.setValue(0);
1860
1663
  if (visit.hasCachedSnapshot() || visit.action != "restore") {
1861
- this.showProgressBarAfterDelay();
1664
+ this.showVisitProgressBarAfterDelay();
1862
1665
  } else {
1863
1666
  this.showProgressBar();
1864
1667
  }
@@ -1879,24 +1682,42 @@ class BrowserAdapter {
1879
1682
  }
1880
1683
  visitRequestFinished(visit) {
1881
1684
  this.progressBar.setValue(1);
1882
- this.hideProgressBar();
1883
- }
1884
- visitCompleted(visit) {
1885
- visit.followRedirect();
1685
+ this.hideVisitProgressBar();
1886
1686
  }
1687
+ visitCompleted(visit) {}
1887
1688
  pageInvalidated() {
1888
1689
  this.reload();
1889
1690
  }
1890
1691
  visitFailed(visit) {}
1891
1692
  visitRendered(visit) {}
1892
- showProgressBarAfterDelay() {
1893
- this.progressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
1693
+ formSubmissionStarted(formSubmission) {
1694
+ this.progressBar.setValue(0);
1695
+ this.showFormProgressBarAfterDelay();
1696
+ }
1697
+ formSubmissionFinished(formSubmission) {
1698
+ this.progressBar.setValue(1);
1699
+ this.hideFormProgressBar();
1894
1700
  }
1895
- hideProgressBar() {
1701
+ showVisitProgressBarAfterDelay() {
1702
+ this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
1703
+ }
1704
+ hideVisitProgressBar() {
1896
1705
  this.progressBar.hide();
1897
- if (this.progressBarTimeout != null) {
1898
- window.clearTimeout(this.progressBarTimeout);
1899
- delete this.progressBarTimeout;
1706
+ if (this.visitProgressBarTimeout != null) {
1707
+ window.clearTimeout(this.visitProgressBarTimeout);
1708
+ delete this.visitProgressBarTimeout;
1709
+ }
1710
+ }
1711
+ showFormProgressBarAfterDelay() {
1712
+ if (this.formProgressBarTimeout == null) {
1713
+ this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
1714
+ }
1715
+ }
1716
+ hideFormProgressBar() {
1717
+ this.progressBar.hide();
1718
+ if (this.formProgressBarTimeout != null) {
1719
+ window.clearTimeout(this.formProgressBarTimeout);
1720
+ delete this.formProgressBarTimeout;
1900
1721
  }
1901
1722
  }
1902
1723
  reload() {
@@ -1907,6 +1728,30 @@ class BrowserAdapter {
1907
1728
  }
1908
1729
  }
1909
1730
 
1731
+ class CacheObserver {
1732
+ constructor() {
1733
+ this.started = false;
1734
+ }
1735
+ start() {
1736
+ if (!this.started) {
1737
+ this.started = true;
1738
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1739
+ }
1740
+ }
1741
+ stop() {
1742
+ if (this.started) {
1743
+ this.started = false;
1744
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1745
+ }
1746
+ }
1747
+ removeStaleElements() {
1748
+ const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1749
+ for (const element of staleElements) {
1750
+ element.remove();
1751
+ }
1752
+ }
1753
+ }
1754
+
1910
1755
  class FormSubmitObserver {
1911
1756
  constructor(delegate) {
1912
1757
  this.started = false;
@@ -1963,6 +1808,7 @@ class FrameRedirector {
1963
1808
  linkClickIntercepted(element, url) {
1964
1809
  const frame = this.findFrameElement(element);
1965
1810
  if (frame) {
1811
+ frame.setAttribute("reloadable", "");
1966
1812
  frame.src = url;
1967
1813
  }
1968
1814
  }
@@ -1970,17 +1816,18 @@ class FrameRedirector {
1970
1816
  return this.shouldRedirect(element, submitter);
1971
1817
  }
1972
1818
  formSubmissionIntercepted(element, submitter) {
1973
- const frame = this.findFrameElement(element);
1819
+ const frame = this.findFrameElement(element, submitter);
1974
1820
  if (frame) {
1821
+ frame.removeAttribute("reloadable");
1975
1822
  frame.delegate.formSubmissionIntercepted(element, submitter);
1976
1823
  }
1977
1824
  }
1978
1825
  shouldRedirect(element, submitter) {
1979
- const frame = this.findFrameElement(element);
1826
+ const frame = this.findFrameElement(element, submitter);
1980
1827
  return frame ? frame != element.closest("turbo-frame") : false;
1981
1828
  }
1982
- findFrameElement(element) {
1983
- const id = element.getAttribute("data-turbo-frame");
1829
+ findFrameElement(element, submitter) {
1830
+ const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
1984
1831
  if (id && id != "_top") {
1985
1832
  const frame = this.element.querySelector(`#${id}:not([disabled])`);
1986
1833
  if (frame instanceof FrameElement) {
@@ -2082,7 +1929,8 @@ class LinkClickObserver {
2082
1929
  };
2083
1930
  this.clickBubbled = event => {
2084
1931
  if (this.clickEventIsSignificant(event)) {
2085
- const link = this.findLinkFromClickTarget(event.target);
1932
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1933
+ const link = this.findLinkFromClickTarget(target);
2086
1934
  if (link) {
2087
1935
  const location = this.getLocationForLink(link);
2088
1936
  if (this.delegate.willFollowLinkToLocation(link, location)) {
@@ -2119,12 +1967,16 @@ class LinkClickObserver {
2119
1967
  }
2120
1968
  }
2121
1969
 
1970
+ function isAction(action) {
1971
+ return action == "advance" || action == "replace" || action == "restore";
1972
+ }
1973
+
2122
1974
  class Navigator {
2123
1975
  constructor(delegate) {
2124
1976
  this.delegate = delegate;
2125
1977
  }
2126
1978
  proposeVisit(location, options = {}) {
2127
- if (this.delegate.allowsVisitingLocation(location)) {
1979
+ if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2128
1980
  this.delegate.visitProposedToLocation(location, options);
2129
1981
  }
2130
1982
  }
@@ -2138,8 +1990,10 @@ class Navigator {
2138
1990
  submitForm(form, submitter) {
2139
1991
  this.stop();
2140
1992
  this.formSubmission = new FormSubmission(this, form, submitter, true);
2141
- if (this.formSubmission.fetchRequest.isIdempotent) {
2142
- this.proposeVisit(this.formSubmission.fetchRequest.url);
1993
+ if (this.formSubmission.isIdempotent) {
1994
+ this.proposeVisit(this.formSubmission.fetchRequest.url, {
1995
+ action: this.getActionForFormSubmission(this.formSubmission)
1996
+ });
2143
1997
  } else {
2144
1998
  this.formSubmission.start();
2145
1999
  }
@@ -2163,7 +2017,11 @@ class Navigator {
2163
2017
  get history() {
2164
2018
  return this.delegate.history;
2165
2019
  }
2166
- formSubmissionStarted(formSubmission) {}
2020
+ formSubmissionStarted(formSubmission) {
2021
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2022
+ this.adapter.formSubmissionStarted(formSubmission);
2023
+ }
2024
+ }
2167
2025
  async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
2168
2026
  if (formSubmission == this.formSubmission) {
2169
2027
  const responseHTML = await fetchResponse.responseHTML;
@@ -2186,24 +2044,49 @@ class Navigator {
2186
2044
  const responseHTML = await fetchResponse.responseHTML;
2187
2045
  if (responseHTML) {
2188
2046
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2189
- await this.view.renderPage(snapshot);
2047
+ if (fetchResponse.serverError) {
2048
+ await this.view.renderError(snapshot);
2049
+ } else {
2050
+ await this.view.renderPage(snapshot);
2051
+ }
2052
+ this.view.scrollToTop();
2190
2053
  this.view.clearSnapshotCache();
2191
2054
  }
2192
2055
  }
2193
- formSubmissionErrored(formSubmission, error) {}
2194
- formSubmissionFinished(formSubmission) {}
2056
+ formSubmissionErrored(formSubmission, error) {
2057
+ console.error(error);
2058
+ }
2059
+ formSubmissionFinished(formSubmission) {
2060
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2061
+ this.adapter.formSubmissionFinished(formSubmission);
2062
+ }
2063
+ }
2195
2064
  visitStarted(visit) {
2196
2065
  this.delegate.visitStarted(visit);
2197
2066
  }
2198
2067
  visitCompleted(visit) {
2199
2068
  this.delegate.visitCompleted(visit);
2200
2069
  }
2070
+ locationWithActionIsSamePage(location, action) {
2071
+ const anchor = getAnchor(location);
2072
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2073
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2074
+ return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2075
+ }
2076
+ visitScrolledToSamePageLocation(oldURL, newURL) {
2077
+ this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
2078
+ }
2201
2079
  get location() {
2202
2080
  return this.history.location;
2203
2081
  }
2204
2082
  get restorationIdentifier() {
2205
2083
  return this.history.restorationIdentifier;
2206
2084
  }
2085
+ getActionForFormSubmission(formSubmission) {
2086
+ const {formElement: formElement, submitter: submitter} = formSubmission;
2087
+ const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
2088
+ return isAction(action) ? action : "advance";
2089
+ }
2207
2090
  }
2208
2091
 
2209
2092
  var PageStage;
@@ -2366,10 +2249,6 @@ function fetchResponseIsStream(response) {
2366
2249
  return contentType.startsWith(StreamMessage.contentType);
2367
2250
  }
2368
2251
 
2369
- function isAction(action) {
2370
- return action == "advance" || action == "replace" || action == "restore";
2371
- }
2372
-
2373
2252
  class ErrorRenderer extends Renderer {
2374
2253
  async render() {
2375
2254
  this.replaceHeadAndBody();
@@ -2409,7 +2288,7 @@ class PageRenderer extends Renderer {
2409
2288
  }
2410
2289
  finishRendering() {
2411
2290
  super.finishRendering();
2412
- if (this.isPreview) {
2291
+ if (!this.isPreview) {
2413
2292
  this.focusFirstAutofocusableElement();
2414
2293
  }
2415
2294
  }
@@ -2464,12 +2343,12 @@ class PageRenderer extends Renderer {
2464
2343
  activateNewBodyScriptElements() {
2465
2344
  for (const inertScriptElement of this.newBodyScriptElements) {
2466
2345
  const activatedScriptElement = this.createScriptElement(inertScriptElement);
2467
- replaceElementWithElement(inertScriptElement, activatedScriptElement);
2346
+ inertScriptElement.replaceWith(activatedScriptElement);
2468
2347
  }
2469
2348
  }
2470
2349
  assignNewBody() {
2471
2350
  if (document.body && this.newElement instanceof HTMLBodyElement) {
2472
- replaceElementWithElement(document.body, this.newElement);
2351
+ document.body.replaceWith(this.newElement);
2473
2352
  } else {
2474
2353
  document.documentElement.appendChild(this.newElement);
2475
2354
  }
@@ -2487,7 +2366,7 @@ class PageRenderer extends Renderer {
2487
2366
  return this.newHeadSnapshot.provisionalElements;
2488
2367
  }
2489
2368
  get newBodyScriptElements() {
2490
- return [ ...this.newElement.querySelectorAll("script") ];
2369
+ return this.newElement.querySelectorAll("script");
2491
2370
  }
2492
2371
  }
2493
2372
 
@@ -2547,7 +2426,7 @@ class PageView extends View {
2547
2426
  }
2548
2427
  renderError(snapshot) {
2549
2428
  const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2550
- this.render(renderer);
2429
+ return this.render(renderer);
2551
2430
  }
2552
2431
  clearSnapshotCache() {
2553
2432
  this.snapshotCache.clear();
@@ -2578,11 +2457,13 @@ class Session {
2578
2457
  this.view = new PageView(this, document.documentElement);
2579
2458
  this.adapter = new BrowserAdapter(this);
2580
2459
  this.pageObserver = new PageObserver(this);
2460
+ this.cacheObserver = new CacheObserver;
2581
2461
  this.linkClickObserver = new LinkClickObserver(this);
2582
2462
  this.formSubmitObserver = new FormSubmitObserver(this);
2583
2463
  this.scrollObserver = new ScrollObserver(this);
2584
2464
  this.streamObserver = new StreamObserver(this);
2585
2465
  this.frameRedirector = new FrameRedirector(document.documentElement);
2466
+ this.drive = true;
2586
2467
  this.enabled = true;
2587
2468
  this.progressBarDelay = 500;
2588
2469
  this.started = false;
@@ -2590,6 +2471,7 @@ class Session {
2590
2471
  start() {
2591
2472
  if (!this.started) {
2592
2473
  this.pageObserver.start();
2474
+ this.cacheObserver.start();
2593
2475
  this.linkClickObserver.start();
2594
2476
  this.formSubmitObserver.start();
2595
2477
  this.scrollObserver.start();
@@ -2606,6 +2488,7 @@ class Session {
2606
2488
  stop() {
2607
2489
  if (this.started) {
2608
2490
  this.pageObserver.stop();
2491
+ this.cacheObserver.stop();
2609
2492
  this.linkClickObserver.stop();
2610
2493
  this.formSubmitObserver.stop();
2611
2494
  this.scrollObserver.stop();
@@ -2642,9 +2525,9 @@ class Session {
2642
2525
  get restorationIdentifier() {
2643
2526
  return this.history.restorationIdentifier;
2644
2527
  }
2645
- historyPoppedToLocationWithRestorationIdentifier(location) {
2528
+ historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2646
2529
  if (this.enabled) {
2647
- this.navigator.proposeVisit(location, {
2530
+ this.navigator.startVisit(location, restorationIdentifier, {
2648
2531
  action: "restore",
2649
2532
  historyChanged: true
2650
2533
  });
@@ -2652,197 +2535,733 @@ class Session {
2652
2535
  this.adapter.pageInvalidated();
2653
2536
  }
2654
2537
  }
2655
- scrollPositionChanged(position) {
2656
- this.history.updateRestorationData({
2657
- scrollPosition: position
2658
- });
2538
+ scrollPositionChanged(position) {
2539
+ this.history.updateRestorationData({
2540
+ scrollPosition: position
2541
+ });
2542
+ }
2543
+ willFollowLinkToLocation(link, location) {
2544
+ return this.elementDriveEnabled(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
2545
+ }
2546
+ followedLinkToLocation(link, location) {
2547
+ const action = this.getActionForLink(link);
2548
+ this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, {
2549
+ action: action
2550
+ });
2551
+ }
2552
+ convertLinkWithMethodClickToFormSubmission(link) {
2553
+ var _a;
2554
+ const linkMethod = link.getAttribute("data-turbo-method");
2555
+ if (linkMethod) {
2556
+ const form = document.createElement("form");
2557
+ form.method = linkMethod;
2558
+ form.action = link.getAttribute("href") || "undefined";
2559
+ (_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
2560
+ return dispatch("submit", {
2561
+ cancelable: true,
2562
+ target: form
2563
+ });
2564
+ } else {
2565
+ return false;
2566
+ }
2567
+ }
2568
+ allowsVisitingLocationWithAction(location, action) {
2569
+ return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2570
+ }
2571
+ visitProposedToLocation(location, options) {
2572
+ extendURLWithDeprecatedProperties(location);
2573
+ this.adapter.visitProposedToLocation(location, options);
2574
+ }
2575
+ visitStarted(visit) {
2576
+ extendURLWithDeprecatedProperties(visit.location);
2577
+ if (!visit.silent) {
2578
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2579
+ }
2580
+ }
2581
+ visitCompleted(visit) {
2582
+ this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2583
+ }
2584
+ locationWithActionIsSamePage(location, action) {
2585
+ return this.navigator.locationWithActionIsSamePage(location, action);
2586
+ }
2587
+ visitScrolledToSamePageLocation(oldURL, newURL) {
2588
+ this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
2589
+ }
2590
+ willSubmitForm(form, submitter) {
2591
+ return this.elementDriveEnabled(form) && this.elementDriveEnabled(submitter);
2592
+ }
2593
+ formSubmitted(form, submitter) {
2594
+ this.navigator.submitForm(form, submitter);
2595
+ }
2596
+ pageBecameInteractive() {
2597
+ this.view.lastRenderedLocation = this.location;
2598
+ this.notifyApplicationAfterPageLoad();
2599
+ }
2600
+ pageLoaded() {
2601
+ this.history.assumeControlOfScrollRestoration();
2602
+ }
2603
+ pageWillUnload() {
2604
+ this.history.relinquishControlOfScrollRestoration();
2605
+ }
2606
+ receivedMessageFromStream(message) {
2607
+ this.renderStreamMessage(message);
2608
+ }
2609
+ viewWillCacheSnapshot() {
2610
+ var _a;
2611
+ if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
2612
+ this.notifyApplicationBeforeCachingSnapshot();
2613
+ }
2614
+ }
2615
+ allowsImmediateRender({element: element}, resume) {
2616
+ const event = this.notifyApplicationBeforeRender(element, resume);
2617
+ return !event.defaultPrevented;
2618
+ }
2619
+ viewRenderedSnapshot(snapshot, isPreview) {
2620
+ this.view.lastRenderedLocation = this.history.location;
2621
+ this.notifyApplicationAfterRender();
2622
+ }
2623
+ viewInvalidated() {
2624
+ this.adapter.pageInvalidated();
2625
+ }
2626
+ frameLoaded(frame) {
2627
+ this.notifyApplicationAfterFrameLoad(frame);
2628
+ }
2629
+ frameRendered(fetchResponse, frame) {
2630
+ this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2631
+ }
2632
+ applicationAllowsFollowingLinkToLocation(link, location) {
2633
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2634
+ return !event.defaultPrevented;
2635
+ }
2636
+ applicationAllowsVisitingLocation(location) {
2637
+ const event = this.notifyApplicationBeforeVisitingLocation(location);
2638
+ return !event.defaultPrevented;
2639
+ }
2640
+ notifyApplicationAfterClickingLinkToLocation(link, location) {
2641
+ return dispatch("turbo:click", {
2642
+ target: link,
2643
+ detail: {
2644
+ url: location.href
2645
+ },
2646
+ cancelable: true
2647
+ });
2648
+ }
2649
+ notifyApplicationBeforeVisitingLocation(location) {
2650
+ return dispatch("turbo:before-visit", {
2651
+ detail: {
2652
+ url: location.href
2653
+ },
2654
+ cancelable: true
2655
+ });
2656
+ }
2657
+ notifyApplicationAfterVisitingLocation(location, action) {
2658
+ return dispatch("turbo:visit", {
2659
+ detail: {
2660
+ url: location.href,
2661
+ action: action
2662
+ }
2663
+ });
2664
+ }
2665
+ notifyApplicationBeforeCachingSnapshot() {
2666
+ return dispatch("turbo:before-cache");
2667
+ }
2668
+ notifyApplicationBeforeRender(newBody, resume) {
2669
+ return dispatch("turbo:before-render", {
2670
+ detail: {
2671
+ newBody: newBody,
2672
+ resume: resume
2673
+ },
2674
+ cancelable: true
2675
+ });
2676
+ }
2677
+ notifyApplicationAfterRender() {
2678
+ return dispatch("turbo:render");
2679
+ }
2680
+ notifyApplicationAfterPageLoad(timing = {}) {
2681
+ return dispatch("turbo:load", {
2682
+ detail: {
2683
+ url: this.location.href,
2684
+ timing: timing
2685
+ }
2686
+ });
2687
+ }
2688
+ notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2689
+ dispatchEvent(new HashChangeEvent("hashchange", {
2690
+ oldURL: oldURL.toString(),
2691
+ newURL: newURL.toString()
2692
+ }));
2693
+ }
2694
+ notifyApplicationAfterFrameLoad(frame) {
2695
+ return dispatch("turbo:frame-load", {
2696
+ target: frame
2697
+ });
2698
+ }
2699
+ notifyApplicationAfterFrameRender(fetchResponse, frame) {
2700
+ return dispatch("turbo:frame-render", {
2701
+ detail: {
2702
+ fetchResponse: fetchResponse
2703
+ },
2704
+ target: frame,
2705
+ cancelable: true
2706
+ });
2707
+ }
2708
+ elementDriveEnabled(element) {
2709
+ const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2710
+ if (this.drive) {
2711
+ if (container) {
2712
+ return container.getAttribute("data-turbo") != "false";
2713
+ } else {
2714
+ return true;
2715
+ }
2716
+ } else {
2717
+ if (container) {
2718
+ return container.getAttribute("data-turbo") == "true";
2719
+ } else {
2720
+ return false;
2721
+ }
2722
+ }
2723
+ }
2724
+ getActionForLink(link) {
2725
+ const action = link.getAttribute("data-turbo-action");
2726
+ return isAction(action) ? action : "advance";
2727
+ }
2728
+ locationIsVisitable(location) {
2729
+ return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2730
+ }
2731
+ get snapshot() {
2732
+ return this.view.snapshot;
2733
+ }
2734
+ }
2735
+
2736
+ function extendURLWithDeprecatedProperties(url) {
2737
+ Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2738
+ }
2739
+
2740
+ const deprecatedLocationPropertyDescriptors = {
2741
+ absoluteURL: {
2742
+ get() {
2743
+ return this.toString();
2744
+ }
2745
+ }
2746
+ };
2747
+
2748
+ const session = new Session;
2749
+
2750
+ const {navigator: navigator} = session;
2751
+
2752
+ function start() {
2753
+ session.start();
2754
+ }
2755
+
2756
+ function registerAdapter(adapter) {
2757
+ session.registerAdapter(adapter);
2758
+ }
2759
+
2760
+ function visit(location, options) {
2761
+ session.visit(location, options);
2762
+ }
2763
+
2764
+ function connectStreamSource(source) {
2765
+ session.connectStreamSource(source);
2766
+ }
2767
+
2768
+ function disconnectStreamSource(source) {
2769
+ session.disconnectStreamSource(source);
2770
+ }
2771
+
2772
+ function renderStreamMessage(message) {
2773
+ session.renderStreamMessage(message);
2774
+ }
2775
+
2776
+ function clearCache() {
2777
+ session.clearCache();
2778
+ }
2779
+
2780
+ function setProgressBarDelay(delay) {
2781
+ session.setProgressBarDelay(delay);
2782
+ }
2783
+
2784
+ var Turbo = Object.freeze({
2785
+ __proto__: null,
2786
+ navigator: navigator,
2787
+ session: session,
2788
+ PageRenderer: PageRenderer,
2789
+ PageSnapshot: PageSnapshot,
2790
+ start: start,
2791
+ registerAdapter: registerAdapter,
2792
+ visit: visit,
2793
+ connectStreamSource: connectStreamSource,
2794
+ disconnectStreamSource: disconnectStreamSource,
2795
+ renderStreamMessage: renderStreamMessage,
2796
+ clearCache: clearCache,
2797
+ setProgressBarDelay: setProgressBarDelay
2798
+ });
2799
+
2800
+ class FrameController {
2801
+ constructor(element) {
2802
+ this.resolveVisitPromise = () => {};
2803
+ this.connected = false;
2804
+ this.hasBeenLoaded = false;
2805
+ this.settingSourceURL = false;
2806
+ this.element = element;
2807
+ this.view = new FrameView(this, this.element);
2808
+ this.appearanceObserver = new AppearanceObserver(this, this.element);
2809
+ this.linkInterceptor = new LinkInterceptor(this, this.element);
2810
+ this.formInterceptor = new FormInterceptor(this, this.element);
2811
+ }
2812
+ connect() {
2813
+ if (!this.connected) {
2814
+ this.connected = true;
2815
+ this.reloadable = false;
2816
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
2817
+ this.appearanceObserver.start();
2818
+ }
2819
+ this.linkInterceptor.start();
2820
+ this.formInterceptor.start();
2821
+ this.sourceURLChanged();
2822
+ }
2823
+ }
2824
+ disconnect() {
2825
+ if (this.connected) {
2826
+ this.connected = false;
2827
+ this.appearanceObserver.stop();
2828
+ this.linkInterceptor.stop();
2829
+ this.formInterceptor.stop();
2830
+ }
2831
+ }
2832
+ disabledChanged() {
2833
+ if (this.loadingStyle == FrameLoadingStyle.eager) {
2834
+ this.loadSourceURL();
2835
+ }
2836
+ }
2837
+ sourceURLChanged() {
2838
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2839
+ this.loadSourceURL();
2840
+ }
2841
+ }
2842
+ loadingStyleChanged() {
2843
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
2844
+ this.appearanceObserver.start();
2845
+ } else {
2846
+ this.appearanceObserver.stop();
2847
+ this.loadSourceURL();
2848
+ }
2849
+ }
2850
+ async loadSourceURL() {
2851
+ if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2852
+ const previousURL = this.currentURL;
2853
+ this.currentURL = this.sourceURL;
2854
+ if (this.sourceURL) {
2855
+ try {
2856
+ this.element.loaded = this.visit(this.sourceURL);
2857
+ this.appearanceObserver.stop();
2858
+ await this.element.loaded;
2859
+ this.hasBeenLoaded = true;
2860
+ session.frameLoaded(this.element);
2861
+ } catch (error) {
2862
+ this.currentURL = previousURL;
2863
+ throw error;
2864
+ }
2865
+ }
2866
+ }
2867
+ }
2868
+ async loadResponse(fetchResponse) {
2869
+ if (fetchResponse.redirected) {
2870
+ this.sourceURL = fetchResponse.response.url;
2871
+ }
2872
+ try {
2873
+ const html = await fetchResponse.responseHTML;
2874
+ if (html) {
2875
+ const {body: body} = parseHTMLDocument(html);
2876
+ const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2877
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
2878
+ if (this.view.renderPromise) await this.view.renderPromise;
2879
+ await this.view.render(renderer);
2880
+ session.frameRendered(fetchResponse, this.element);
2881
+ }
2882
+ } catch (error) {
2883
+ console.error(error);
2884
+ this.view.invalidate();
2885
+ }
2886
+ }
2887
+ elementAppearedInViewport(element) {
2888
+ this.loadSourceURL();
2889
+ }
2890
+ shouldInterceptLinkClick(element, url) {
2891
+ if (element.hasAttribute("data-turbo-method")) {
2892
+ return false;
2893
+ } else {
2894
+ return this.shouldInterceptNavigation(element);
2895
+ }
2896
+ }
2897
+ linkClickIntercepted(element, url) {
2898
+ this.reloadable = true;
2899
+ this.navigateFrame(element, url);
2900
+ }
2901
+ shouldInterceptFormSubmission(element, submitter) {
2902
+ return this.shouldInterceptNavigation(element, submitter);
2903
+ }
2904
+ formSubmissionIntercepted(element, submitter) {
2905
+ if (this.formSubmission) {
2906
+ this.formSubmission.stop();
2907
+ }
2908
+ this.reloadable = false;
2909
+ this.formSubmission = new FormSubmission(this, element, submitter);
2910
+ if (this.formSubmission.fetchRequest.isIdempotent) {
2911
+ this.navigateFrame(element, this.formSubmission.fetchRequest.url.href, submitter);
2912
+ } else {
2913
+ const {fetchRequest: fetchRequest} = this.formSubmission;
2914
+ this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2915
+ this.formSubmission.start();
2916
+ }
2917
+ }
2918
+ prepareHeadersForRequest(headers, request) {
2919
+ headers["Turbo-Frame"] = this.id;
2920
+ }
2921
+ requestStarted(request) {
2922
+ this.element.setAttribute("busy", "");
2923
+ }
2924
+ requestPreventedHandlingResponse(request, response) {
2925
+ this.resolveVisitPromise();
2926
+ }
2927
+ async requestSucceededWithResponse(request, response) {
2928
+ await this.loadResponse(response);
2929
+ this.resolveVisitPromise();
2930
+ }
2931
+ requestFailedWithResponse(request, response) {
2932
+ console.error(response);
2933
+ this.resolveVisitPromise();
2934
+ }
2935
+ requestErrored(request, error) {
2936
+ console.error(error);
2937
+ this.resolveVisitPromise();
2938
+ }
2939
+ requestFinished(request) {
2940
+ this.element.removeAttribute("busy");
2941
+ }
2942
+ formSubmissionStarted(formSubmission) {
2943
+ const frame = this.findFrameElement(formSubmission.formElement);
2944
+ frame.setAttribute("busy", "");
2945
+ }
2946
+ formSubmissionSucceededWithResponse(formSubmission, response) {
2947
+ const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
2948
+ frame.delegate.loadResponse(response);
2949
+ }
2950
+ formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
2951
+ this.element.delegate.loadResponse(fetchResponse);
2952
+ }
2953
+ formSubmissionErrored(formSubmission, error) {
2954
+ console.error(error);
2955
+ }
2956
+ formSubmissionFinished(formSubmission) {
2957
+ const frame = this.findFrameElement(formSubmission.formElement);
2958
+ frame.removeAttribute("busy");
2959
+ }
2960
+ allowsImmediateRender(snapshot, resume) {
2961
+ return true;
2962
+ }
2963
+ viewRenderedSnapshot(snapshot, isPreview) {}
2964
+ viewInvalidated() {}
2965
+ async visit(url) {
2966
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url), undefined, this.element);
2967
+ return new Promise((resolve => {
2968
+ this.resolveVisitPromise = () => {
2969
+ this.resolveVisitPromise = () => {};
2970
+ resolve();
2971
+ };
2972
+ request.perform();
2973
+ }));
2974
+ }
2975
+ navigateFrame(element, url, submitter) {
2976
+ const frame = this.findFrameElement(element, submitter);
2977
+ frame.setAttribute("reloadable", "");
2978
+ frame.src = url;
2979
+ }
2980
+ findFrameElement(element, submitter) {
2981
+ var _a;
2982
+ const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
2983
+ return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
2984
+ }
2985
+ async extractForeignFrameElement(container) {
2986
+ let element;
2987
+ const id = CSS.escape(this.id);
2988
+ try {
2989
+ if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
2990
+ return element;
2991
+ }
2992
+ if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
2993
+ await element.loaded;
2994
+ return await this.extractForeignFrameElement(element);
2995
+ }
2996
+ console.error(`Response has no matching <turbo-frame id="${id}"> element`);
2997
+ } catch (error) {
2998
+ console.error(error);
2999
+ }
3000
+ return new FrameElement;
3001
+ }
3002
+ shouldInterceptNavigation(element, submitter) {
3003
+ const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3004
+ if (!this.enabled || id == "_top") {
3005
+ return false;
3006
+ }
3007
+ if (id) {
3008
+ const frameElement = getFrameElementById(id);
3009
+ if (frameElement) {
3010
+ return !frameElement.disabled;
3011
+ }
3012
+ }
3013
+ if (!session.elementDriveEnabled(element)) {
3014
+ return false;
3015
+ }
3016
+ if (submitter && !session.elementDriveEnabled(submitter)) {
3017
+ return false;
3018
+ }
3019
+ return true;
3020
+ }
3021
+ get id() {
3022
+ return this.element.id;
3023
+ }
3024
+ get enabled() {
3025
+ return !this.element.disabled;
2659
3026
  }
2660
- willFollowLinkToLocation(link, location) {
2661
- return this.elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
3027
+ get sourceURL() {
3028
+ if (this.element.src) {
3029
+ return this.element.src;
3030
+ }
2662
3031
  }
2663
- followedLinkToLocation(link, location) {
2664
- const action = this.getActionForLink(link);
2665
- this.visit(location.href, {
2666
- action: action
2667
- });
3032
+ get reloadable() {
3033
+ const frame = this.findFrameElement(this.element);
3034
+ return frame.hasAttribute("reloadable");
2668
3035
  }
2669
- allowsVisitingLocation(location) {
2670
- return this.applicationAllowsVisitingLocation(location);
3036
+ set reloadable(value) {
3037
+ const frame = this.findFrameElement(this.element);
3038
+ if (value) {
3039
+ frame.setAttribute("reloadable", "");
3040
+ } else {
3041
+ frame.removeAttribute("reloadable");
3042
+ }
2671
3043
  }
2672
- visitProposedToLocation(location, options) {
2673
- extendURLWithDeprecatedProperties(location);
2674
- this.adapter.visitProposedToLocation(location, options);
3044
+ set sourceURL(sourceURL) {
3045
+ this.settingSourceURL = true;
3046
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3047
+ this.currentURL = this.element.src;
3048
+ this.settingSourceURL = false;
2675
3049
  }
2676
- visitStarted(visit) {
2677
- extendURLWithDeprecatedProperties(visit.location);
2678
- this.notifyApplicationAfterVisitingLocation(visit.location);
3050
+ get loadingStyle() {
3051
+ return this.element.loading;
2679
3052
  }
2680
- visitCompleted(visit) {
2681
- this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3053
+ get isLoading() {
3054
+ return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
2682
3055
  }
2683
- willSubmitForm(form, submitter) {
2684
- return this.elementIsNavigable(form) && this.elementIsNavigable(submitter);
3056
+ get isActive() {
3057
+ return this.element.isActive && this.connected;
2685
3058
  }
2686
- formSubmitted(form, submitter) {
2687
- this.navigator.submitForm(form, submitter);
3059
+ }
3060
+
3061
+ function getFrameElementById(id) {
3062
+ if (id != null) {
3063
+ const element = document.getElementById(id);
3064
+ if (element instanceof FrameElement) {
3065
+ return element;
3066
+ }
2688
3067
  }
2689
- pageBecameInteractive() {
2690
- this.view.lastRenderedLocation = this.location;
2691
- this.notifyApplicationAfterPageLoad();
3068
+ }
3069
+
3070
+ function activateElement(element, currentURL) {
3071
+ if (element) {
3072
+ const src = element.getAttribute("src");
3073
+ if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
3074
+ throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
3075
+ }
3076
+ if (element.ownerDocument !== document) {
3077
+ element = document.importNode(element, true);
3078
+ }
3079
+ if (element instanceof FrameElement) {
3080
+ element.connectedCallback();
3081
+ return element;
3082
+ }
2692
3083
  }
2693
- pageLoaded() {
2694
- this.history.assumeControlOfScrollRestoration();
3084
+ }
3085
+
3086
+ const StreamActions = {
3087
+ after() {
3088
+ this.targetElements.forEach((e => {
3089
+ var _a;
3090
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3091
+ }));
3092
+ },
3093
+ append() {
3094
+ this.removeDuplicateTargetChildren();
3095
+ this.targetElements.forEach((e => e.append(this.templateContent)));
3096
+ },
3097
+ before() {
3098
+ this.targetElements.forEach((e => {
3099
+ var _a;
3100
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3101
+ }));
3102
+ },
3103
+ prepend() {
3104
+ this.removeDuplicateTargetChildren();
3105
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
3106
+ },
3107
+ remove() {
3108
+ this.targetElements.forEach((e => e.remove()));
3109
+ },
3110
+ replace() {
3111
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3112
+ },
3113
+ update() {
3114
+ this.targetElements.forEach((e => {
3115
+ e.innerHTML = "";
3116
+ e.append(this.templateContent);
3117
+ }));
2695
3118
  }
2696
- pageWillUnload() {
2697
- this.history.relinquishControlOfScrollRestoration();
3119
+ };
3120
+
3121
+ class StreamElement extends HTMLElement {
3122
+ async connectedCallback() {
3123
+ try {
3124
+ await this.render();
3125
+ } catch (error) {
3126
+ console.error(error);
3127
+ } finally {
3128
+ this.disconnect();
3129
+ }
2698
3130
  }
2699
- receivedMessageFromStream(message) {
2700
- this.renderStreamMessage(message);
3131
+ async render() {
3132
+ var _a;
3133
+ return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
3134
+ if (this.dispatchEvent(this.beforeRenderEvent)) {
3135
+ await nextAnimationFrame();
3136
+ this.performAction();
3137
+ }
3138
+ })();
2701
3139
  }
2702
- viewWillCacheSnapshot() {
2703
- this.notifyApplicationBeforeCachingSnapshot();
3140
+ disconnect() {
3141
+ try {
3142
+ this.remove();
3143
+ } catch (_a) {}
2704
3144
  }
2705
- viewWillRenderSnapshot({element: element}, isPreview) {
2706
- this.notifyApplicationBeforeRender(element);
3145
+ removeDuplicateTargetChildren() {
3146
+ this.duplicateChildren.forEach((c => c.remove()));
2707
3147
  }
2708
- viewRenderedSnapshot(snapshot, isPreview) {
2709
- this.view.lastRenderedLocation = this.history.location;
2710
- this.notifyApplicationAfterRender();
3148
+ get duplicateChildren() {
3149
+ var _a;
3150
+ const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3151
+ const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
3152
+ return existingChildren.filter((c => newChildrenIds.includes(c.id)));
2711
3153
  }
2712
- viewInvalidated() {
2713
- this.adapter.pageInvalidated();
3154
+ get performAction() {
3155
+ if (this.action) {
3156
+ const actionFunction = StreamActions[this.action];
3157
+ if (actionFunction) {
3158
+ return actionFunction;
3159
+ }
3160
+ this.raise("unknown action");
3161
+ }
3162
+ this.raise("action attribute is missing");
2714
3163
  }
2715
- applicationAllowsFollowingLinkToLocation(link, location) {
2716
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2717
- return !event.defaultPrevented;
3164
+ get targetElements() {
3165
+ if (this.target) {
3166
+ return this.targetElementsById;
3167
+ } else if (this.targets) {
3168
+ return this.targetElementsByQuery;
3169
+ } else {
3170
+ this.raise("target or targets attribute is missing");
3171
+ }
2718
3172
  }
2719
- applicationAllowsVisitingLocation(location) {
2720
- const event = this.notifyApplicationBeforeVisitingLocation(location);
2721
- return !event.defaultPrevented;
3173
+ get templateContent() {
3174
+ return this.templateElement.content.cloneNode(true);
2722
3175
  }
2723
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2724
- return dispatch("turbo:click", {
2725
- target: link,
2726
- detail: {
2727
- url: location.href
2728
- },
2729
- cancelable: true
2730
- });
3176
+ get templateElement() {
3177
+ if (this.firstElementChild instanceof HTMLTemplateElement) {
3178
+ return this.firstElementChild;
3179
+ }
3180
+ this.raise("first child element must be a <template> element");
2731
3181
  }
2732
- notifyApplicationBeforeVisitingLocation(location) {
2733
- return dispatch("turbo:before-visit", {
2734
- detail: {
2735
- url: location.href
2736
- },
2737
- cancelable: true
2738
- });
3182
+ get action() {
3183
+ return this.getAttribute("action");
2739
3184
  }
2740
- notifyApplicationAfterVisitingLocation(location) {
2741
- return dispatch("turbo:visit", {
2742
- detail: {
2743
- url: location.href
2744
- }
2745
- });
3185
+ get target() {
3186
+ return this.getAttribute("target");
2746
3187
  }
2747
- notifyApplicationBeforeCachingSnapshot() {
2748
- return dispatch("turbo:before-cache");
3188
+ get targets() {
3189
+ return this.getAttribute("targets");
2749
3190
  }
2750
- notifyApplicationBeforeRender(newBody) {
2751
- return dispatch("turbo:before-render", {
2752
- detail: {
2753
- newBody: newBody
2754
- }
2755
- });
3191
+ raise(message) {
3192
+ throw new Error(`${this.description}: ${message}`);
2756
3193
  }
2757
- notifyApplicationAfterRender() {
2758
- return dispatch("turbo:render");
3194
+ get description() {
3195
+ var _a, _b;
3196
+ return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
2759
3197
  }
2760
- notifyApplicationAfterPageLoad(timing = {}) {
2761
- return dispatch("turbo:load", {
2762
- detail: {
2763
- url: this.location.href,
2764
- timing: timing
2765
- }
3198
+ get beforeRenderEvent() {
3199
+ return new CustomEvent("turbo:before-stream-render", {
3200
+ bubbles: true,
3201
+ cancelable: true
2766
3202
  });
2767
3203
  }
2768
- getActionForLink(link) {
2769
- const action = link.getAttribute("data-turbo-action");
2770
- return isAction(action) ? action : "advance";
2771
- }
2772
- elementIsNavigable(element) {
2773
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2774
- if (container) {
2775
- return container.getAttribute("data-turbo") != "false";
3204
+ get targetElementsById() {
3205
+ var _a;
3206
+ const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
3207
+ if (element !== null) {
3208
+ return [ element ];
2776
3209
  } else {
2777
- return true;
3210
+ return [];
2778
3211
  }
2779
3212
  }
2780
- locationIsVisitable(location) {
2781
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2782
- }
2783
- get snapshot() {
2784
- return this.view.snapshot;
2785
- }
2786
- }
2787
-
2788
- function extendURLWithDeprecatedProperties(url) {
2789
- Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2790
- }
2791
-
2792
- const deprecatedLocationPropertyDescriptors = {
2793
- absoluteURL: {
2794
- get() {
2795
- return this.toString();
3213
+ get targetElementsByQuery() {
3214
+ var _a;
3215
+ const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
3216
+ if (elements.length !== 0) {
3217
+ return Array.prototype.slice.call(elements);
3218
+ } else {
3219
+ return [];
2796
3220
  }
2797
3221
  }
2798
- };
2799
-
2800
- const session = new Session;
2801
-
2802
- const {navigator: navigator} = session;
2803
-
2804
- function start() {
2805
- session.start();
2806
3222
  }
2807
3223
 
2808
- function registerAdapter(adapter) {
2809
- session.registerAdapter(adapter);
2810
- }
3224
+ FrameElement.delegateConstructor = FrameController;
2811
3225
 
2812
- function visit(location, options) {
2813
- session.visit(location, options);
2814
- }
3226
+ customElements.define("turbo-frame", FrameElement);
2815
3227
 
2816
- function connectStreamSource(source) {
2817
- session.connectStreamSource(source);
2818
- }
3228
+ customElements.define("turbo-stream", StreamElement);
2819
3229
 
2820
- function disconnectStreamSource(source) {
2821
- session.disconnectStreamSource(source);
2822
- }
3230
+ (() => {
3231
+ let element = document.currentScript;
3232
+ if (!element) return;
3233
+ if (element.hasAttribute("data-turbo-suppress-warning")) return;
3234
+ while (element = element.parentElement) {
3235
+ if (element == document.body) {
3236
+ return console.warn(unindent`
3237
+ You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
2823
3238
 
2824
- function renderStreamMessage(message) {
2825
- session.renderStreamMessage(message);
2826
- }
3239
+ Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
2827
3240
 
2828
- function clearCache() {
2829
- session.clearCache();
2830
- }
3241
+ For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements
2831
3242
 
2832
- function setProgressBarDelay(delay) {
2833
- session.setProgressBarDelay(delay);
2834
- }
3243
+ ——
3244
+ Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3245
+ `, element.outerHTML);
3246
+ }
3247
+ }
3248
+ })();
3249
+
3250
+ window.Turbo = Turbo;
2835
3251
 
2836
3252
  start();
2837
3253
 
2838
3254
  var turbo_es2017Esm = Object.freeze({
2839
3255
  __proto__: null,
3256
+ PageRenderer: PageRenderer,
3257
+ PageSnapshot: PageSnapshot,
2840
3258
  clearCache: clearCache,
2841
3259
  connectStreamSource: connectStreamSource,
2842
3260
  disconnectStreamSource: disconnectStreamSource,
2843
3261
  navigator: navigator,
2844
3262
  registerAdapter: registerAdapter,
2845
3263
  renderStreamMessage: renderStreamMessage,
3264
+ session: session,
2846
3265
  setProgressBarDelay: setProgressBarDelay,
2847
3266
  start: start,
2848
3267
  visit: visit
@@ -2851,17 +3270,20 @@ var turbo_es2017Esm = Object.freeze({
2851
3270
  let consumer;
2852
3271
 
2853
3272
  async function getConsumer() {
2854
- if (consumer) return consumer;
2855
- const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
2856
- return index;
2857
- }));
2858
- return setConsumer(createConsumer());
3273
+ return consumer || setConsumer(createConsumer().then(setConsumer));
2859
3274
  }
2860
3275
 
2861
3276
  function setConsumer(newConsumer) {
2862
3277
  return consumer = newConsumer;
2863
3278
  }
2864
3279
 
3280
+ async function createConsumer() {
3281
+ const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
3282
+ return index;
3283
+ }));
3284
+ return createConsumer();
3285
+ }
3286
+
2865
3287
  async function subscribeTo(channel, mixin) {
2866
3288
  const {subscriptions: subscriptions} = await getConsumer();
2867
3289
  return subscriptions.create(channel, mixin);
@@ -2871,6 +3293,7 @@ var cable = Object.freeze({
2871
3293
  __proto__: null,
2872
3294
  getConsumer: getConsumer,
2873
3295
  setConsumer: setConsumer,
3296
+ createConsumer: createConsumer,
2874
3297
  subscribeTo: subscribeTo
2875
3298
  });
2876
3299
 
@@ -3334,7 +3757,7 @@ function createWebSocketURL(url) {
3334
3757
  }
3335
3758
  }
3336
3759
 
3337
- function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
3760
+ function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
3338
3761
  return new Consumer(url);
3339
3762
  }
3340
3763
 
@@ -3356,7 +3779,7 @@ var index = Object.freeze({
3356
3779
  adapters: adapters,
3357
3780
  createWebSocketURL: createWebSocketURL,
3358
3781
  logger: logger,
3359
- createConsumer: createConsumer,
3782
+ createConsumer: createConsumer$1,
3360
3783
  getConfig: getConfig
3361
3784
  });
3362
3785