turbo-rails 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/app/assets/javascripts/turbo.js +243 -129
  4. data/app/helpers/turbo/streams_helper.rb +2 -2
  5. data/lib/install/turbo_with_webpacker.rb +1 -2
  6. data/lib/turbo/engine.rb +1 -1
  7. data/lib/turbo/version.rb +1 -1
  8. metadata +7 -173
  9. data/.github/workflows/ci.yml +0 -30
  10. data/.gitignore +0 -2
  11. data/Gemfile +0 -6
  12. data/Gemfile.lock +0 -147
  13. data/package.json +0 -47
  14. data/rollup.config.js +0 -23
  15. data/test/drive/drive_helper_test.rb +0 -8
  16. data/test/dummy/.babelrc +0 -18
  17. data/test/dummy/.gitignore +0 -3
  18. data/test/dummy/.postcssrc.yml +0 -3
  19. data/test/dummy/Rakefile +0 -6
  20. data/test/dummy/app/assets/config/manifest.js +0 -2
  21. data/test/dummy/app/assets/images/.keep +0 -0
  22. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  23. data/test/dummy/app/assets/stylesheets/scaffold.css +0 -80
  24. data/test/dummy/app/channels/application_cable/channel.rb +0 -4
  25. data/test/dummy/app/channels/application_cable/connection.rb +0 -4
  26. data/test/dummy/app/controllers/application_controller.rb +0 -2
  27. data/test/dummy/app/controllers/concerns/.keep +0 -0
  28. data/test/dummy/app/controllers/messages_controller.rb +0 -12
  29. data/test/dummy/app/controllers/trays_controller.rb +0 -17
  30. data/test/dummy/app/helpers/application_helper.rb +0 -2
  31. data/test/dummy/app/javascript/packs/application.js +0 -0
  32. data/test/dummy/app/jobs/application_job.rb +0 -2
  33. data/test/dummy/app/mailboxes/application_mailbox.rb +0 -2
  34. data/test/dummy/app/mailboxes/messages_mailbox.rb +0 -4
  35. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  36. data/test/dummy/app/models/application_record.rb +0 -3
  37. data/test/dummy/app/models/concerns/.keep +0 -0
  38. data/test/dummy/app/models/message.rb +0 -29
  39. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  40. data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
  41. data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
  42. data/test/dummy/app/views/messages/_message.html.erb +0 -1
  43. data/test/dummy/app/views/messages/_message.turbo_stream.erb +0 -1
  44. data/test/dummy/app/views/messages/show.turbo_stream.erb +0 -9
  45. data/test/dummy/app/views/trays/index.html.erb +0 -3
  46. data/test/dummy/app/views/trays/show.html.erb +0 -3
  47. data/test/dummy/bin/bundle +0 -3
  48. data/test/dummy/bin/rails +0 -4
  49. data/test/dummy/bin/rake +0 -4
  50. data/test/dummy/bin/setup +0 -36
  51. data/test/dummy/bin/update +0 -31
  52. data/test/dummy/bin/yarn +0 -11
  53. data/test/dummy/config.ru +0 -5
  54. data/test/dummy/config/application.rb +0 -22
  55. data/test/dummy/config/boot.rb +0 -5
  56. data/test/dummy/config/cable.yml +0 -10
  57. data/test/dummy/config/environment.rb +0 -5
  58. data/test/dummy/config/environments/development.rb +0 -34
  59. data/test/dummy/config/environments/production.rb +0 -96
  60. data/test/dummy/config/environments/test.rb +0 -38
  61. data/test/dummy/config/initializers/application_controller_renderer.rb +0 -8
  62. data/test/dummy/config/initializers/assets.rb +0 -14
  63. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  64. data/test/dummy/config/initializers/content_security_policy.rb +0 -22
  65. data/test/dummy/config/initializers/cookies_serializer.rb +0 -5
  66. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  67. data/test/dummy/config/initializers/inflections.rb +0 -16
  68. data/test/dummy/config/initializers/mime_types.rb +0 -4
  69. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  70. data/test/dummy/config/locales/en.yml +0 -33
  71. data/test/dummy/config/puma.rb +0 -34
  72. data/test/dummy/config/routes.rb +0 -4
  73. data/test/dummy/config/spring.rb +0 -6
  74. data/test/dummy/config/webpack/development.js +0 -3
  75. data/test/dummy/config/webpack/environment.js +0 -3
  76. data/test/dummy/config/webpack/production.js +0 -3
  77. data/test/dummy/config/webpack/test.js +0 -3
  78. data/test/dummy/config/webpacker.yml +0 -65
  79. data/test/dummy/lib/assets/.keep +0 -0
  80. data/test/dummy/log/.keep +0 -0
  81. data/test/dummy/public/404.html +0 -67
  82. data/test/dummy/public/422.html +0 -67
  83. data/test/dummy/public/500.html +0 -66
  84. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  85. data/test/dummy/public/apple-touch-icon.png +0 -0
  86. data/test/dummy/public/favicon.ico +0 -0
  87. data/test/frames/frame_request_controller_test.rb +0 -21
  88. data/test/frames/frames_helper_test.rb +0 -21
  89. data/test/native/navigation_controller_test.rb +0 -42
  90. data/test/streams/broadcastable_test.rb +0 -80
  91. data/test/streams/streams_channel_test.rb +0 -111
  92. data/test/streams/streams_controller_test.rb +0 -29
  93. data/test/turbo_test.rb +0 -10
  94. data/turbo-rails.gemspec +0 -17
  95. data/yarn.lock +0 -283
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d7e1db921a74088b68618665ff1df3ae7e3216fff065c2236c4bc8fd3c94812
4
- data.tar.gz: e3b2ab5358a0c5b333c32401650f3c78325342e9e0e3eac47c1da4ab05621b13
3
+ metadata.gz: 5ecf40714a59522c484900685826a53c6c5396d7f4cbe02583278e657db5d999
4
+ data.tar.gz: 418e450bb22bb03b92605b47f2181ba9ebdceae4e9cf4337c124cf4c6598e5fc
5
5
  SHA512:
