turbo-rails 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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();