turbo-rails 0.5.5 → 0.5.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94c9c9d12f7b7318bb4e2eede2cb11e8c7f8ca2eb623d21d7c71c27005422f7a
4
- data.tar.gz: e2df4c5dc7d36d4c960dfeff36318e1ed36f0b94e386ce151bda33ecf8826131
3
+ metadata.gz: 1abdc38a6d5a735bf6bbd2ff6fb8db49ea74d14e3452afa5c2bda93b23ffb073
4
+ data.tar.gz: 0bf468488fd8232fec62b975fb9f0ef5449325cf57b932858e03af876d0548e6
5
5
  SHA512:
6
- metadata.gz: 40dac08e7447ab44a6d6924d60e8bc8c6d5fff7f88bc8f7ba00c54f957b9559e0d0202cb5ece9165fa571c81b9edd49cd83afc0d75f274c16fb44b193ade9578
7
- data.tar.gz: bf61cb90835e8aee3aca8c13728a91b488dae4cb903f3bede52f5991279c3587f9c3dd1eee8b04b450721d65f37d9687e69ad07a690a28d0e5d412932095ccbd
6
+ metadata.gz: 79272181e9d15ab14434ba684ac33a2fa186df4fa15462d2ad96a77caaeb8caaf111beebd9bf70f67b7fee3eae0e35c5af5f6e86a4e2c970f07c7b1c58e983bf
7
+ data.tar.gz: ed1f850a041f5d66ae7bafa8264ac3ee5679db81eb5f91869e4e8624ef0cfadb92f345e9f1de192ee917a568deb866e1231db248820ecdb0afcafc96f8296dc4
data/Rakefile CHANGED
@@ -2,6 +2,10 @@ require "bundler/setup"
2
2
  require "bundler/gem_tasks"
3
3
  require "rake/testtask"
4
4
 
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+ load "rails/tasks/statistics.rake"
8
+
5
9
  Rake::TestTask.new do |test|
6
10
  test.libs << "test"
7
11
  test.test_files = FileList["test/**/*_test.rb"]
@@ -55,7 +55,7 @@ class FrameElement extends HTMLElement {
55
55
  this.delegate = new FrameElement.delegateConstructor(this);
56
56
  }
57
57
  static get observedAttributes() {
58
- return [ "loading", "src" ];
58
+ return [ "disabled", "loading", "src" ];
59
59
  }
60
60
  connectedCallback() {
61
61
  this.delegate.connect();
@@ -68,6 +68,8 @@ class FrameElement extends HTMLElement {
68
68
  this.delegate.loadingStyleChanged();
69
69
  } else if (name == "src") {
70
70
  this.delegate.sourceURLChanged();
71
+ } else {
72
+ this.delegate.disabledChanged();
71
73
  }
72
74
  }
73
75
  get src() {
@@ -132,82 +134,61 @@ function frameLoadingStyleFromString(style) {
132
134
  }
133
135
  }
134
136
 