6
- metadata.gz: c28d47c9a443718f9737b040b30d91964b1a02fb6f68fd3c9913b13ba7467ecbdb8e9a4bef4df711ad7aa99b8eb1d5a1005cc805b535a0fd21c41ff2f6ac279b
7
- data.tar.gz: a20b70fa8892752160064a79ede46b553a1d04f84e91200d3f62092e973aa49c2627f6cc2e866b43c2452c5f8143ca5bc103c1807c3cf0f80499d2ae12be14b0
6
+ metadata.gz: de89fcedf66a77fc03c984ce128cf088194c13969417e978e95755b94cb8a04b852269ff39196abf2ca7d2da91738cbf4d5f06f958d150eebc2fe53c38865664
7
+ data.tar.gz: 0223bc0f45e495659673f7c5bdbf03cefa8645f8a5781e372753a034007524a06828d5db37dd0eeaad99a9b6036598e983abdf453f168513f4d2237595bb009d
data/README.md CHANGED
@@ -40,8 +40,14 @@ The JavaScript for Turbo can either be run through the asset pipeline, which is
40
40
 
41
41
  Running `turbo:install` will install through NPM if Webpacker is installed in the application. Otherwise the asset pipeline version is used.
42
42
 
43
- If you're using Webpack and need to use either the cable consumer or the Turbo instance, you can import [`Turbo`](https://turbo.hotwire.dev/reference/drive) and/or [`cable`](https://github.com/hotwired/turbo-rails/blob/87542edecc4008c46249e5d8ede79b3eda62a5e2/app/javascript/turbo/cable.js) (`import { Turbo, cable } from "@hotwired/turbo-rails"`), but ensure that your application actually *uses* the members it `import`s when using this style (see [turbo-rails#48](https://github.com/hotwired/turbo-rails/issues/48)).
43
+ If you're using Webpack and need to use either the cable consumer or the Turbo instance, you can import [`Turbo`](https://turbo.hotwire.dev/reference/drive) and/or [`cable`](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable.js) (`import { Turbo, cable } from "@hotwired/turbo-rails"`), but ensure that your application actually *uses* the members it `import`s when using this style (see [turbo-rails#48](https://github.com/hotwired/turbo-rails/issues/48)).
44
44
 
45
+ If you're using a [native adapter](https://turbo.hotwire.dev/handbook/native), you'll need to assign `window.Turbo`, even if it's not used for anything else:
46
+
47
+ ```js
48
+ import { Turbo } from "@hotwired/turbo-rails"
49
+ window.Turbo = Turbo
50
+ ```
45
51
 
46
52
  ## Usage
47
53
 
@@ -41,6 +41,97 @@ function clickCaptured(event) {
41
41
  });