135
- class Location {
136
- constructor(url) {
137
- const linkWithAnchor = document.createElement("a");
138
- linkWithAnchor.href = url;
139
- this.absoluteURL = linkWithAnchor.href;
140
- const anchorLength = linkWithAnchor.hash.length;
141
- if (anchorLength < 2) {
142
- this.requestURL = this.absoluteURL;
143
- } else {
144
- this.requestURL = this.absoluteURL.slice(0, -anchorLength);
145
- this.anchor = linkWithAnchor.hash.slice(1);
146
- }
147
- }
148
- static get currentLocation() {
149
- return this.wrap(window.location.toString());
150
- }
151
- static wrap(locatable) {
152
- if (typeof locatable == "string") {
153
- return new this(locatable);
154
- } else if (locatable != null) {
155
- return locatable;
156
- }
157
- }
158
- getOrigin() {
159
- return this.absoluteURL.split("/", 3).join("/");
160
- }
161
- getPath() {
162
- return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
163
- }
164
- getPathComponents() {
165
- return this.getPath().split("/").slice(1);
166
- }
167
- getLastPathComponent() {
168
- return this.getPathComponents().slice(-1)[0];
169
- }
170
- getExtension() {
171
- return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
172
- }
173
- isHTML() {
174
- return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
175
- }
176
- isPrefixedBy(location) {
177
- const prefixURL = getPrefixURL(location);
178
- return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
179
- }
180
- isEqualTo(location) {
181
- return location && this.absoluteURL === location.absoluteURL;
182
- }
183
- toCacheKey() {
184
- return this.requestURL;
185
- }
186
- toJSON() {
187
- return this.absoluteURL;
188
- }
189
- toString() {
190
- return this.absoluteURL;
137
+ function expandURL(locatable) {
138
+ return new URL(locatable.toString(), document.baseURI);
139
+ }
140
+
141
+ function getAnchor(url) {
142
+ let anchorMatch;
143
+ if (url.hash) {
144
+ return url.hash.slice(1);
145
+ } else if (anchorMatch = url.href.match(/#(.*)$/)) {
146
+ return anchorMatch[1];
147
+ } else {
148
+ return "";
191
149
  }
192
- valueOf() {
193
- return this.absoluteURL;
150
+ }
151
+
152
+ function getExtension(url) {
153
+ return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
154
+ }
155
+
156
+ function isHTML(url) {
157
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
158
+ }
159
+
160
+ function isPrefixedBy(baseURL, url) {
161
+ const prefix = getPrefix(url);
162
+ return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
163
+ }
164
+
165
+ 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);
194
171
  }
195
172
  }
196
173
 
197
- function getPrefixURL(location) {
198
- return addTrailingSlash(location.getOrigin() + location.getPath());
174
+ function urlsAreEqual(left, right) {
175
+ return expandURL(left).href == expandURL(right).href;
176
+ }
177
+
178
+ function getPathComponents(url) {
179
+ return url.pathname.split("/").slice(1);
199
180
  }
200
181
 
201
- function addTrailingSlash(url) {
202
- return stringEndsWith(url, "/") ? url : url + "/";
182
+ function getLastPathComponent(url) {
183
+ return getPathComponents(url).slice(-1)[0];
203
184
  }
204
185
 
205
- function stringStartsWith(string, prefix) {
206
- return string.slice(0, prefix.length) === prefix;
186
+ function getPrefix(url) {
187
+ return addTrailingSlash(url.origin + url.pathname);
207
188
  }
208
189
 
209
- function stringEndsWith(string, suffix) {
210
- return string.slice(-suffix.length) === suffix;
190
+ function addTrailingSlash(value) {
191
+ return value.endsWith("/") ? value : value + "/";
211
192
  }
212
193
 
213
194
  class FetchResponse {
@@ -230,7 +211,7 @@ class FetchResponse {
230
211
  return this.response.redirected;
231
212
  }
232
213
  get location() {
233
- return Location.wrap(this.response.url);
214
+ return expandURL(this.response.url);
234
215
  }
235
216
  get isHTML() {
236
217
  return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
@@ -270,10 +251,18 @@ function nextAnimationFrame() {
270
251
  return new Promise((resolve => requestAnimationFrame((() => resolve()))));
271
252
  }
272
253
 
254
+ function nextEventLoopTick() {
255
+ return new Promise((resolve => setTimeout((() => resolve()), 0)));
256
+ }
257
+
273
258
  function nextMicrotask() {
274
259
  return Promise.resolve();
275
260
  }
276
261
 
262
+ function parseHTMLDocument(html = "") {
263
+ return (new DOMParser).parseFromString(html, "text/html");
264
+ }
265
+
277
266
  function unindent(strings, ...values) {
278
267
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
279
268
  const match = lines[0].match(/^\s+/);
@@ -334,27 +323,23 @@ function fetchMethodFromString(method) {
334
323
  }
335
324
 
336
325
  class FetchRequest {
337
- constructor(delegate, method, location, body) {
326
+ constructor(delegate, method, location, body = new URLSearchParams) {
338
327
  this.abortController = new AbortController;
339
328
  this.delegate = delegate;
340
329
  this.method = method;
341
- this.location = location;
342
- this.body = body;
343
- }
344
- get url() {
345
- const url = this.location.absoluteURL;
346
- const query = this.params.toString();
347
- if (this.isIdempotent && query.length) {
348
- return [ url, query ].join(url.includes("?") ? "&" : "?");
330
+ this.headers = this.defaultHeaders;
331
+ if (this.isIdempotent) {
332
+ this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
349
333
  } else {
350
- return url;
334
+ this.body = body;
335
+ this.url = location;
351
336
  }
352
337
  }
338
+ get location() {
339
+ return this.url;
340
+ }
353
341
  get params() {
354
- return this.entries.reduce(((params, [name, value]) => {
355
- params.append(name, value.toString());
356
- return params;
357
- }), new URLSearchParams);
342
+ return this.url.searchParams;
358
343
  }
359
344
  get entries() {
360
345
  return this.body ? Array.from(this.body.entries()) : [];
@@ -363,7 +348,9 @@ class FetchRequest {
363
348
  this.abortController.abort();
364
349
  }
365
350
  async perform() {
351
+ var _a, _b;
366
352
  const {fetchOptions: fetchOptions} = this;
353
+ (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
367
354
  dispatch("turbo:before-fetch-request", {
368
355
  detail: {
369
356
  fetchOptions: fetchOptions
@@ -371,7 +358,7 @@ class FetchRequest {
371
358
  });
372
359
  try {
373
360
  this.delegate.requestStarted(this);
374
- const response = await fetch(this.url, fetchOptions);
361
+ const response = await fetch(this.url.href, fetchOptions);
375
362
  return await this.receive(response);
376
363
  } catch (error) {
377
364
  this.delegate.requestErrored(this, error);
@@ -403,28 +390,35 @@ class FetchRequest {
403
390
  credentials: "same-origin",
404
391
  headers: this.headers,
405
392
  redirect: "follow",
406
- body: this.isIdempotent ? undefined : this.body,
393
+ body: this.body,
407
394
  signal: this.abortSignal
408
395
  };
409
396
  }
397
+ get defaultHeaders() {
398
+ return {
399
+ Accept: "text/html, application/xhtml+xml"
400
+ };
401
+ }
410
402
  get isIdempotent() {
411
403
  return this.method == FetchMethod.get;
412
404
  }
413
- get headers() {
414
- return Object.assign({
415
- Accept: "text/html, application/xhtml+xml"
416
- }, this.additionalHeaders);
405
+ get abortSignal() {
406
+ return this.abortController.signal;
417
407
  }
418
- get additionalHeaders() {
419
- if (typeof this.delegate.additionalHeadersForRequest == "function") {
420
- return this.delegate.additionalHeadersForRequest(this);
408
+ }
409
+
410
+ function mergeFormDataEntries(url, entries) {
411
+ const currentSearchParams = new URLSearchParams(url.search);
412
+ for (const [name, value] of entries) {
413
+ if (value instanceof File) continue;
414
+ if (currentSearchParams.has(name)) {
415
+ currentSearchParams.delete(name);
416
+ url.searchParams.set(name, value);
421
417
  } else {
422
- return {};
418
+ url.searchParams.append(name, value);
423
419
  }
424
420
  }
425
- get abortSignal() {
426
- return this.abortController.signal;
427
- }
421
+ return url;
428
422
  }
429
423
 
430
424
  class AppearanceObserver {
@@ -454,6 +448,41 @@ class AppearanceObserver {
454
448
  }
455
449
  }
456
450
 
451
+ class StreamMessage {
452
+ constructor(html) {
453
+ this.templateElement = document.createElement("template");
454
+ this.templateElement.innerHTML = html;
455
+ }
456
+ static wrap(message) {
457
+ if (typeof message == "string") {
458
+ return new this(message);
459
+ } else {
460
+ return message;
461
+ }
462
+ }
463
+ get fragment() {
464
+ const fragment = document.createDocumentFragment();
465
+ for (const element of this.foreignElements) {
466
+ fragment.appendChild(document.importNode(element, true));
467
+ }
468
+ return fragment;
469
+ }
470
+ get foreignElements() {
471
+ return this.templateChildren.reduce(((streamElements, child) => {
472
+ if (child.tagName.toLowerCase() == "turbo-stream") {
473
+ return [ ...streamElements, child ];
474
+ } else {
475
+ return streamElements;
476
+ }
477
+ }), []);
478
+ }
479
+ get templateChildren() {
480
+ return Array.from(this.templateElement.content.children);
481
+ }
482
+ }
483
+
484
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
485
+
457
486
  var FormSubmissionState;
458
487
 
459
488
  (function(FormSubmissionState) {
@@ -465,14 +494,35 @@ var FormSubmissionState;
465
494
  FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
466
495
  })(FormSubmissionState || (FormSubmissionState = {}));
467
496
 
497
+ var FormEnctype;
498
+
499
+ (function(FormEnctype) {
500
+ FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
501
+ FormEnctype["multipart"] = "multipart/form-data";
502
+ FormEnctype["plain"] = "text/plain";
503
+ })(FormEnctype || (FormEnctype = {}));
504
+
505
+ function formEnctypeFromString(encoding) {
506
+ switch (encoding.toLowerCase()) {
507
+ case FormEnctype.multipart:
508
+ return FormEnctype.multipart;
509
+
510
+ case FormEnctype.plain:
511
+ return FormEnctype.plain;
512
+
513
+ default:
514
+ return FormEnctype.urlEncoded;
515
+ }
516
+ }
517
+
468
518
  class FormSubmission {
469
519
  constructor(delegate, formElement, submitter, mustRedirect = false) {
470
520
  this.state = FormSubmissionState.initialized;
471
521
  this.delegate = delegate;
472
522
  this.formElement = formElement;
473
- this.formData = buildFormData(formElement, submitter);
474
523
  this.submitter = submitter;
475
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.formData);
524
+ this.formData = buildFormData(formElement, submitter);
525
+ this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
476
526
  this.mustRedirect = mustRedirect;
477
527
  }
478
528
  get method() {
@@ -485,7 +535,24 @@ class FormSubmission {
485
535
  return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
486
536
  }
487
537
  get location() {
488
- return Location.wrap(this.action);
538
+ return expandURL(this.action);
539
+ }
540
+ get body() {
541
+ if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
542
+ return new URLSearchParams(this.stringFormData);
543
+ } else {
544
+ return this.formData;
545
+ }
546
+ }
547
+ get enctype() {
548
+ var _a;
549
+ return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
550
+ }
551
+ get isIdempotent() {
552
+ return this.fetchRequest.isIdempotent;
553
+ }
554
+ get stringFormData() {
555
+ return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
489
556
  }
490
557
  async start() {
491
558
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
@@ -502,15 +569,14 @@ class FormSubmission {
502
569
  return true;
503
570
  }
504
571
  }
505
- additionalHeadersForRequest(request) {
506
- const headers = {};
507
- if (this.method != FetchMethod.get) {
572
+ prepareHeadersForRequest(headers, request) {
573
+ if (!request.isIdempotent) {
508
574
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
509
575
  if (token) {
510
576
  headers["X-CSRF-Token"] = token;
511
577
  }
578
+ headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
512
579
  }
513
- return headers;
514
580
  }
515
581
  requestStarted(request) {
516
582
  this.state = FormSubmissionState.waiting;
@@ -576,8 +642,8 @@ function buildFormData(formElement, submitter) {
576
642
  const formData = new FormData(formElement);
577
643
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
578
644
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
579
- if (name && formData.get(name) != value) {
580
- formData.append(name, value || "");
645
+ if (name && value != null && formData.get(name) != value) {
646
+ formData.append(name, value);
581
647
  }
582
648
  return formData;
583
649
  }
@@ -602,6 +668,48 @@ function responseSucceededWithoutRedirect(response) {
602
668
  return response.statusCode == 200 && !response.redirected;
603
669
  }
604
670
 
671
+ class Snapshot {
672
+ constructor(element) {
673
+ this.element = element;
674
+ }
675
+ get children() {
676
+ return [ ...this.element.children ];
677
+ }
678
+ hasAnchor(anchor) {
679
+ return this.getElementForAnchor(anchor) != null;
680
+ }
681
+ getElementForAnchor(anchor) {
682
+ try {
683
+ return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
684
+ } catch (_a) {
685
+ return null;
686
+ }
687
+ }
688
+ get isConnected() {
689
+ return this.element.isConnected;
690
+ }
691
+ get firstAutofocusableElement() {
692
+ return this.element.querySelector("[autofocus]");
693
+ }
694
+ get permanentElements() {
695
+ return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
696
+ }
697
+ getPermanentElementById(id) {
698
+ return this.element.querySelector(`#${id}[data-turbo-permanent]`);
699
+ }
700
+ getPermanentElementMapForSnapshot(snapshot) {
701
+ const permanentElementMap = {};
702
+ for (const currentPermanentElement of this.permanentElements) {
703
+ const {id: id} = currentPermanentElement;
704
+ const newPermanentElement = snapshot.getPermanentElementById(id);
705
+ if (newPermanentElement) {
706
+ permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
707
+ }
708
+ }
709
+ return permanentElementMap;
710
+ }
711
+ }
712
+
605
713
  class FormInterceptor {
606
714
  constructor(delegate, element) {
607
715
  this.submitBubbled = event => {
@@ -626,6 +734,82 @@ class FormInterceptor {
626
734
  }
627
735
  }
628
736
 
737
+ class View {
738
+ constructor(delegate, element) {
739
+ this.delegate = delegate;
740
+ this.element = element;
741
+ }
742
+ scrollToAnchor(anchor) {
743
+ const element = this.snapshot.getElementForAnchor(anchor);
744
+ if (element) {
745
+ this.scrollToElement(element);
746
+ } else {
747
+ this.scrollToPosition({
748
+ x: 0,
749
+ y: 0
750
+ });
751
+ }
752
+ }
753
+ scrollToElement(element) {
754
+ element.scrollIntoView();
755
+ }
756
+ scrollToPosition({x: x, y: y}) {
757
+ this.scrollRoot.scrollTo(x, y);
758
+ }
759
+ get scrollRoot() {
760
+ return window;
761
+ }
762
+ async render(renderer) {
763
+ if (this.renderer) {
764
+ throw new Error("rendering is already in progress");
765
+ }
766
+ const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
767
+ if (shouldRender) {
768
+ try {
769
+ this.renderer = renderer;
770
+ this.prepareToRenderSnapshot(renderer);
771
+ this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
772
+ await this.renderSnapshot(renderer);
773
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview);
774
+ this.finishRenderingSnapshot(renderer);
775
+ } finally {
776
+ delete this.renderer;
777
+ }
778
+ } else {
779
+ this.invalidate();
780
+ }
781
+ }
782
+ invalidate() {
783
+ this.delegate.viewInvalidated();
784
+ }
785
+ prepareToRenderSnapshot(renderer) {
786
+ this.markAsPreview(renderer.isPreview);
787
+ renderer.prepareToRender();
788
+ }
789
+ markAsPreview(isPreview) {
790
+ if (isPreview) {
791
+ this.element.setAttribute("data-turbo-preview", "");
792
+ } else {
793
+ this.element.removeAttribute("data-turbo-preview");
794
+ }
795
+ }
796
+ async renderSnapshot(renderer) {
797
+ await renderer.render();
798
+ }
799
+ finishRenderingSnapshot(renderer) {
800
+ renderer.finishRendering();
801
+ }
802
+ }
803
+
804
+ class FrameView extends View {
805
+ invalidate() {
806
+ this.element.innerHTML = "";
807
+ }
808
+ get snapshot() {
809
+ return new Snapshot(this.element);
810
+ }
811
+ }
812
+
629
813
  class LinkInterceptor {
630
814
  constructor(delegate, element) {
631
815
  this.clickBubbled = event => {
@@ -640,7 +824,7 @@ class LinkInterceptor {
640
824
  if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
641
825
  this.clickEvent.preventDefault();
642
826
  event.preventDefault();
643
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
827
+ this.convertLinkWithMethodClickToFormSubmission(event.target) || this.delegate.linkClickIntercepted(event.target, event.detail.url);
644
828
  }
645
829
  }
646
830
  delete this.clickEvent;
@@ -661,183 +845,172 @@ class LinkInterceptor {
661
845
  document.removeEventListener("turbo:click", this.linkClicked);
662
846
  document.removeEventListener("turbo:before-visit", this.willVisit);
663
847
  }
848
+ convertLinkWithMethodClickToFormSubmission(link) {
849
+ var _a;
850
+ const linkMethod = link.getAttribute("data-turbo-method") || link.getAttribute("data-method");
851
+ if (linkMethod) {
852
+ const form = document.createElement("form");
853
+ form.method = linkMethod;
854
+ form.action = link.getAttribute("href") || "undefined";
855
+ (_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
856
+ return dispatch("submit", {
857
+ target: form
858
+ });
859
+ } else {
860
+ return false;
861
+ }
862
+ }
664
863
  respondsToEventTarget(target) {
665
864
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
666
865
  return element && element.closest("turbo-frame, html") == this.element;
667
866
  }
668
867
  }
669
868
 
670
- class FrameController {
671
- constructor(element) {
672
- this.resolveVisitPromise = () => {};
673
- this.element = element;
674
- this.appearanceObserver = new AppearanceObserver(this, this.element);
675
- this.linkInterceptor = new LinkInterceptor(this, this.element);
676
- this.formInterceptor = new FormInterceptor(this, this.element);
677
- }
678
- connect() {
679
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
680
- this.appearanceObserver.start();
681
- }
682
- this.linkInterceptor.start();
683
- this.formInterceptor.start();
869
+ class Bardo {
870
+ constructor(permanentElementMap) {
871
+ this.permanentElementMap = permanentElementMap;
684
872
  }
685
- disconnect() {
686
- this.appearanceObserver.stop();
687
- this.linkInterceptor.stop();
688
- this.formInterceptor.stop();
873
+ static preservingPermanentElements(permanentElementMap, callback) {
874
+ const bardo = new this(permanentElementMap);
875
+ bardo.enter();
876
+ callback();
877
+ bardo.leave();
689
878
  }
690
- sourceURLChanged() {
691
- if (this.loadingStyle == FrameLoadingStyle.eager) {
692
- this.loadSourceURL();
879
+ enter() {
880
+ for (const id in this.permanentElementMap) {
881
+ const [, newPermanentElement] = this.permanentElementMap[id];
882
+ this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
693
883
  }
694
884
  }
695
- loadingStyleChanged() {
696
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
697
- this.appearanceObserver.start();
698
- } else {
699
- this.appearanceObserver.stop();
700
- this.loadSourceURL();
885
+ leave() {
886
+ for (const id in this.permanentElementMap) {
887
+ const [currentPermanentElement] = this.permanentElementMap[id];
888
+ this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
889
+ this.replacePlaceholderWithPermanentElement(currentPermanentElement);
701
890
  }
702
891
  }
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
- }
892
+ replaceNewPermanentElementWithPlaceholder(permanentElement) {
893
+ const placeholder = createPlaceholderForPermanentElement(permanentElement);
894
+ permanentElement.replaceWith(placeholder);
714
895
  }
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
- }
896
+ replaceCurrentPermanentElementWithClone(permanentElement) {
897
+ const clone = permanentElement.cloneNode(true);
898
+ permanentElement.replaceWith(clone);
725
899
  }
726
- elementAppearedInViewport(element) {
727
- this.loadSourceURL();
900
+ replacePlaceholderWithPermanentElement(permanentElement) {
901
+ const placeholder = this.getPlaceholderById(permanentElement.id);
902
+ placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
728
903
  }
729
- shouldInterceptLinkClick(element, url) {
730
- return this.shouldInterceptNavigation(element);
904
+ getPlaceholderById(id) {
905
+ return this.placeholders.find((element => element.content == id));
731
906
  }
732
- linkClickIntercepted(element, url) {
733
- this.navigateFrame(element, url);
907
+ get placeholders() {
908
+ return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
734
909
  }
735
- shouldInterceptFormSubmission(element) {
736
- return this.shouldInterceptNavigation(element);
910
+ }
911
+
912
+ function createPlaceholderForPermanentElement(permanentElement) {
913
+ const element = document.createElement("meta");
914
+ element.setAttribute("name", "turbo-permanent-placeholder");
915
+ element.setAttribute("content", permanentElement.id);
916
+ return element;
917
+ }
918
+
919
+ class Renderer {
920
+ constructor(currentSnapshot, newSnapshot, isPreview) {
921
+ this.currentSnapshot = currentSnapshot;
922
+ this.newSnapshot = newSnapshot;
923
+ this.isPreview = isPreview;
924
+ this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
925
+ resolve: resolve,
926
+ reject: reject
927
+ }));
737
928
  }
738
- formSubmissionIntercepted(element, submitter) {
739
- if (this.formSubmission) {
740
- this.formSubmission.stop();
929
+ get shouldRender() {
930
+ return true;
931
+ }
932
+ prepareToRender() {
933
+ return;
934
+ }
935
+ finishRendering() {
936
+ if (this.resolvingFunctions) {
937
+ this.resolvingFunctions.resolve();
938
+ delete this.resolvingFunctions;
741
939
  }
742
- this.formSubmission = new FormSubmission(this, element, submitter);
743
- if (this.formSubmission.fetchRequest.isIdempotent) {
744
- this.navigateFrame(element, this.formSubmission.fetchRequest.url);
940
+ }
941
+ createScriptElement(element) {
942
+ if (element.getAttribute("data-turbo-eval") == "false") {
943
+ return element;
745
944
  } else {
746
- this.formSubmission.start();
945
+ const createdScriptElement = document.createElement("script");
946
+ createdScriptElement.textContent = element.textContent;
947
+ createdScriptElement.async = false;
948
+ copyElementAttributes(createdScriptElement, element);
949
+ return createdScriptElement;
747
950
  }
748
951
  }
749
- additionalHeadersForRequest(request) {
750
- return {
751
- "Turbo-Frame": this.id
752
- };
952
+ preservingPermanentElements(callback) {
953
+ Bardo.preservingPermanentElements(this.permanentElementMap, callback);
753
954
  }
754
- requestStarted(request) {
755
- this.element.setAttribute("busy", "");
756
- }
757
- requestPreventedHandlingResponse(request, response) {
758
- this.resolveVisitPromise();
955
+ focusFirstAutofocusableElement() {
956
+ const element = this.connectedSnapshot.firstAutofocusableElement;
957
+ if (elementIsFocusable(element)) {
958
+ element.focus();
959
+ }
759
960
  }
760
- async requestSucceededWithResponse(request, response) {
761
- await this.loadResponse(response);
762
- this.resolveVisitPromise();
961
+ get connectedSnapshot() {
962
+ return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
763
963
  }
764
- requestFailedWithResponse(request, response) {
765
- console.error(response);
766
- this.resolveVisitPromise();
964
+ get currentElement() {
965
+ return this.currentSnapshot.element;
767
966
  }
768
- requestErrored(request, error) {
769
- console.error(error);
770
- this.resolveVisitPromise();
967
+ get newElement() {
968
+ return this.newSnapshot.element;
771
969
  }
772
- requestFinished(request) {
773
- this.element.removeAttribute("busy");
970
+ get permanentElementMap() {
971
+ return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
774
972
  }
775
- formSubmissionStarted(formSubmission) {}
776
- formSubmissionSucceededWithResponse(formSubmission, response) {
777
- const frame = this.findFrameElement(formSubmission.formElement);
778
- frame.delegate.loadResponse(response);
973
+ }
974
+
975
+ function copyElementAttributes(destinationElement, sourceElement) {
976
+ for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
977
+ destinationElement.setAttribute(name, value);
779
978
  }
780
- formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
781
- this.element.delegate.loadResponse(fetchResponse);
979
+ }
980
+
981
+ function elementIsFocusable(element) {
982
+ return element && typeof element.focus == "function";
983
+ }
984
+
985
+ class FrameRenderer extends Renderer {
986
+ get shouldRender() {
987
+ return true;
782
988
  }
783
- formSubmissionErrored(formSubmission, error) {}
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();
989
+ async render() {
990
+ await nextAnimationFrame();
991
+ this.preservingPermanentElements((() => {
992
+ this.loadFrameElement();
794
993
  }));
994
+ this.scrollFrameIntoView();
995
+ await nextAnimationFrame();
996
+ this.focusFirstAutofocusableElement();
795
997
  }
796
- navigateFrame(element, url) {
797
- const frame = this.findFrameElement(element);
798
- frame.src = url;
799
- }
800
- findFrameElement(element) {
801
- var _a;
802
- const id = element.getAttribute("data-turbo-frame");
803
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
804
- }
805
- async extractForeignFrameElement(container) {
806
- let element;
807
- const id = CSS.escape(this.id);
808
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
809
- return element;
810
- }
811
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
812
- await element.loaded;
813
- return await this.extractForeignFrameElement(element);
814
- }
815
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
816
- return new FrameElement;
817
- }
818
- loadFrameElement(frameElement) {
998
+ loadFrameElement() {
819
999
  var _a;
820
1000
  const destinationRange = document.createRange();
821
- destinationRange.selectNodeContents(this.element);
1001
+ destinationRange.selectNodeContents(this.currentElement);
822
1002
  destinationRange.deleteContents();
1003
+ const frameElement = this.newElement;
823
1004
  const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
824
1005
  if (sourceRange) {
825
1006
  sourceRange.selectNodeContents(frameElement);
826
- this.element.appendChild(sourceRange.extractContents());
827
- }
828
- }
829
- focusFirstAutofocusableElement() {
830
- const element = this.firstAutofocusableElement;
831
- if (element) {
832
- element.focus();
833
- return true;
1007
+ this.currentElement.appendChild(sourceRange.extractContents());
834
1008
  }
835
- return false;
836
1009
  }
837
- scrollFrameIntoView(frame) {
838
- if (this.element.autoscroll || frame.autoscroll) {
839
- const element = this.element.firstElementChild;
840
- const block = readScrollLogicalPosition(this.element.getAttribute("data-autoscroll-block"), "end");
1010
+ scrollFrameIntoView() {
1011
+ if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1012
+ const element = this.currentElement.firstElementChild;
1013
+ const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
841
1014
  if (element) {
842
1015
  element.scrollIntoView({
843
1016
  block: block
@@ -847,50 +1020,6 @@ class FrameController {
847
1020
  }
848
1021
  return false;
849
1022
  }
850
- shouldInterceptNavigation(element) {
851
- const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
852
- if (!this.enabled || id == "_top") {
853
- return false;
854
- }
855
- if (id) {
856
- const frameElement = getFrameElementById(id);
857
- if (frameElement) {
858
- return !frameElement.disabled;
859
- }
860
- }
861
- return true;
862
- }
863
- get firstAutofocusableElement() {
864
- const element = this.element.querySelector("[autofocus]");
865
- return element instanceof HTMLElement ? element : null;
866
- }
867
- get id() {
868
- return this.element.id;
869
- }
870
- get enabled() {
871
- return !this.element.disabled;
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
- }
885
- }
886
-
887
- function getFrameElementById(id) {
888
- if (id != null) {
889
- const element = document.getElementById(id);
890
- if (element instanceof FrameElement) {
891
- return element;
892
- }
893
- }
894
1023
  }
895
1024
 
896
1025
  function readScrollLogicalPosition(value, defaultValue) {
@@ -901,144 +1030,6 @@ function readScrollLogicalPosition(value, defaultValue) {
901
1030
  }
902
1031
  }
903
1032
 
904
- function fragmentFromHTML(html) {
905
- if (html) {
906
- const foreignDocument = document.implementation.createHTMLDocument();
907
- return foreignDocument.createRange().createContextualFragment(html);
908
- }
909
- }
910
-
911
- function activateElement(element) {
912
- if (element && element.ownerDocument !== document) {
913
- element = document.importNode(element, true);
914
- }
915
- if (element instanceof FrameElement) {
916
- return element;
917
- }
918
- }
919
-
920
- const StreamActions = {
921
- append() {
922
- var _a;
923
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
924
- },
925
- prepend() {
926
- var _a;
927
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
928
- },
929
- remove() {
930
- var _a;
931
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
932
- },
933
- replace() {
934
- var _a;
935
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
936
- },
937
- update() {
938
- if (this.targetElement) {
939
- this.targetElement.innerHTML = "";
940
- this.targetElement.append(this.templateContent);
941
- }
942
- }
943
- };
944
-
945
- class StreamElement extends HTMLElement {
946
- async connectedCallback() {
947
- try {
948
- await this.render();
949
- } catch (error) {
950
- console.error(error);
951
- } finally {
952
- this.disconnect();
953
- }
954
- }
955
- async render() {
956
- var _a;
957
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
958
- if (this.dispatchEvent(this.beforeRenderEvent)) {
959
- await nextAnimationFrame();
960
- this.performAction();
961
- }
962
- })();
963
- }
964
- disconnect() {
965
- try {
966
- this.remove();
967
- } catch (_a) {}
968
- }
969
- get performAction() {
970
- if (this.action) {
971
- const actionFunction = StreamActions[this.action];
972
- if (actionFunction) {
973
- return actionFunction;
974
- }
975
- this.raise("unknown action");
976
- }
977
- this.raise("action attribute is missing");
978
- }
979
- get targetElement() {
980
- var _a;
981
- if (this.target) {
982
- return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
983
- }
984
- this.raise("target attribute is missing");
985
- }
986
- get templateContent() {
987
- return this.templateElement.content;
988
- }
989
- get templateElement() {
990
- if (this.firstElementChild instanceof HTMLTemplateElement) {
991
- return this.firstElementChild;
992
- }
993
- this.raise("first child element must be a <template> element");
994
- }
995
- get action() {
996
- return this.getAttribute("action");
997
- }
998
- get target() {
999
- return this.getAttribute("target");
1000
- }
1001
- raise(message) {
1002
- throw new Error(`${this.description}: ${message}`);
1003
- }
1004
- get description() {
1005
- var _a, _b;
1006
- return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
1007
- }
1008
- get beforeRenderEvent() {
1009
- return new CustomEvent("turbo:before-stream-render", {
1010
- bubbles: true,
1011
- cancelable: true
1012
- });
1013
- }
1014
- }
1015
-
1016
- FrameElement.delegateConstructor = FrameController;
1017
-
1018
- customElements.define("turbo-frame", FrameElement);
1019
-
1020
- customElements.define("turbo-stream", StreamElement);
1021
-
1022
- (() => {
1023
- let element = document.currentScript;
1024
- if (!element) return;
1025
- if (element.hasAttribute("data-turbo-suppress-warning")) return;
1026
- while (element = element.parentElement) {
1027
- if (element == document.body) {
1028
- return console.warn(unindent`
1029
- You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
1030
-
1031
- Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
1032
-
1033
- For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
1034
-
1035
- ——
1036
- Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
1037
- `, element.outerHTML);
1038
- }
1039
- }
1040
- })();
1041
-
1042
1033
  class ProgressBar {
1043
1034
  constructor() {
1044
1035
  this.hiding = false;
@@ -1138,9 +1129,10 @@ class ProgressBar {
1138
1129
 
1139
1130
  ProgressBar.animationDuration = 300;
1140
1131
 
1141
- class HeadDetails {
1142
- constructor(children) {
1143
- this.detailsByOuterHTML = children.reduce(((result, element) => {
1132
+ class HeadSnapshot extends Snapshot {
1133
+ constructor() {
1134
+ super(...arguments);
1135
+ this.detailsByOuterHTML = this.children.reduce(((result, element) => {
1144
1136
  const {outerHTML: outerHTML} = element;
1145
1137
  const details = outerHTML in result ? result[outerHTML] : {
1146
1138
  type: elementType(element),
@@ -1154,23 +1146,19 @@ class HeadDetails {
1154
1146
  });
1155
1147
  }), {});
1156
1148
  }
1157
- static fromHeadElement(headElement) {
1158
- const children = headElement ? [ ...headElement.children ] : [];
1159
- return new this(children);
1160
- }
1161
- getTrackedElementSignature() {
1149
+ get trackedElementSignature() {
1162
1150
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1163
1151
  }
1164
- getScriptElementsNotInDetails(headDetails) {
1165
- return this.getElementsMatchingTypeNotInDetails("script", headDetails);
1152
+ getScriptElementsNotInSnapshot(snapshot) {
1153
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1166
1154
  }
1167
- getStylesheetElementsNotInDetails(headDetails) {
1168
- return this.getElementsMatchingTypeNotInDetails("stylesheet", headDetails);
1155
+ getStylesheetElementsNotInSnapshot(snapshot) {
1156
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1169
1157
  }
1170
- getElementsMatchingTypeNotInDetails(matchedType, headDetails) {
1171
- return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in headDetails.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
1158
+ getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1159
+ 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));
1172
1160
  }
1173
- getProvisionalElements() {
1161
+ get provisionalElements() {
1174
1162
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1175
1163
  const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
1176
1164
  if (type == null && !tracked) {
@@ -1221,75 +1209,45 @@ function elementIsMetaElementWithName(element, name) {
1221
1209
  return tagName == "meta" && element.getAttribute("name") == name;
1222
1210
  }
1223
1211
 
1224
- class Snapshot {
1225
- constructor(headDetails, bodyElement) {
1226
- this.headDetails = headDetails;
1227
- this.bodyElement = bodyElement;
1228
- }
1229
- static wrap(value) {
1230
- if (value instanceof this) {
1231
- return value;
1232
- } else if (typeof value == "string") {
1233
- return this.fromHTMLString(value);
1234
- } else {
1235
- return this.fromHTMLElement(value);
1236
- }
1212
+ class PageSnapshot extends Snapshot {
1213
+ constructor(element, headSnapshot) {
1214
+ super(element);
1215
+ this.headSnapshot = headSnapshot;
1216
+ }
1217
+ static fromHTMLString(html = "") {
1218
+ return this.fromDocument(parseHTMLDocument(html));
1237
1219
  }
1238
- static fromHTMLString(html) {
1239
- const {documentElement: documentElement} = (new DOMParser).parseFromString(html, "text/html");
1240
- return this.fromHTMLElement(documentElement);
1220
+ static fromElement(element) {
1221
+ return this.fromDocument(element.ownerDocument);
1241
1222
  }
1242
- static fromHTMLElement(htmlElement) {
1243
- const headElement = htmlElement.querySelector("head");
1244
- const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
1245
- const headDetails = HeadDetails.fromHeadElement(headElement);
1246
- return new this(headDetails, bodyElement);
1223
+ static fromDocument({head: head, body: body}) {
1224
+ return new this(body, new HeadSnapshot(head));
1247
1225
  }
1248
1226
  clone() {
1249
- const {bodyElement: bodyElement} = Snapshot.fromHTMLString(this.bodyElement.outerHTML);
1250
- return new Snapshot(this.headDetails, bodyElement);
1227
+ return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1251
1228
  }
1252
- getRootLocation() {
1253
- const root = this.getSetting("root", "/");
1254
- return new Location(root);
1229
+ get headElement() {
1230
+ return this.headSnapshot.element;
1255
1231
  }
1256
- getCacheControlValue() {
1257
- return this.getSetting("cache-control");
1232
+ get rootLocation() {
1233
+ var _a;
1234
+ const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1235
+ return expandURL(root);
1258
1236
  }
1259
- getElementForAnchor(anchor) {
1260
- try {
1261
- return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
1262
- } catch (_a) {
1263
- return null;
1264
- }
1237
+ get cacheControlValue() {
1238
+ return this.getSetting("cache-control");
1265
1239
  }
1266
- getPermanentElements() {
1267
- return [ ...this.bodyElement.querySelectorAll("[id][data-turbo-permanent]") ];
1240
+ get isPreviewable() {
1241
+ return this.cacheControlValue != "no-preview";
1268
1242
  }
1269
- getPermanentElementById(id) {
1270
- return this.bodyElement.querySelector(`#${id}[data-turbo-permanent]`);
1243
+ get isCacheable() {
1244
+ return this.cacheControlValue != "no-cache";
1271
1245
  }
1272
- getPermanentElementsPresentInSnapshot(snapshot) {
1273
- return this.getPermanentElements().filter((({id: id}) => snapshot.getPermanentElementById(id)));
1246
+ get isVisitable() {
1247
+ return this.getSetting("visit-control") != "reload";
1274
1248
  }
1275
- findFirstAutofocusableElement() {
1276
- return this.bodyElement.querySelector("[autofocus]");
1277
- }
1278
- hasAnchor(anchor) {
1279
- return this.getElementForAnchor(anchor) != null;
1280
- }
1281
- isPreviewable() {
1282
- return this.getCacheControlValue() != "no-preview";
1283
- }
1284
- isCacheable() {
1285
- return this.getCacheControlValue() != "no-cache";
1286
- }
1287
- isVisitable() {
1288
- return this.getSetting("visit-control") != "reload";
1289
- }
1290
- getSetting(name, defaultValue) {
1291
- const value = this.headDetails.getMetaValue(`turbo-${name}`);
1292
- return value == null ? defaultValue : value;
1249
+ getSetting(name) {
1250
+ return this.headSnapshot.getMetaValue(`turbo-${name}`);
1293
1251
  }
1294
1252
  }
1295
1253
 
@@ -1334,16 +1292,6 @@ class Visit {
1334
1292
  this.scrolled = false;
1335
1293
  this.snapshotCached = false;
1336
1294
  this.state = VisitState.initialized;
1337
- this.performScroll = () => {
1338
- if (!this.scrolled) {
1339
- if (this.action == "restore") {
1340
- this.scrollToRestoredPosition() || this.scrollToTop();
1341
- } else {
1342
- this.scrollToAnchor() || this.scrollToTop();
1343
- }
1344
- this.scrolled = true;
1345
- }
1346
- };
1347
1295
  this.delegate = delegate;
1348
1296
  this.location = location;
1349
1297
  this.restorationIdentifier = restorationIdentifier || uuid();
@@ -1398,8 +1346,9 @@ class Visit {
1398
1346
  }
1399
1347
  }
1400
1348
  changeHistory() {
1349
+ var _a;
1401
1350
  if (!this.historyChanged) {
1402
- const actionForHistory = this.location.isEqualTo(this.referrer) ? "replace" : this.action;
1351
+ const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1403
1352
  const method = this.getHistoryMethodForAction(actionForHistory);
1404
1353
  this.history.update(method, this.location, this.restorationIdentifier);
1405
1354
  this.historyChanged = true;
@@ -1442,18 +1391,14 @@ class Visit {
1442
1391
  loadResponse() {
1443
1392
  if (this.response) {
1444
1393
  const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
1445
- this.render((() => {
1394
+ this.render((async () => {
1446
1395
  this.cacheSnapshot();
1447
1396
  if (isSuccessful(statusCode) && responseHTML != null) {
1448
- this.view.render({
1449
- snapshot: Snapshot.fromHTMLString(responseHTML)
1450
- }, this.performScroll);
1397
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1451
1398
  this.adapter.visitRendered(this);
1452
1399
  this.complete();
1453
1400
  } else {
1454
- this.view.render({
1455
- error: responseHTML
1456
- }, this.performScroll);
1401
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1457
1402
  this.adapter.visitRendered(this);
1458
1403
  this.fail();
1459
1404
  }
@@ -1462,15 +1407,15 @@ class Visit {
1462
1407
  }
1463
1408
  getCachedSnapshot() {
1464
1409
  const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
1465
- if (snapshot && (!this.location.anchor || snapshot.hasAnchor(this.location.anchor))) {
1466
- if (this.action == "restore" || snapshot.isPreviewable()) {
1410
+ if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
1411
+ if (this.action == "restore" || snapshot.isPreviewable) {
1467
1412
  return snapshot;
1468
1413
  }
1469
1414
  }
1470
1415
  }
1471
1416
  getPreloadedSnapshot() {
1472
1417
  if (this.snapshotHTML) {
1473
- return Snapshot.wrap(this.snapshotHTML);
1418
+ return PageSnapshot.fromHTMLString(this.snapshotHTML);
1474
1419
  }
1475
1420
  }
1476
1421
  hasCachedSnapshot() {
@@ -1480,12 +1425,9 @@ class Visit {
1480
1425
  const snapshot = this.getCachedSnapshot();
1481
1426
  if (snapshot) {
1482
1427
  const isPreview = this.shouldIssueRequest();
1483
- this.render((() => {
1428
+ this.render((async () => {
1484
1429
  this.cacheSnapshot();
1485
- this.view.render({
1486
- snapshot: snapshot,
1487
- isPreview: isPreview
1488
- }, this.performScroll);
1430
+ await this.view.renderPage(snapshot, isPreview);
1489
1431
  this.adapter.visitRendered(this);
1490
1432
  if (!isPreview) {
1491
1433
  this.complete();
@@ -1539,6 +1481,16 @@ class Visit {
1539
1481
  requestFinished() {
1540
1482
  this.finishRequest();
1541
1483
  }
1484
+ performScroll() {
1485
+ if (!this.scrolled) {
1486
+ if (this.action == "restore") {
1487
+ this.scrollToRestoredPosition() || this.scrollToTop();
1488
+ } else {
1489
+ this.scrollToAnchor() || this.scrollToTop();
1490
+ }
1491
+ this.scrolled = true;
1492
+ }
1493
+ }
1542
1494
  scrollToRestoredPosition() {
1543
1495
  const {scrollPosition: scrollPosition} = this.restorationData;
1544
1496
  if (scrollPosition) {
@@ -1547,8 +1499,8 @@ class Visit {
1547
1499
  }
1548
1500
  }
1549
1501
  scrollToAnchor() {
1550
- if (this.location.anchor != null) {
1551
- this.view.scrollToAnchor(this.location.anchor);
1502
+ if (getAnchor(this.location)) {
1503
+ this.view.scrollToAnchor(getAnchor(this.location));
1552
1504
  return true;
1553
1505
  }
1554
1506
  }
@@ -1586,12 +1538,14 @@ class Visit {
1586
1538
  this.snapshotCached = true;
1587
1539
  }
1588
1540
  }
1589
- render(callback) {
1541
+ async render(callback) {
1590
1542
  this.cancelRender();
1591
- this.frame = requestAnimationFrame((() => {
1592
- delete this.frame;
1593
- callback.call(this);
1543
+ await new Promise((resolve => {
1544
+ this.frame = requestAnimationFrame((() => resolve()));
1594
1545
  }));
1546
+ callback();
1547
+ delete this.frame;
1548
+ this.performScroll();
1595
1549
  }
1596
1550
  cancelRender() {
1597
1551
  if (this.frame) {
@@ -1766,11 +1720,10 @@ class History {
1766
1720
  if (this.shouldHandlePopState()) {
1767
1721
  const {turbo: turbo} = event.state || {};
1768
1722
  if (turbo) {
1769
- const location = Location.currentLocation;
1770
- this.location = location;
1723
+ this.location = new URL(window.location.href);
1771
1724
  const {restorationIdentifier: restorationIdentifier} = turbo;
1772
1725
  this.restorationIdentifier = restorationIdentifier;
1773
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
1726
+ this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
1774
1727
  }
1775
1728
  }
1776
1729
  };
@@ -1785,7 +1738,7 @@ class History {
1785
1738
  addEventListener("popstate", this.onPopState, false);
1786
1739
  addEventListener("load", this.onPageLoad, false);
1787
1740
  this.started = true;
1788
- this.replace(Location.currentLocation);
1741
+ this.replace(new URL(window.location.href));
1789
1742
  }
1790
1743
  }
1791
1744
  stop() {
@@ -1807,7 +1760,7 @@ class History {
1807
1760
  restorationIdentifier: restorationIdentifier
1808
1761
  }
1809
1762
  };
1810
- method.call(history, state, "", location.absoluteURL);
1763
+ method.call(history, state, "", location.href);
1811
1764
  this.location = location;
1812
1765
  this.restorationIdentifier = restorationIdentifier;
1813
1766
  }
@@ -1882,10 +1835,14 @@ class LinkClickObserver {
1882
1835
  }
1883
1836
  }
1884
1837
  getLocationForLink(link) {
1885
- return new Location(link.getAttribute("href") || "");
1838
+ return expandURL(link.getAttribute("href") || "");
1886
1839
  }
1887
1840
  }
1888
1841
 
1842
+ function isAction(action) {
1843
+ return action == "advance" || action == "replace" || action == "restore";
1844
+ }
1845
+
1889
1846
  class Navigator {
1890
1847
  constructor(delegate) {
1891
1848
  this.delegate = delegate;
@@ -1895,9 +1852,9 @@ class Navigator {
1895
1852
  this.delegate.visitProposedToLocation(location, options);
1896
1853
  }
1897
1854
  }
1898
- startVisit(location, restorationIdentifier, options = {}) {
1855
+ startVisit(locatable, restorationIdentifier, options = {}) {
1899
1856
  this.stop();
1900
- this.currentVisit = new Visit(this, Location.wrap(location), restorationIdentifier, Object.assign({
1857
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
1901
1858
  referrer: this.location
1902
1859
  }, options));
1903
1860
  this.currentVisit.start();
@@ -1905,7 +1862,13 @@ class Navigator {
1905
1862
  submitForm(form, submitter) {
1906
1863
  this.stop();
1907
1864
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1908
- this.formSubmission.start();
1865
+ if (this.formSubmission.isIdempotent) {
1866
+ this.proposeVisit(this.formSubmission.fetchRequest.url, {
1867
+ action: this.getActionForFormSubmission(this.formSubmission)
1868
+ });
1869
+ } else {
1870
+ this.formSubmission.start();
1871
+ }
1909
1872
  }
1910
1873
  stop() {
1911
1874
  if (this.formSubmission) {
@@ -1948,14 +1911,14 @@ class Navigator {
1948
1911
  async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
1949
1912
  const responseHTML = await fetchResponse.responseHTML;
1950
1913
  if (responseHTML) {
1951
- const snapshot = Snapshot.fromHTMLString(responseHTML);
1952
- this.view.render({
1953
- snapshot: snapshot
1954
- }, (() => {}));
1914
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
1915
+ await this.view.renderPage(snapshot);
1955
1916
  this.view.clearSnapshotCache();
1956
1917
  }
1957
1918
  }
1958
- formSubmissionErrored(formSubmission, error) {}
1919
+ formSubmissionErrored(formSubmission, error) {
1920
+ console.error(error);
1921
+ }
1959
1922
  formSubmissionFinished(formSubmission) {}
1960
1923
  visitStarted(visit) {
1961
1924
  this.delegate.visitStarted(visit);
@@ -1969,6 +1932,11 @@ class Navigator {
1969
1932
  get restorationIdentifier() {
1970
1933
  return this.history.restorationIdentifier;
1971
1934
  }
1935
+ getActionForFormSubmission(formSubmission) {
1936
+ const {formElement: formElement, submitter: submitter} = formSubmission;
1937
+ const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
1938
+ return isAction(action) ? action : "advance";
1939
+ }
1972
1940
  }
1973
1941
 
1974
1942
  var PageStage;
@@ -2061,51 +2029,10 @@ class ScrollObserver {
2061
2029
  }
2062
2030
  }
2063
2031
 
2064
- class StreamMessage {
2065
- constructor(html) {
2066
- this.templateElement = document.createElement("template");
2067
- this.templateElement.innerHTML = html;
2068
- }
2069
- static wrap(message) {
2070
- if (typeof message == "string") {
2071
- return new this(message);
2072
- } else {
2073
- return message;
2074
- }
2075
- }
2076
- get fragment() {
2077
- const fragment = document.createDocumentFragment();
2078
- for (const element of this.foreignElements) {
2079
- fragment.appendChild(document.importNode(element, true));
2080
- }
2081
- return fragment;
2082
- }
2083
- get foreignElements() {
2084
- return this.templateChildren.reduce(((streamElements, child) => {
2085
- if (child.tagName.toLowerCase() == "turbo-stream") {
2086
- return [ ...streamElements, child ];
2087
- } else {
2088
- return streamElements;
2089
- }
2090
- }), []);
2091
- }
2092
- get templateChildren() {
2093
- return Array.from(this.templateElement.content.children);
2094
- }
2095
- }
2096
-
2097
2032
  class StreamObserver {
2098
2033
  constructor(delegate) {
2099
2034
  this.sources = new Set;
2100
2035
  this.started = false;
2101
- this.prepareFetchRequest = event => {
2102
- var _a;
2103
- const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
2104
- if (fetchOptions) {
2105
- const {headers: headers} = fetchOptions;
2106
- headers.Accept = [ "text/vnd.turbo-stream.html", headers.Accept ].join(", ");
2107
- }
2108
- };
2109
2036
  this.inspectFetchResponse = event => {
2110
2037
  const response = fetchResponseFromEvent(event);
2111
2038
  if (response && fetchResponseIsStream(response)) {
@@ -2123,14 +2050,12 @@ class StreamObserver {
2123
2050
  start() {
2124
2051
  if (!this.started) {
2125
2052
  this.started = true;
2126
- addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2127
2053
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2128
2054
  }
2129
2055
  }
2130
2056
  stop() {
2131
2057
  if (this.started) {
2132
2058
  this.started = false;
2133
- removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2134
2059
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2135
2060
  }
2136
2061
  }
@@ -2171,70 +2096,21 @@ function fetchResponseFromEvent(event) {
2171
2096
  function fetchResponseIsStream(response) {
2172
2097
  var _a;
2173
2098
  const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2174
- return /^text\/vnd\.turbo-stream\.html\b/.test(contentType);
2175
- }
2176
-
2177
- function isAction(action) {
2178
- return action == "advance" || action == "replace" || action == "restore";
2179
- }
2180
-
2181
- class Renderer {
2182
- renderView(callback) {
2183
- this.delegate.viewWillRender(this.newBody);
2184
- callback();
2185
- this.delegate.viewRendered(this.newBody);
2186
- }
2187
- invalidateView() {
2188
- this.delegate.viewInvalidated();
2189
- }
2190
- createScriptElement(element) {
2191
- if (element.getAttribute("data-turbo-eval") == "false") {
2192
- return element;
2193
- } else {
2194
- const createdScriptElement = document.createElement("script");
2195
- createdScriptElement.textContent = element.textContent;
2196
- createdScriptElement.async = false;
2197
- copyElementAttributes(createdScriptElement, element);
2198
- return createdScriptElement;
2199
- }
2200
- }
2201
- }
2202
-
2203
- function copyElementAttributes(destinationElement, sourceElement) {
2204
- for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
2205
- destinationElement.setAttribute(name, value);
2206
- }
2099
+ return contentType.startsWith(StreamMessage.contentType);
2207
2100
  }
2208
2101
 
2209
2102
  class ErrorRenderer extends Renderer {
2210
- constructor(delegate, html) {
2211
- super();
2212
- this.delegate = delegate;
2213
- this.htmlElement = (() => {
2214
- const htmlElement = document.createElement("html");
2215
- htmlElement.innerHTML = html;
2216
- return htmlElement;
2217
- })();
2218
- this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
2219
- this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
2220
- }
2221
- static render(delegate, callback, html) {
2222
- return new this(delegate, html).render(callback);
2223
- }
2224
- render(callback) {
2225
- this.renderView((() => {
2226
- this.replaceHeadAndBody();
2227
- this.activateBodyScriptElements();
2228
- callback();
2229
- }));
2103
+ async render() {
2104
+ this.replaceHeadAndBody();
2105
+ this.activateScriptElements();
2230
2106
  }
2231
2107
  replaceHeadAndBody() {
2232
2108
  const {documentElement: documentElement, head: head, body: body} = document;
2233
2109
  documentElement.replaceChild(this.newHead, head);
2234
- documentElement.replaceChild(this.newBody, body);
2110
+ documentElement.replaceChild(this.newElement, body);
2235
2111
  }
2236
- activateBodyScriptElements() {
2237
- for (const replaceableElement of this.getScriptElements()) {
2112
+ activateScriptElements() {
2113
+ for (const replaceableElement of this.scriptElements) {
2238
2114
  const parentNode = replaceableElement.parentNode;
2239
2115
  if (parentNode) {
2240
2116
  const element = this.createScriptElement(replaceableElement);
@@ -2242,82 +2118,38 @@ class ErrorRenderer extends Renderer {
2242
2118
  }
2243
2119
  }
2244
2120
  }
2245
- getScriptElements() {
2121
+ get newHead() {
2122
+ return this.newSnapshot.headSnapshot.element;
2123
+ }
2124
+ get scriptElements() {
2246
2125
  return [ ...document.documentElement.querySelectorAll("script") ];
2247
2126
  }
2248
2127
  }
2249
2128
 
2250
- class SnapshotCache {
2251
- constructor(size) {
2252
- this.keys = [];
2253
- this.snapshots = {};
2254
- this.size = size;
2255
- }
2256
- has(location) {
2257
- return location.toCacheKey() in this.snapshots;
2129
+ class PageRenderer extends Renderer {
2130
+ get shouldRender() {
2131
+ return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2258
2132
  }
2259
- get(location) {
2260
- if (this.has(location)) {
2261
- const snapshot = this.read(location);
2262
- this.touch(location);
2263
- return snapshot;
2264
- }
2133
+ prepareToRender() {
2134
+ this.mergeHead();
2265
2135
  }
2266
- put(location, snapshot) {
2267
- this.write(location, snapshot);
2268
- this.touch(location);
2269
- return snapshot;
2270
- }
2271
- clear() {
2272
- this.snapshots = {};
2273
- }
2274
- read(location) {
2275
- return this.snapshots[location.toCacheKey()];
2276
- }
2277
- write(location, snapshot) {
2278
- this.snapshots[location.toCacheKey()] = snapshot;
2279
- }
2280
- touch(location) {
2281
- const key = location.toCacheKey();
2282
- const index = this.keys.indexOf(key);
2283
- if (index > -1) this.keys.splice(index, 1);
2284
- this.keys.unshift(key);
2285
- this.trim();
2136
+ async render() {
2137
+ this.replaceBody();
2286
2138
  }
2287
- trim() {
2288
- for (const key of this.keys.splice(this.size)) {
2289
- delete this.snapshots[key];
2139
+ finishRendering() {
2140
+ super.finishRendering();
2141
+ if (!this.isPreview) {
2142
+ this.focusFirstAutofocusableElement();
2290
2143
  }
2291
2144
  }
2292
- }
2293
-
2294
- class SnapshotRenderer extends Renderer {
2295
- constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
2296
- super();
2297
- this.delegate = delegate;
2298
- this.currentSnapshot = currentSnapshot;
2299
- this.currentHeadDetails = currentSnapshot.headDetails;
2300
- this.newSnapshot = newSnapshot;
2301
- this.newHeadDetails = newSnapshot.headDetails;
2302
- this.newBody = newSnapshot.bodyElement;
2303
- this.isPreview = isPreview;
2145
+ get currentHeadSnapshot() {
2146
+ return this.currentSnapshot.headSnapshot;
2304
2147
  }
2305
- static render(delegate, callback, currentSnapshot, newSnapshot, isPreview) {
2306
- return new this(delegate, currentSnapshot, newSnapshot, isPreview).render(callback);
2148
+ get newHeadSnapshot() {
2149
+ return this.newSnapshot.headSnapshot;
2307
2150
  }
2308
- render(callback) {
2309
- if (this.shouldRender()) {
2310
- this.mergeHead();
2311
- this.renderView((() => {
2312
- this.replaceBody();
2313
- if (!this.isPreview) {
2314
- this.focusFirstAutofocusableElement();
2315
- }
2316
- callback();
2317
- }));
2318
- } else {
2319
- this.invalidateView();
2320
- }
2151
+ get newElement() {
2152
+ return this.newSnapshot.element;
2321
2153
  }
2322
2154
  mergeHead() {
2323
2155
  this.copyNewHeadStylesheetElements();
@@ -2326,190 +2158,145 @@ class SnapshotRenderer extends Renderer {
2326
2158
  this.copyNewHeadProvisionalElements();
2327
2159
  }
2328
2160
  replaceBody() {
2329
- const placeholders = this.relocateCurrentBodyPermanentElements();
2330
- this.activateNewBody();
2331
- this.assignNewBody();
2332
- this.replacePlaceholderElementsWithClonedPermanentElements(placeholders);
2333
- }
2334
- shouldRender() {
2335
- return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
2161
+ this.preservingPermanentElements((() => {
2162
+ this.activateNewBody();
2163
+ this.assignNewBody();
2164
+ }));
2336
2165
  }
2337
- trackedElementsAreIdentical() {
2338
- return this.currentHeadDetails.getTrackedElementSignature() == this.newHeadDetails.getTrackedElementSignature();
2166
+ get trackedElementsAreIdentical() {
2167
+ return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2339
2168
  }
2340
2169
  copyNewHeadStylesheetElements() {
2341
- for (const element of this.getNewHeadStylesheetElements()) {
2170
+ for (const element of this.newHeadStylesheetElements) {
2342
2171
  document.head.appendChild(element);
2343
2172
  }
2344
2173
  }
2345
2174
  copyNewHeadScriptElements() {
2346
- for (const element of this.getNewHeadScriptElements()) {
2175
+ for (const element of this.newHeadScriptElements) {
2347
2176
  document.head.appendChild(this.createScriptElement(element));
2348
2177
  }
2349
2178
  }
2350
2179
  removeCurrentHeadProvisionalElements() {
2351
- for (const element of this.getCurrentHeadProvisionalElements()) {
2180
+ for (const element of this.currentHeadProvisionalElements) {
2352
2181
  document.head.removeChild(element);
2353
2182
  }
2354
2183
  }
2355
2184
  copyNewHeadProvisionalElements() {
2356
- for (const element of this.getNewHeadProvisionalElements()) {
2185
+ for (const element of this.newHeadProvisionalElements) {
2357
2186
  document.head.appendChild(element);
2358
2187
  }
2359
2188
  }
2360
- relocateCurrentBodyPermanentElements() {
2361
- return this.getCurrentBodyPermanentElements().reduce(((placeholders, permanentElement) => {
2362
- const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
2363
- if (newElement) {
2364
- const placeholder = createPlaceholderForPermanentElement(permanentElement);
2365
- replaceElementWithElement(permanentElement, placeholder.element);
2366
- replaceElementWithElement(newElement, permanentElement);
2367
- return [ ...placeholders, placeholder ];
2368
- } else {
2369
- return placeholders;
2370
- }
2371
- }), []);
2372
- }
2373
- replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
2374
- for (const {element: element, permanentElement: permanentElement} of placeholders) {
2375
- const clonedElement = permanentElement.cloneNode(true);
2376
- replaceElementWithElement(element, clonedElement);
2377
- }
2378
- }
2379
2189
  activateNewBody() {
2380
- document.adoptNode(this.newBody);
2190
+ document.adoptNode(this.newElement);
2381
2191
  this.activateNewBodyScriptElements();
2382
2192
  }
2383
2193
  activateNewBodyScriptElements() {
2384
- for (const inertScriptElement of this.getNewBodyScriptElements()) {
2194
+ for (const inertScriptElement of this.newBodyScriptElements) {
2385
2195
  const activatedScriptElement = this.createScriptElement(inertScriptElement);
2386
- replaceElementWithElement(inertScriptElement, activatedScriptElement);
2196
+ inertScriptElement.replaceWith(activatedScriptElement);
2387
2197
  }
2388
2198
  }
2389
2199
  assignNewBody() {
2390
- if (document.body) {
2391
- replaceElementWithElement(document.body, this.newBody);
2200
+ if (document.body && this.newElement instanceof HTMLBodyElement) {
2201
+ document.body.replaceWith(this.newElement);
2392
2202
  } else {
2393
- document.documentElement.appendChild(this.newBody);
2394
- }
2395
- }
2396
- focusFirstAutofocusableElement() {
2397
- const element = this.newSnapshot.findFirstAutofocusableElement();
2398
- if (elementIsFocusable(element)) {
2399
- element.focus();
2203
+ document.documentElement.appendChild(this.newElement);
2400
2204
  }
2401
2205
  }
2402
- getNewHeadStylesheetElements() {
2403
- return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails);
2206
+ get newHeadStylesheetElements() {
2207
+ return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2404
2208
  }
2405
- getNewHeadScriptElements() {
2406
- return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails);
2209
+ get newHeadScriptElements() {
2210
+ return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
2407
2211
  }
2408
- getCurrentHeadProvisionalElements() {
2409
- return this.currentHeadDetails.getProvisionalElements();
2212
+ get currentHeadProvisionalElements() {
2213
+ return this.currentHeadSnapshot.provisionalElements;
2410
2214
  }
2411
- getNewHeadProvisionalElements() {
2412
- return this.newHeadDetails.getProvisionalElements();
2215
+ get newHeadProvisionalElements() {
2216
+ return this.newHeadSnapshot.provisionalElements;
2413
2217
  }
2414
- getCurrentBodyPermanentElements() {
2415
- return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot);
2416
- }
2417
- getNewBodyScriptElements() {
2418
- return [ ...this.newBody.querySelectorAll("script") ];
2218
+ get newBodyScriptElements() {
2219
+ return [ ...this.newElement.querySelectorAll("script") ];
2419
2220
  }
2420
2221
  }
2421
2222
 
2422
- function createPlaceholderForPermanentElement(permanentElement) {
2423
- const element = document.createElement("meta");
2424
- element.setAttribute("name", "turbo-permanent-placeholder");
2425
- element.setAttribute("content", permanentElement.id);
2426
- return {
2427
- element: element,
2428
- permanentElement: permanentElement
2429
- };
2430
- }
2431
-
2432
- function replaceElementWithElement(fromElement, toElement) {
2433
- const parentElement = fromElement.parentElement;
2434
- if (parentElement) {
2435
- return parentElement.replaceChild(toElement, fromElement);
2223
+ class SnapshotCache {
2224
+ constructor(size) {
2225
+ this.keys = [];
2226
+ this.snapshots = {};
2227
+ this.size = size;
2228
+ }
2229
+ has(location) {
2230
+ return toCacheKey(location) in this.snapshots;
2231
+ }
2232
+ get(location) {
2233
+ if (this.has(location)) {
2234
+ const snapshot = this.read(location);
2235
+ this.touch(location);
2236
+ return snapshot;
2237
+ }
2238
+ }
2239
+ put(location, snapshot) {
2240
+ this.write(location, snapshot);
2241
+ this.touch(location);
2242
+ return snapshot;
2243
+ }
2244
+ clear() {
2245
+ this.snapshots = {};
2246
+ }
2247
+ read(location) {
2248
+ return this.snapshots[toCacheKey(location)];
2249
+ }
2250
+ write(location, snapshot) {
2251
+ this.snapshots[toCacheKey(location)] = snapshot;
2252
+ }
2253
+ touch(location) {
2254
+ const key = toCacheKey(location);
2255
+ const index = this.keys.indexOf(key);
2256
+ if (index > -1) this.keys.splice(index, 1);
2257
+ this.keys.unshift(key);
2258
+ this.trim();
2259
+ }
2260
+ trim() {
2261
+ for (const key of this.keys.splice(this.size)) {
2262
+ delete this.snapshots[key];
2263
+ }
2436
2264
  }
2437
2265
  }
2438
2266
 
2439
- function elementIsFocusable(element) {
2440
- return element && typeof element.focus == "function";
2441
- }
2442
-
2443
- class View {
2444
- constructor(delegate) {
2445
- this.htmlElement = document.documentElement;
2267
+ class PageView extends View {
2268
+ constructor() {
2269
+ super(...arguments);
2446
2270
  this.snapshotCache = new SnapshotCache(10);
2447
- this.delegate = delegate;
2448
- }
2449
- getRootLocation() {
2450
- return this.getSnapshot().getRootLocation();
2271
+ this.lastRenderedLocation = new URL(location.href);
2451
2272
  }
2452
- getElementForAnchor(anchor) {
2453
- return this.getSnapshot().getElementForAnchor(anchor);
2273
+ renderPage(snapshot, isPreview = false) {
2274
+ const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2275
+ return this.render(renderer);
2454
2276
  }
2455
- getSnapshot() {
2456
- return Snapshot.fromHTMLElement(this.htmlElement);
2277
+ renderError(snapshot) {
2278
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2279
+ this.render(renderer);
2457
2280
  }
2458
2281
  clearSnapshotCache() {
2459
2282
  this.snapshotCache.clear();
2460
2283
  }
2461
- shouldCacheSnapshot() {
2462
- return this.getSnapshot().isCacheable();
2463
- }
2464
2284
  async cacheSnapshot() {
2465
- if (this.shouldCacheSnapshot()) {
2285
+ if (this.shouldCacheSnapshot) {
2466
2286
  this.delegate.viewWillCacheSnapshot();
2467
- const snapshot = this.getSnapshot();
2468
- const location = this.lastRenderedLocation || Location.currentLocation;
2469
- await nextMicrotask();
2287
+ const {snapshot: snapshot, lastRenderedLocation: location} = this;
2288
+ await nextEventLoopTick();
2470
2289
  this.snapshotCache.put(location, snapshot.clone());
2471
2290
  }
2472
2291
  }
2473
2292
  getCachedSnapshotForLocation(location) {
2474
2293
  return this.snapshotCache.get(location);
2475
2294
  }
2476
- render({snapshot: snapshot, error: error, isPreview: isPreview}, callback) {
2477
- this.markAsPreview(isPreview);
2478
- if (snapshot) {
2479
- this.renderSnapshot(snapshot, isPreview, callback);
2480
- } else {
2481
- this.renderError(error, callback);
2482
- }
2483
- }
2484
- scrollToAnchor(anchor) {
2485
- const element = this.getElementForAnchor(anchor);
2486
- if (element) {
2487
- this.scrollToElement(element);
2488
- } else {
2489
- this.scrollToPosition({
2490
- x: 0,
2491
- y: 0
2492
- });
2493
- }
2494
- }
2495
- scrollToElement(element) {
2496
- element.scrollIntoView();
2295
+ get snapshot() {
2296
+ return PageSnapshot.fromElement(this.element);
2497
2297
  }
2498
- scrollToPosition({x: x, y: y}) {
2499
- window.scrollTo(x, y);
2500
- }
2501
- markAsPreview(isPreview) {
2502
- if (isPreview) {
2503
- this.htmlElement.setAttribute("data-turbo-preview", "");
2504
- } else {
2505
- this.htmlElement.removeAttribute("data-turbo-preview");
2506
- }
2507
- }
2508
- renderSnapshot(snapshot, isPreview, callback) {
2509
- SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
2510
- }
2511
- renderError(error, callback) {
2512
- ErrorRenderer.render(this.delegate, callback, error || "");
2298
+ get shouldCacheSnapshot() {
2299
+ return this.snapshot.isCacheable;
2513
2300
  }
2514
2301
  }
2515
2302
 
@@ -2517,7 +2304,7 @@ class Session {
2517
2304
  constructor() {
2518
2305
  this.navigator = new Navigator(this);
2519
2306
  this.history = new History(this);
2520
- this.view = new View(this);
2307
+ this.view = new PageView(this, document.documentElement);
2521
2308
  this.adapter = new BrowserAdapter(this);
2522
2309
  this.pageObserver = new PageObserver(this);
2523
2310
  this.linkClickObserver = new LinkClickObserver(this);
@@ -2561,7 +2348,7 @@ class Session {
2561
2348
  this.adapter = adapter;
2562
2349
  }
2563
2350
  visit(location, options = {}) {
2564
- this.navigator.proposeVisit(Location.wrap(location), options);
2351
+ this.navigator.proposeVisit(expandURL(location), options);
2565
2352
  }
2566
2353
  connectStreamSource(source) {
2567
2354
  this.streamObserver.connectStreamSource(source);
@@ -2599,128 +2386,553 @@ class Session {
2599
2386
  scrollPosition: position
2600
2387
  });
2601
2388
  }
2602
- willFollowLinkToLocation(link, location) {
2603
- return this.elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
2389
+ willFollowLinkToLocation(link, location) {
2390
+ return elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
2391
+ }
2392
+ followedLinkToLocation(link, location) {
2393
+ const action = this.getActionForLink(link);
2394
+ this.visit(location.href, {
2395
+ action: action
2396
+ });
2397
+ }
2398
+ allowsVisitingLocation(location) {
2399
+ return this.applicationAllowsVisitingLocation(location);
2400
+ }
2401
+ visitProposedToLocation(location, options) {
2402
+ extendURLWithDeprecatedProperties(location);
2403
+ this.adapter.visitProposedToLocation(location, options);
2404
+ }
2405
+ visitStarted(visit) {
2406
+ extendURLWithDeprecatedProperties(visit.location);
2407
+ this.notifyApplicationAfterVisitingLocation(visit.location);
2408
+ }
2409
+ visitCompleted(visit) {
2410
+ this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2411
+ }
2412
+ willSubmitForm(form, submitter) {
2413
+ return elementIsNavigable(form) && elementIsNavigable(submitter);
2414
+ }
2415
+ formSubmitted(form, submitter) {
2416
+ this.navigator.submitForm(form, submitter);
2417
+ }
2418
+ pageBecameInteractive() {
2419
+ this.view.lastRenderedLocation = this.location;
2420
+ this.notifyApplicationAfterPageLoad();
2421
+ }
2422
+ pageLoaded() {
2423
+ this.history.assumeControlOfScrollRestoration();
2424
+ }
2425
+ pageWillUnload() {
2426
+ this.history.relinquishControlOfScrollRestoration();
2427
+ }
2428
+ receivedMessageFromStream(message) {
2429
+ this.renderStreamMessage(message);
2430
+ }
2431
+ viewWillCacheSnapshot() {
2432
+ this.notifyApplicationBeforeCachingSnapshot();
2433
+ }
2434
+ viewWillRenderSnapshot({element: element}, isPreview) {
2435
+ this.notifyApplicationBeforeRender(element);
2436
+ }
2437
+ viewRenderedSnapshot(snapshot, isPreview) {
2438
+ this.view.lastRenderedLocation = this.history.location;
2439
+ this.notifyApplicationAfterRender();
2440
+ }
2441
+ viewInvalidated() {
2442
+ this.adapter.pageInvalidated();
2443
+ }
2444
+ applicationAllowsFollowingLinkToLocation(link, location) {
2445
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2446
+ return !event.defaultPrevented;
2447
+ }
2448
+ applicationAllowsVisitingLocation(location) {
2449
+ const event = this.notifyApplicationBeforeVisitingLocation(location);
2450
+ return !event.defaultPrevented;
2451
+ }
2452
+ notifyApplicationAfterClickingLinkToLocation(link, location) {
2453
+ return dispatch("turbo:click", {
2454
+ target: link,
2455
+ detail: {
2456
+ url: location.href
2457
+ },
2458
+ cancelable: true
2459
+ });
2460
+ }
2461
+ notifyApplicationBeforeVisitingLocation(location) {
2462
+ return dispatch("turbo:before-visit", {
2463
+ detail: {
2464
+ url: location.href
2465
+ },
2466
+ cancelable: true
2467
+ });
2468
+ }
2469
+ notifyApplicationAfterVisitingLocation(location) {
2470
+ return dispatch("turbo:visit", {
2471
+ detail: {
2472
+ url: location.href
2473
+ }
2474
+ });
2475
+ }
2476
+ notifyApplicationBeforeCachingSnapshot() {
2477
+ return dispatch("turbo:before-cache");
2478
+ }
2479
+ notifyApplicationBeforeRender(newBody) {
2480
+ return dispatch("turbo:before-render", {
2481
+ detail: {
2482
+ newBody: newBody
2483
+ }
2484
+ });
2485
+ }
2486
+ notifyApplicationAfterRender() {
2487
+ return dispatch("turbo:render");
2488
+ }
2489
+ notifyApplicationAfterPageLoad(timing = {}) {
2490
+ return dispatch("turbo:load", {
2491
+ detail: {
2492
+ url: this.location.href,
2493
+ timing: timing
2494
+ }
2495
+ });
2496
+ }
2497
+ getActionForLink(link) {
2498
+ const action = link.getAttribute("data-turbo-action");
2499
+ return isAction(action) ? action : "advance";
2500
+ }
2501
+ locationIsVisitable(location) {
2502
+ return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2503
+ }
2504
+ get snapshot() {
2505
+ return this.view.snapshot;
2506
+ }
2507
+ }
2508
+
2509
+ function elementIsNavigable(element) {
2510
+ const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2511
+ if (container) {
2512
+ return container.getAttribute("data-turbo") != "false";
2513
+ } else {
2514
+ return true;
2515
+ }
2516
+ }
2517
+
2518
+ function extendURLWithDeprecatedProperties(url) {
2519
+ Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2520
+ }
2521
+
2522
+ const deprecatedLocationPropertyDescriptors = {
2523
+ absoluteURL: {
2524
+ get() {
2525
+ return this.toString();
2526
+ }
2527
+ }
2528
+ };
2529
+
2530
+ class FrameController {
2531
+ constructor(element) {
2532
+ this.resolveVisitPromise = () => {};
2533
+ this.connected = false;
2534
+ this.hasBeenLoaded = false;
2535
+ this.settingSourceURL = false;
2536
+ this.element = element;
2537
+ this.view = new FrameView(this, this.element);
2538
+ this.appearanceObserver = new AppearanceObserver(this, this.element);
2539
+ this.linkInterceptor = new LinkInterceptor(this, this.element);
2540
+ this.formInterceptor = new FormInterceptor(this, this.element);
2541
+ }
2542
+ connect() {
2543
+ if (!this.connected) {
2544
+ this.connected = true;
2545
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
2546
+ this.appearanceObserver.start();
2547
+ }
2548
+ this.linkInterceptor.start();
2549
+ this.formInterceptor.start();
2550
+ this.sourceURLChanged();
2551
+ }
2552
+ }
2553
+ disconnect() {
2554
+ if (this.connected) {
2555
+ this.connected = false;
2556
+ this.appearanceObserver.stop();
2557
+ this.linkInterceptor.stop();
2558
+ this.formInterceptor.stop();
2559
+ }
2560
+ }
2561
+ disabledChanged() {
2562
+ if (this.loadingStyle == FrameLoadingStyle.eager) {
2563
+ this.loadSourceURL();
2564
+ }
2565
+ }
2566
+ sourceURLChanged() {
2567
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2568
+ this.loadSourceURL();
2569
+ }
2570
+ }
2571
+ loadingStyleChanged() {
2572
+ if (this.loadingStyle == FrameLoadingStyle.lazy) {
2573
+ this.appearanceObserver.start();
2574
+ } else {
2575
+ this.appearanceObserver.stop();
2576
+ this.loadSourceURL();
2577
+ }
2578
+ }
2579
+ async loadSourceURL() {
2580
+ if (!this.settingSourceURL && this.enabled && this.isActive && this.sourceURL != this.currentURL) {
2581
+ const previousURL = this.currentURL;
2582
+ this.currentURL = this.sourceURL;
2583
+ if (this.sourceURL) {
2584
+ try {
2585
+ this.element.loaded = this.visit(this.sourceURL);
2586
+ this.appearanceObserver.stop();
2587
+ await this.element.loaded;
2588
+ this.hasBeenLoaded = true;
2589
+ } catch (error) {
2590
+ this.currentURL = previousURL;
2591
+ throw error;
2592
+ }
2593
+ }
2594
+ }
2595
+ }
2596
+ async loadResponse(fetchResponse) {
2597
+ if (fetchResponse.redirected) {
2598
+ this.sourceURL = fetchResponse.response.url;
2599
+ }
2600
+ try {
2601
+ const html = await fetchResponse.responseHTML;
2602
+ if (html) {
2603
+ const {body: body} = parseHTMLDocument(html);
2604
+ const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2605
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
2606
+ await this.view.render(renderer);
2607
+ }
2608
+ } catch (error) {
2609
+ console.error(error);
2610
+ this.view.invalidate();
2611
+ }
2612
+ }
2613
+ elementAppearedInViewport(element) {
2614
+ this.loadSourceURL();
2615
+ }
2616
+ shouldInterceptLinkClick(element, url) {
2617
+ return this.shouldInterceptNavigation(element);
2618
+ }
2619
+ linkClickIntercepted(element, url) {
2620
+ this.navigateFrame(element, url);
2621
+ }
2622
+ shouldInterceptFormSubmission(element, submitter) {
2623
+ return this.shouldInterceptNavigation(element, submitter);
2624
+ }
2625
+ formSubmissionIntercepted(element, submitter) {
2626
+ if (this.formSubmission) {
2627
+ this.formSubmission.stop();
2628
+ }
2629
+ this.formSubmission = new FormSubmission(this, element, submitter);
2630
+ if (this.formSubmission.fetchRequest.isIdempotent) {
2631
+ this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
2632
+ } else {
2633
+ const {fetchRequest: fetchRequest} = this.formSubmission;
2634
+ this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2635
+ this.formSubmission.start();
2636
+ }
2637
+ }
2638
+ prepareHeadersForRequest(headers, request) {
2639
+ headers["Turbo-Frame"] = this.id;
2640
+ }
2641
+ requestStarted(request) {
2642
+ this.element.setAttribute("busy", "");
2643
+ }
2644
+ requestPreventedHandlingResponse(request, response) {
2645
+ this.resolveVisitPromise();
2646
+ }
2647
+ async requestSucceededWithResponse(request, response) {
2648
+ await this.loadResponse(response);
2649
+ this.resolveVisitPromise();
2650
+ }
2651
+ requestFailedWithResponse(request, response) {
2652
+ console.error(response);
2653
+ this.resolveVisitPromise();
2654
+ }
2655
+ requestErrored(request, error) {
2656
+ console.error(error);
2657
+ this.resolveVisitPromise();
2658
+ }
2659
+ requestFinished(request) {
2660
+ this.element.removeAttribute("busy");
2661
+ }
2662
+ formSubmissionStarted(formSubmission) {
2663
+ const frame = this.findFrameElement(formSubmission.formElement);
2664
+ frame.setAttribute("busy", "");
2665
+ }
2666
+ formSubmissionSucceededWithResponse(formSubmission, response) {
2667
+ const frame = this.findFrameElement(formSubmission.formElement);
2668
+ frame.delegate.loadResponse(response);
2669
+ }
2670
+ formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
2671
+ this.element.delegate.loadResponse(fetchResponse);
2672
+ }
2673
+ formSubmissionErrored(formSubmission, error) {
2674
+ console.error(error);
2675
+ }
2676
+ formSubmissionFinished(formSubmission) {
2677
+ const frame = this.findFrameElement(formSubmission.formElement);
2678
+ frame.removeAttribute("busy");
2679
+ }
2680
+ viewWillRenderSnapshot(snapshot, isPreview) {}
2681
+ viewRenderedSnapshot(snapshot, isPreview) {}
2682
+ viewInvalidated() {}
2683
+ async visit(url) {
2684
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
2685
+ return new Promise((resolve => {
2686
+ this.resolveVisitPromise = () => {
2687
+ this.resolveVisitPromise = () => {};
2688
+ resolve();
2689
+ };
2690
+ request.perform();
2691
+ }));
2692
+ }
2693
+ navigateFrame(element, url) {
2694
+ const frame = this.findFrameElement(element);
2695
+ frame.src = url;
2696
+ }
2697
+ findFrameElement(element) {
2698
+ var _a;
2699
+ const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
2700
+ return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
2701
+ }
2702
+ async extractForeignFrameElement(container) {
2703
+ let element;
2704
+ const id = CSS.escape(this.id);
2705
+ try {
2706
+ if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
2707
+ return element;
2708
+ }
2709
+ if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
2710
+ await element.loaded;
2711
+ return await this.extractForeignFrameElement(element);
2712
+ }
2713
+ console.error(`Response has no matching <turbo-frame id="${id}"> element`);
2714
+ } catch (error) {
2715
+ console.error(error);
2716
+ }
2717
+ return new FrameElement;
2718
+ }
2719
+ shouldInterceptNavigation(element, submitter) {
2720
+ const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
2721
+ if (!this.enabled || id == "_top") {
2722
+ return false;
2723
+ }
2724
+ if (id) {
2725
+ const frameElement = getFrameElementById(id);
2726
+ if (frameElement) {
2727
+ return !frameElement.disabled;
2728
+ }
2729
+ }
2730
+ if (!elementIsNavigable(element)) {
2731
+ return false;
2732
+ }
2733
+ if (submitter && !elementIsNavigable(submitter)) {
2734
+ return false;
2735
+ }
2736
+ return true;
2604
2737
  }
2605
- followedLinkToLocation(link, location) {
2606
- const action = this.getActionForLink(link);
2607
- this.visit(location, {
2608
- action: action
2609
- });
2738
+ get id() {
2739
+ return this.element.id;
2610
2740
  }
2611
- allowsVisitingLocation(location) {
2612
- return this.applicationAllowsVisitingLocation(location);
2741
+ get enabled() {
2742
+ return !this.element.disabled;
2613
2743
  }
2614
- visitProposedToLocation(location, options) {
2615
- this.adapter.visitProposedToLocation(location, options);
2744
+ get sourceURL() {
2745
+ if (this.element.src) {
2746
+ return this.element.src;
2747
+ }
2616
2748
  }
2617
- visitStarted(visit) {
2618
- this.notifyApplicationAfterVisitingLocation(visit.location);
2749
+ set sourceURL(sourceURL) {
2750
+ this.settingSourceURL = true;
2751
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
2752
+ this.settingSourceURL = false;
2619
2753
  }
2620
- visitCompleted(visit) {
2621
- this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2754
+ get loadingStyle() {
2755
+ return this.element.loading;
2622
2756
  }
2623
- willSubmitForm(form, submitter) {
2624
- return this.elementIsNavigable(form) && this.elementIsNavigable(submitter);
2757
+ get isLoading() {
2758
+ return this.formSubmission !== undefined || this.resolveVisitPromise !== undefined;
2625
2759
  }
2626
- formSubmitted(form, submitter) {
2627
- this.navigator.submitForm(form, submitter);
2760
+ get isActive() {
2761
+ return this.element.isActive && this.connected;
2628
2762
  }
2629
- pageBecameInteractive() {
2630
- this.view.lastRenderedLocation = this.location;
2631
- this.notifyApplicationAfterPageLoad();
2763
+ }
2764
+
2765
+ function getFrameElementById(id) {
2766
+ if (id != null) {
2767
+ const element = document.getElementById(id);
2768
+ if (element instanceof FrameElement) {
2769
+ return element;
2770
+ }
2632
2771
  }
2633
- pageLoaded() {
2634
- this.history.assumeControlOfScrollRestoration();
2772
+ }
2773
+
2774
+ function activateElement(element, currentURL) {
2775
+ if (element) {
2776
+ const src = element.getAttribute("src");
2777
+ if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
2778
+ throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
2779
+ }
2780
+ if (element.ownerDocument !== document) {
2781
+ element = document.importNode(element, true);
2782
+ }
2783
+ if (element instanceof FrameElement) {
2784
+ element.connectedCallback();
2785
+ return element;
2786
+ }
2635
2787
  }
2636
- pageWillUnload() {
2637
- this.history.relinquishControlOfScrollRestoration();
2788
+ }
2789
+
2790
+ const StreamActions = {
2791
+ after() {
2792
+ var _a, _b;
2793
+ (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement.nextSibling);
2794
+ },
2795
+ append() {
2796
+ var _a;
2797
+ this.removeDuplicateTargetChildren();
2798
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
2799
+ },
2800
+ before() {
2801
+ var _a, _b;
2802
+ (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
2803
+ },
2804
+ prepend() {
2805
+ var _a;
2806
+ this.removeDuplicateTargetChildren();
2807
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
2808
+ },
2809
+ remove() {
2810
+ var _a;
2811
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
2812
+ },
2813
+ replace() {
2814
+ var _a;
2815
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
2816
+ },
2817
+ update() {
2818
+ if (this.targetElement) {
2819
+ this.targetElement.innerHTML = "";
2820
+ this.targetElement.append(this.templateContent);
2821
+ }
2638
2822
  }
2639
- receivedMessageFromStream(message) {
2640
- this.renderStreamMessage(message);
2823
+ };
2824
+
2825
+ class StreamElement extends HTMLElement {
2826
+ async connectedCallback() {
2827
+ try {
2828
+ await this.render();
2829
+ } catch (error) {
2830
+ console.error(error);
2831
+ } finally {
2832
+ this.disconnect();
2833
+ }
2641
2834
  }
2642
- viewWillRender(newBody) {
2643
- this.notifyApplicationBeforeRender(newBody);
2835
+ async render() {
2836
+ var _a;
2837
+ return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
2838
+ if (this.dispatchEvent(this.beforeRenderEvent)) {
2839
+ await nextAnimationFrame();
2840
+ this.performAction();
2841
+ }
2842
+ })();
2644
2843
  }
2645
- viewRendered() {
2646
- this.view.lastRenderedLocation = this.history.location;
2647
- this.notifyApplicationAfterRender();
2844
+ disconnect() {
2845
+ try {
2846
+ this.remove();
2847
+ } catch (_a) {}
2648
2848
  }
2649
- viewInvalidated() {
2650
- this.adapter.pageInvalidated();
2849
+ removeDuplicateTargetChildren() {
2850
+ this.duplicateChildren.forEach((({targetChild: targetChild}) => {
2851
+ targetChild.remove();
2852
+ }));
2651
2853
  }
2652
- viewWillCacheSnapshot() {
2653
- this.notifyApplicationBeforeCachingSnapshot();
2854
+ get duplicateChildren() {
2855
+ var _a;
2856
+ return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
2857
+ let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
2858
+ return {
2859
+ targetChild: targetChild,
2860
+ templateChild: templateChild
2861
+ };
2862
+ })).filter((({targetChild: targetChild}) => targetChild));
2654
2863
  }
2655
- applicationAllowsFollowingLinkToLocation(link, location) {
2656
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2657
- return !event.defaultPrevented;
2864
+ get performAction() {
2865
+ if (this.action) {
2866
+ const actionFunction = StreamActions[this.action];
2867
+ if (actionFunction) {
2868
+ return actionFunction;
2869
+ }
2870
+ this.raise("unknown action");
2871
+ }
2872
+ this.raise("action attribute is missing");
2658
2873
  }
2659
- applicationAllowsVisitingLocation(location) {
2660
- const event = this.notifyApplicationBeforeVisitingLocation(location);
2661
- return !event.defaultPrevented;
2874
+ get targetElement() {
2875
+ var _a;
2876
+ if (this.target) {
2877
+ return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
2878
+ }
2879
+ this.raise("target attribute is missing");
2662
2880
  }
2663
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2664
- return dispatch("turbo:click", {
2665
- target: link,
2666
- detail: {
2667
- url: location.absoluteURL
2668
- },
2669
- cancelable: true
2670
- });
2881
+ get templateContent() {
2882
+ return this.templateElement.content;
2671
2883
  }
2672
- notifyApplicationBeforeVisitingLocation(location) {
2673
- return dispatch("turbo:before-visit", {
2674
- detail: {
2675
- url: location.absoluteURL
2676
- },
2677
- cancelable: true
2678
- });
2884
+ get templateElement() {
2885
+ if (this.firstElementChild instanceof HTMLTemplateElement) {
2886
+ return this.firstElementChild;
2887
+ }
2888
+ this.raise("first child element must be a <template> element");
2679
2889
  }
2680
- notifyApplicationAfterVisitingLocation(location) {
2681
- return dispatch("turbo:visit", {
2682
- detail: {
2683
- url: location.absoluteURL
2684
- }
2685
- });
2890
+ get action() {
2891
+ return this.getAttribute("action");
2686
2892
  }
2687
- notifyApplicationBeforeCachingSnapshot() {
2688
- return dispatch("turbo:before-cache");
2893
+ get target() {
2894
+ return this.getAttribute("target");
2689
2895
  }
2690
- notifyApplicationBeforeRender(newBody) {
2691
- return dispatch("turbo:before-render", {
2692
- detail: {
2693
- newBody: newBody
2694
- }
2695
- });
2896
+ raise(message) {
2897
+ throw new Error(`${this.description}: ${message}`);
2696
2898
  }
2697
- notifyApplicationAfterRender() {
2698
- return dispatch("turbo:render");
2899
+ get description() {
2900
+ var _a, _b;
2901
+ return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
2699
2902
  }
2700
- notifyApplicationAfterPageLoad(timing = {}) {
2701
- return dispatch("turbo:load", {
2702
- detail: {
2703
- url: this.location.absoluteURL,
2704
- timing: timing
2705
- }
2903
+ get beforeRenderEvent() {
2904
+ return new CustomEvent("turbo:before-stream-render", {
2905
+ bubbles: true,
2906
+ cancelable: true
2706
2907
  });
2707
2908
  }
2708
- getActionForLink(link) {
2709
- const action = link.getAttribute("data-turbo-action");
2710
- return isAction(action) ? action : "advance";
2711
- }
2712
- elementIsNavigable(element) {
2713
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2714
- if (container) {
2715
- return container.getAttribute("data-turbo") != "false";
2716
- } else {
2717
- return true;
2909
+ }
2910
+
2911
+ FrameElement.delegateConstructor = FrameController;
2912
+
2913
+ customElements.define("turbo-frame", FrameElement);
2914
+
2915
+ customElements.define("turbo-stream", StreamElement);
2916
+
2917
+ (() => {
2918
+ let element = document.currentScript;
2919
+ if (!element) return;
2920
+ if (element.hasAttribute("data-turbo-suppress-warning")) return;
2921
+ while (element = element.parentElement) {
2922
+ if (element == document.body) {
2923
+ return console.warn(unindent`
2924
+ You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
2925
+
2926
+ Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
2927
+
2928
+ For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
2929
+
2930
+ ——
2931
+ Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
2932
+ `, element.outerHTML);
2718
2933
  }
2719
2934
  }
2720
- locationIsVisitable(location) {
2721
- return location.isPrefixedBy(this.view.getRootLocation()) && location.isHTML();
2722
- }
2723
- }
2935
+ })();
2724
2936
 
2725
2937
  const session = new Session;
2726
2938
 
@@ -2758,6 +2970,21 @@ function setProgressBarDelay(delay) {
2758
2970
  session.setProgressBarDelay(delay);
2759
2971
  }
2760
2972
 
2973
+ var Turbo = Object.freeze({
2974
+ __proto__: null,
2975
+ navigator: navigator,
2976
+ start: start,
2977
+ registerAdapter: registerAdapter,
2978
+ visit: visit,
2979
+ connectStreamSource: connectStreamSource,
2980
+ disconnectStreamSource: disconnectStreamSource,
2981
+ renderStreamMessage: renderStreamMessage,
2982
+ clearCache: clearCache,
2983
+ setProgressBarDelay: setProgressBarDelay
2984
+ });
2985
+
2986
+ window.Turbo = Turbo;
2987
+
2761
2988
  start();
2762
2989
 
2763
2990
  var turbo_es2017Esm = Object.freeze({
@@ -2776,17 +3003,20 @@ var turbo_es2017Esm = Object.freeze({
2776
3003
  let consumer;
2777
3004
 
2778
3005
  async function getConsumer() {
2779
- if (consumer) return consumer;
2780
- const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
2781
- return index;
2782
- }));
2783
- return setConsumer(createConsumer());
3006
+ return consumer || setConsumer(createConsumer().then(setConsumer));
2784
3007
  }
2785
3008
 
2786
3009
  function setConsumer(newConsumer) {
2787
3010
  return consumer = newConsumer;
2788
3011
  }
2789
3012
 
3013
+ async function createConsumer() {
3014
+ const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
3015
+ return index;
3016
+ }));
3017
+ return createConsumer();
3018
+ }
3019
+
2790
3020
  async function subscribeTo(channel, mixin) {
2791
3021
  const {subscriptions: subscriptions} = await getConsumer();
2792
3022
  return subscriptions.create(channel, mixin);
@@ -2796,6 +3026,7 @@ var cable = Object.freeze({
2796
3026
  __proto__: null,
2797
3027
  getConsumer: getConsumer,
2798
3028
  setConsumer: setConsumer,
3029
+ createConsumer: createConsumer,
2799
3030
  subscribeTo: subscribeTo
2800
3031
  });
2801
3032
 
@@ -3259,7 +3490,7 @@ function createWebSocketURL(url) {
3259
3490
  }
3260
3491
  }
3261
3492
 
3262
- function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
3493
+ function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
3263
3494
  return new Consumer(url);
3264
3495
  }
3265
3496
 
@@ -3281,7 +3512,7 @@ var index = Object.freeze({
3281
3512
  adapters: adapters,
3282
3513
  createWebSocketURL: createWebSocketURL,
3283
3514
  logger: logger,
3284
- createConsumer: createConsumer,
3515
+ createConsumer: createConsumer$1,
3285
3516
  getConfig: getConfig
3286
3517
  });
3287
3518