42
42
  })();
43
43
 
44
+ var FrameLoadingStyle;
45
+
46
+ (function(FrameLoadingStyle) {
47
+ FrameLoadingStyle["eager"] = "eager";
48
+ FrameLoadingStyle["lazy"] = "lazy";
49
+ })(FrameLoadingStyle || (FrameLoadingStyle = {}));
50
+
51
+ class FrameElement extends HTMLElement {
52
+ constructor() {
53
+ super();
54
+ this.loaded = Promise.resolve();
55
+ this.delegate = new FrameElement.delegateConstructor(this);
56
+ }
57
+ static get observedAttributes() {
58
+ return [ "loading", "src" ];
59
+ }
60
+ connectedCallback() {
61
+ this.delegate.connect();
62
+ }
63
+ disconnectedCallback() {
64
+ this.delegate.disconnect();
65
+ }
66
+ attributeChangedCallback(name) {
67
+ if (name == "loading") {
68
+ this.delegate.loadingStyleChanged();
69
+ } else if (name == "src") {
70
+ this.delegate.sourceURLChanged();
71
+ }
72
+ }
73
+ get src() {
74
+ return this.getAttribute("src");
75
+ }
76
+ set src(value) {
77
+ if (value) {
78
+ this.setAttribute("src", value);
79
+ } else {
80
+ this.removeAttribute("src");
81
+ }
82
+ }
83
+ get loading() {
84
+ return frameLoadingStyleFromString(this.getAttribute("loading") || "");
85
+ }
86
+ set loading(value) {
87
+ if (value) {
88
+ this.setAttribute("loading", value);
89
+ } else {
90
+ this.removeAttribute("loading");
91
+ }
92
+ }
93
+ get disabled() {
94
+ return this.hasAttribute("disabled");
95
+ }
96
+ set disabled(value) {
97
+ if (value) {
98
+ this.setAttribute("disabled", "");
99
+ } else {
100
+ this.removeAttribute("disabled");
101
+ }
102
+ }
103
+ get autoscroll() {
104
+ return this.hasAttribute("autoscroll");
105
+ }
106
+ set autoscroll(value) {
107
+ if (value) {
108
+ this.setAttribute("autoscroll", "");
109
+ } else {
110
+ this.removeAttribute("autoscroll");
111
+ }
112
+ }
113
+ get complete() {
114
+ return !this.delegate.isLoading;
115
+ }
116
+ get isActive() {
117
+ return this.ownerDocument === document && !this.isPreview;
118
+ }
119
+ get isPreview() {
120
+ var _a, _b;
121
+ return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
122
+ }
123
+ }
124
+
125
+ function frameLoadingStyleFromString(style) {
126
+ switch (style.toLowerCase()) {
127
+ case "lazy":
128
+ return FrameLoadingStyle.lazy;
129
+
130
+ default:
131
+ return FrameLoadingStyle.eager;
132
+ }
133
+ }
134
+
44
135
  class Location {
45
136
  constructor(url) {
46
137
  const linkWithAnchor = document.createElement("a");
@@ -142,7 +233,7 @@ class FetchResponse {
142
233
  return Location.wrap(this.response.url);
143
234
  }
144
235
  get isHTML() {
145
- return this.contentType && this.contentType.match(/^text\/html|^application\/xhtml\+xml/);
236
+ return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
146
237
  }
147
238
  get statusCode() {
148
239
  return this.response.status;
@@ -336,27 +427,30 @@ class FetchRequest {
336
427
  }
337
428
  }
338
429
 
339
- class FormInterceptor {
430
+ class AppearanceObserver {
340
431
  constructor(delegate, element) {
341
- this.submitBubbled = event => {
342
- if (event.target instanceof HTMLFormElement) {
343
- const form = event.target;
344
- const submitter = event.submitter || undefined;
345
- if (this.delegate.shouldInterceptFormSubmission(form, submitter)) {
346
- event.preventDefault();
347
- event.stopImmediatePropagation();
348
- this.delegate.formSubmissionIntercepted(form, submitter);
349
- }
432
+ this.started = false;
433
+ this.intersect = entries => {
434
+ const lastEntry = entries.slice(-1)[0];
435
+ if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
436
+ this.delegate.elementAppearedInViewport(this.element);
350
437
  }
351
438
  };
352
439
  this.delegate = delegate;
353
440
  this.element = element;
441
+ this.intersectionObserver = new IntersectionObserver(this.intersect);
354
442
  }
355
443
  start() {
356
- this.element.addEventListener("submit", this.submitBubbled);
444
+ if (!this.started) {
445
+ this.started = true;
446
+ this.intersectionObserver.observe(this.element);
447
+ }
357
448
  }
358
449
  stop() {
359
- this.element.removeEventListener("submit", this.submitBubbled);
450
+ if (this.started) {
451
+ this.started = false;
452
+ this.intersectionObserver.unobserve(this.element);
453
+ }
360
454
  }
361
455
  }
362
456
 
@@ -437,7 +531,7 @@ class FormSubmission {
437
531
  requestSucceededWithResponse(request, response) {
438
532
  if (response.clientError || response.serverError) {
439
533
  this.delegate.formSubmissionFailedWithResponse(this, response);
440
- } else if (this.requestMustRedirect(request) && !response.redirected) {
534
+ } else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
441
535
  const error = new Error("Form responses must redirect to another location");
442
536
  this.delegate.formSubmissionErrored(this, error);
443
537
  } else {
@@ -504,6 +598,34 @@ function getMetaContent(name) {
504
598
  return element && element.content;
505
599
  }
506
600
 
601
+ function responseSucceededWithoutRedirect(response) {
602
+ return response.statusCode == 200 && !response.redirected;
603
+ }
604
+
605
+ class FormInterceptor {
606
+ constructor(delegate, element) {
607
+ this.submitBubbled = event => {
608
+ if (event.target instanceof HTMLFormElement) {
609
+ const form = event.target;
610
+ const submitter = event.submitter || undefined;
611
+ if (this.delegate.shouldInterceptFormSubmission(form, submitter)) {
612
+ event.preventDefault();
613
+ event.stopImmediatePropagation();
614
+ this.delegate.formSubmissionIntercepted(form, submitter);
615
+ }
616
+ }
617
+ };
618
+ this.delegate = delegate;
619
+ this.element = element;
620
+ }
621
+ start() {
622
+ this.element.addEventListener("submit", this.submitBubbled);
623
+ }
624
+ stop() {
625
+ this.element.removeEventListener("submit", this.submitBubbled);
626
+ }
627
+ }
628
+
507
629
  class LinkInterceptor {
508
630
  constructor(delegate, element) {
509
631
  this.clickBubbled = event => {
@@ -549,17 +671,61 @@ class FrameController {
549
671
  constructor(element) {
550
672
  this.resolveVisitPromise = () => {};
551
673
  this.element = element;
674
+ this.appearanceObserver = new AppearanceObserver(this, this.element);
552
675
  this.linkInterceptor = new LinkInterceptor(this, this.element);
553
676
  this.formInterceptor = new FormInterceptor(this, this.element);
554
677
  }
555
678
  connect() {
679
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
680
+ this.appearanceObserver.start();
681
+ }
556
682
  this.linkInterceptor.start();
557
683
  this.formInterceptor.start();
558
684
  }
559
685
  disconnect() {
686
+ this.appearanceObserver.stop();
560
687
  this.linkInterceptor.stop();
561
688
  this.formInterceptor.stop();
562
689
  }
690
+ sourceURLChanged() {
691
+ if (this.loadingStyle == FrameLoadingStyle.eager) {
692
+ this.loadSourceURL();
693
+ }
694
+ }
695
+ loadingStyleChanged() {
696
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
697
+ this.appearanceObserver.start();
698
+ } else {
699
+ this.appearanceObserver.stop();
700
+ this.loadSourceURL();
701
+ }
702
+ }
703
+ async loadSourceURL() {
704
+ if (this.isActive && this.sourceURL && this.sourceURL != this.loadingURL) {
705
+ try {
706
+ this.loadingURL = this.sourceURL;
707
+ this.element.loaded = this.visit(this.sourceURL);
708
+ this.appearanceObserver.stop();
709
+ await this.element.loaded;
710
+ } finally {
711
+ delete this.loadingURL;
712
+ }
713
+ }
714
+ }
715
+ async loadResponse(response) {
716
+ const fragment = fragmentFromHTML(await response.responseHTML);
717
+ if (fragment) {
718
+ const element = await this.extractForeignFrameElement(fragment);
719
+ await nextAnimationFrame();
720
+ this.loadFrameElement(element);
721
+ this.scrollFrameIntoView(element);
722
+ await nextAnimationFrame();
723
+ this.focusFirstAutofocusableElement();
724
+ }
725
+ }
726
+ elementAppearedInViewport(element) {
727
+ this.loadSourceURL();
728
+ }
563
729
  shouldInterceptLinkClick(element, url) {
564
730
  return this.shouldInterceptNavigation(element);
565
731
  }
@@ -580,17 +746,6 @@ class FrameController {
580
746
  this.formSubmission.start();
581
747
  }
582
748
  }
583
- async visit(url) {
584
- const location = Location.wrap(url);
585
- const request = new FetchRequest(this, FetchMethod.get, location);
586
- return new Promise((resolve => {
587
- this.resolveVisitPromise = () => {
588
- this.resolveVisitPromise = () => {};
589
- resolve();
590
- };
591
- request.perform();
592
- }));
593
- }
594
749
  additionalHeadersForRequest(request) {
595
750
  return {
596
751
  "Turbo-Frame": this.id
@@ -620,13 +775,24 @@ class FrameController {
620
775
  formSubmissionStarted(formSubmission) {}
621
776
  formSubmissionSucceededWithResponse(formSubmission, response) {
622
777
  const frame = this.findFrameElement(formSubmission.formElement);
623
- frame.controller.loadResponse(response);
778
+ frame.delegate.loadResponse(response);
624
779
  }
625
780
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
626
- this.element.controller.loadResponse(fetchResponse);
781
+ this.element.delegate.loadResponse(fetchResponse);
627
782
  }
628
783
  formSubmissionErrored(formSubmission, error) {}
629
784
  formSubmissionFinished(formSubmission) {}
785
+ async visit(url) {
786
+ const location = Location.wrap(url);
787
+ const request = new FetchRequest(this, FetchMethod.get, location);
788
+ return new Promise((resolve => {
789
+ this.resolveVisitPromise = () => {
790
+ this.resolveVisitPromise = () => {};
791
+ resolve();
792
+ };
793
+ request.perform();
794
+ }));
795
+ }
630
796
  navigateFrame(element, url) {
631
797
  const frame = this.findFrameElement(element);
632
798
  frame.src = url;
@@ -636,17 +802,6 @@ class FrameController {
636
802
  const id = element.getAttribute("data-turbo-frame");
637
803
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
638
804
  }
639
- async loadResponse(response) {
640
- const fragment = fragmentFromHTML(await response.responseHTML);
641
- const element = await this.extractForeignFrameElement(fragment);
642
- if (element) {
643
- await nextAnimationFrame();
644
- this.loadFrameElement(element);
645
- this.scrollFrameIntoView(element);
646
- await nextAnimationFrame();
647
- this.focusFirstAutofocusableElement();
648
- }
649
- }
650
805
  async extractForeignFrameElement(container) {
651
806
  let element;
652
807
  const id = CSS.escape(this.id);
@@ -657,6 +812,8 @@ class FrameController {
657
812
  await element.loaded;
658
813
  return await this.extractForeignFrameElement(element);
659
814
  }
815
+ console.error(`Response has no matching <turbo-frame id="${id}"> element`);
816
+ return new FrameElement;
660
817
  }
661
818
  loadFrameElement(frameElement) {
662
819
  var _a;
@@ -713,6 +870,18 @@ class FrameController {
713
870
  get enabled() {
714
871
  return !this.element.disabled;
715
872
  }
873
+ get sourceURL() {
874
+ return this.element.src;
875
+ }
876
+ get loadingStyle() {
877
+ return this.element.loading;
878
+ }
879
+ get isLoading() {
880
+ return this.formSubmission !== undefined || this.loadingURL !== undefined;
881
+ }
882
+ get isActive() {
883
+ return this.element.isActive;
884
+ }
716
885
  }
717
886
 
718
887
  function getFrameElementById(id) {
@@ -732,9 +901,11 @@ function readScrollLogicalPosition(value, defaultValue) {
732
901
  }
733
902
  }
734
903
 
735
- function fragmentFromHTML(html = "") {
736
- const foreignDocument = document.implementation.createHTMLDocument();
737
- return foreignDocument.createRange().createContextualFragment(html);
904
+ function fragmentFromHTML(html) {
905
+ if (html) {
906
+ const foreignDocument = document.implementation.createHTMLDocument();
907
+ return foreignDocument.createRange().createContextualFragment(html);
908
+ }
738
909
  }
739
910
 
740
911
  function activateElement(element) {
@@ -746,76 +917,6 @@ function activateElement(element) {
746
917
  }
747
918
  }
748
919
 
749
- class FrameElement extends HTMLElement {
750
- constructor() {
751
- super();
752
- this.controller = new FrameController(this);
753
- }
754
- static get observedAttributes() {
755
- return [ "src" ];
756
- }
757
- connectedCallback() {
758
- this.controller.connect();
759
- }
760
- disconnectedCallback() {
761
- this.controller.disconnect();
762
- }
763
- attributeChangedCallback() {
764
- if (this.src && this.isActive) {
765
- const value = this.controller.visit(this.src);
766
- Object.defineProperty(this, "loaded", {
767
- value: value,
768
- configurable: true
769
- });
770
- }
771
- }
772
- formSubmissionIntercepted(element, submitter) {
773
- this.controller.formSubmissionIntercepted(element, submitter);
774
- }
775
- get src() {
776
- return this.getAttribute("src");
777
- }
778
- set src(value) {
779
- if (value) {
780
- this.setAttribute("src", value);
781
- } else {
782
- this.removeAttribute("src");
783
- }
784
- }
785
- get loaded() {
786
- return Promise.resolve(undefined);
787
- }
788
- get disabled() {
789
- return this.hasAttribute("disabled");
790
- }
791
- set disabled(value) {
792
- if (value) {
793
- this.setAttribute("disabled", "");
794
- } else {
795
- this.removeAttribute("disabled");
796
- }
797
- }
798
- get autoscroll() {
799
- return this.hasAttribute("autoscroll");
800
- }
801
- set autoscroll(value) {
802
- if (value) {
803
- this.setAttribute("autoscroll", "");
804
- } else {
805
- this.removeAttribute("autoscroll");
806
- }
807
- }
808
- get isActive() {
809
- return this.ownerDocument === document && !this.isPreview;
810
- }
811
- get isPreview() {
812
- var _a, _b;
813
- return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
814
- }
815
- }
816
-
817
- customElements.define("turbo-frame", FrameElement);
818
-
819
920
  const StreamActions = {
820
921
  append() {
821
922
  var _a;
@@ -912,6 +1013,10 @@ class StreamElement extends HTMLElement {
912
1013
  }
913
1014
  }
914
1015
 
1016
+ FrameElement.delegateConstructor = FrameController;
1017
+
1018
+ customElements.define("turbo-frame", FrameElement);
1019
+
915
1020
  customElements.define("turbo-stream", StreamElement);
916
1021
 
917
1022
  (() => {
@@ -1633,7 +1738,7 @@ class FrameRedirector {
1633
1738
  formSubmissionIntercepted(element, submitter) {
1634
1739
  const frame = this.findFrameElement(element);
1635
1740
  if (frame) {
1636
- frame.formSubmissionIntercepted(element, submitter);
1741
+ frame.delegate.formSubmissionIntercepted(element, submitter);
1637
1742
  }
1638
1743
  }
1639
1744
  shouldRedirect(element, submitter) {
@@ -1677,8 +1782,6 @@ class History {
1677
1782
  }
1678
1783
  start() {
1679
1784
  if (!this.started) {
1680
- this.previousScrollRestoration = history.scrollRestoration;
1681
- history.scrollRestoration = "manual";
1682
1785
  addEventListener("popstate", this.onPopState, false);
1683
1786
  addEventListener("load", this.onPageLoad, false);
1684
1787
  this.started = true;
@@ -1686,9 +1789,7 @@ class History {
1686
1789
  }
1687
1790
  }
1688
1791
  stop() {
1689
- var _a;
1690
1792
  if (this.started) {
1691
- history.scrollRestoration = (_a = this.previousScrollRestoration) !== null && _a !== void 0 ? _a : "auto";
1692
1793
  removeEventListener("popstate", this.onPopState, false);
1693
1794
  removeEventListener("load", this.onPageLoad, false);
1694
1795
  this.started = false;
@@ -1718,6 +1819,19 @@ class History {
1718
1819
  const restorationData = this.restorationData[restorationIdentifier];
1719
1820
  this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
1720
1821
  }
1822
+ assumeControlOfScrollRestoration() {
1823
+ var _a;
1824
+ if (!this.previousScrollRestoration) {
1825
+ this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
1826
+ history.scrollRestoration = "manual";
1827
+ }
1828
+ }
1829
+ relinquishControlOfScrollRestoration() {
1830
+ if (this.previousScrollRestoration) {
1831
+ history.scrollRestoration = this.previousScrollRestoration;
1832
+ delete this.previousScrollRestoration;
1833
+ }
1834
+ }
1721
1835
  shouldHandlePopState() {
1722
1836
  return this.pageIsLoaded();
1723
1837
  }
@@ -1864,7 +1978,6 @@ var PageStage;
1864
1978
  PageStage[PageStage["loading"] = 1] = "loading";
1865
1979
  PageStage[PageStage["interactive"] = 2] = "interactive";
1866
1980
  PageStage[PageStage["complete"] = 3] = "complete";
1867
- PageStage[PageStage["invalidated"] = 4] = "invalidated";
1868
1981
  })(PageStage || (PageStage = {}));
1869
1982
 
1870
1983
  class PageObserver {
@@ -1879,6 +1992,9 @@ class PageObserver {
1879
1992
  this.pageIsComplete();
1880
1993
  }
1881
1994
  };
1995
+ this.pageWillUnload = () => {
1996
+ this.delegate.pageWillUnload();
1997
+ };
1882
1998
  this.delegate = delegate;
1883
1999
  }
1884
2000
  start() {
@@ -1887,21 +2003,17 @@ class PageObserver {
1887
2003
  this.stage = PageStage.loading;
1888
2004
  }
1889
2005
  document.addEventListener("readystatechange", this.interpretReadyState, false);
2006
+ addEventListener("pagehide", this.pageWillUnload, false);
1890
2007
  this.started = true;
1891
2008
  }
1892
2009
  }
1893
2010
  stop() {
1894
2011
  if (this.started) {
1895
2012
  document.removeEventListener("readystatechange", this.interpretReadyState, false);
2013
+ removeEventListener("pagehide", this.pageWillUnload, false);
1896
2014
  this.started = false;
1897
2015
  }
1898
2016
  }
1899
- invalidate() {
1900
- if (this.stage != PageStage.invalidated) {
1901
- this.stage = PageStage.invalidated;
1902
- this.delegate.pageInvalidated();
1903
- }
1904
- }
1905
2017
  pageIsInteractive() {
1906
2018
  if (this.stage == PageStage.loading) {
1907
2019
  this.stage = PageStage.interactive;
@@ -1991,7 +2103,7 @@ class StreamObserver {
1991
2103
  const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
1992
2104
  if (fetchOptions) {
1993
2105
  const {headers: headers} = fetchOptions;
1994
- headers.Accept = [ "text/html; turbo-stream", headers.Accept ].join(", ");
2106
+ headers.Accept = [ "text/vnd.turbo-stream.html", headers.Accept ].join(", ");
1995
2107
  }
1996
2108
  };
1997
2109
  this.inspectFetchResponse = event => {
@@ -2059,7 +2171,7 @@ function fetchResponseFromEvent(event) {
2059
2171
  function fetchResponseIsStream(response) {
2060
2172
  var _a;
2061
2173
  const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2062
- return /text\/html;.*\bturbo-stream\b/.test(contentType);
2174
+ return /^text\/vnd\.turbo-stream\.html\b/.test(contentType);
2063
2175
  }
2064
2176
 
2065
2177
  function isAction(action) {
@@ -2518,9 +2630,11 @@ class Session {
2518
2630
  this.view.lastRenderedLocation = this.location;
2519
2631
  this.notifyApplicationAfterPageLoad();
2520
2632
  }
2521
- pageLoaded() {}
2522
- pageInvalidated() {
2523
- this.adapter.pageInvalidated();
2633
+ pageLoaded() {
2634
+ this.history.assumeControlOfScrollRestoration();
2635
+ }
2636
+ pageWillUnload() {
2637
+ this.history.relinquishControlOfScrollRestoration();
2524
2638
  }
2525
2639
  receivedMessageFromStream(message) {
2526
2640
  this.renderStreamMessage(message);
@@ -2533,7 +2647,7 @@ class Session {
2533
2647
  this.notifyApplicationAfterRender();
2534
2648
  }
2535
2649
  viewInvalidated() {
2536
- this.pageObserver.invalidate();
2650
+ this.adapter.pageInvalidated();
2537
2651
  }
2538
2652
  viewWillCacheSnapshot() {
2539
2653
  this.notifyApplicationBeforeCachingSnapshot();