@hotwired/turbo 7.0.0-beta.3 → 7.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/turbo.es2017-esm.js +647 -574
  2. package/dist/turbo.es2017-esm.js.map +1 -1
  3. package/dist/turbo.es2017-umd.js +647 -574
  4. package/dist/turbo.es2017-umd.js.map +1 -1
  5. package/dist/turbo.es5-umd.js +1153 -843
  6. package/dist/turbo.es5-umd.js.map +1 -1
  7. package/dist/types/core/drive/error_renderer.d.ts +7 -11
  8. package/dist/types/core/drive/form_submission.d.ts +11 -3
  9. package/dist/types/core/drive/head_snapshot.d.ts +21 -0
  10. package/dist/types/core/drive/history.d.ts +5 -6
  11. package/dist/types/core/drive/navigator.d.ts +7 -7
  12. package/dist/types/core/drive/page_renderer.d.ts +26 -0
  13. package/dist/types/core/drive/page_snapshot.d.ts +17 -0
  14. package/dist/types/core/drive/page_view.d.ts +21 -0
  15. package/dist/types/core/drive/snapshot_cache.d.ts +8 -9
  16. package/dist/types/core/drive/visit.d.ts +13 -15
  17. package/dist/types/core/frames/frame_controller.d.ts +11 -10
  18. package/dist/types/core/frames/frame_renderer.d.ts +8 -0
  19. package/dist/types/core/frames/frame_view.d.ts +7 -0
  20. package/dist/types/core/index.d.ts +1 -1
  21. package/dist/types/core/native/adapter.d.ts +1 -2
  22. package/dist/types/core/native/browser_adapter.d.ts +1 -2
  23. package/dist/types/core/renderer.d.ts +26 -0
  24. package/dist/types/core/session.d.ts +23 -21
  25. package/dist/types/core/snapshot.d.ts +11 -0
  26. package/dist/types/core/streams/stream_message.d.ts +1 -0
  27. package/dist/types/core/url.d.ts +7 -0
  28. package/dist/types/core/view.d.ts +27 -0
  29. package/dist/types/http/fetch_request.d.ts +8 -11
  30. package/dist/types/http/fetch_response.d.ts +1 -2
  31. package/dist/types/observers/link_click_observer.d.ts +3 -4
  32. package/dist/types/observers/stream_observer.d.ts +0 -1
  33. package/dist/types/tests/functional/form_submission_tests.d.ts +11 -3
  34. package/dist/types/tests/functional/frame_tests.d.ts +4 -0
  35. package/dist/types/tests/functional/navigation_tests.d.ts +1 -0
  36. package/dist/types/tests/functional/rendering_tests.d.ts +2 -0
  37. package/dist/types/tests/functional/stream_tests.d.ts +0 -2
  38. package/dist/types/tests/helpers/functional_test_case.d.ts +3 -0
  39. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +21 -0
  40. package/dist/types/tests/unit/index.d.ts +1 -0
  41. package/dist/types/util.d.ts +2 -0
  42. package/package.json +1 -1
  43. package/dist/types/core/drive/head_details.d.ts +0 -22
  44. package/dist/types/core/drive/renderer.d.ts +0 -13
  45. package/dist/types/core/drive/snapshot.d.ts +0 -24
  46. package/dist/types/core/drive/snapshot_renderer.d.ts +0 -43
  47. package/dist/types/core/drive/view.d.ts +0 -34
  48. package/dist/types/core/location.d.ts +0 -22
@@ -1,5 +1,5 @@
1
1
  /*
2
- Turbo 7.0.0-beta.3
2
+ Turbo 7.0.0-beta.4
3
3
  Copyright © 2021 Basecamp, LLC
4
4
  */
5
5
  (function () {
@@ -135,80 +135,53 @@ function frameLoadingStyleFromString(style) {
135
135
  }
136
136
  }
137
137
 
138
- class Location {
139
- constructor(url) {
140
- const linkWithAnchor = document.createElement("a");
141
- linkWithAnchor.href = url;
142
- this.absoluteURL = linkWithAnchor.href;
143
- const anchorLength = linkWithAnchor.hash.length;
144
- if (anchorLength < 2) {
145
- this.requestURL = this.absoluteURL;
146
- }
147
- else {
148
- this.requestURL = this.absoluteURL.slice(0, -anchorLength);
149
- this.anchor = linkWithAnchor.hash.slice(1);
150
- }
151
- }
152
- static get currentLocation() {
153
- return this.wrap(window.location.toString());
154
- }
155
- static wrap(locatable) {
156
- if (typeof locatable == "string") {
157
- return new this(locatable);
158
- }
159
- else if (locatable != null) {
160
- return locatable;
161
- }
162
- }
163
- getOrigin() {
164
- return this.absoluteURL.split("/", 3).join("/");
165
- }
166
- getPath() {
167
- return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
168
- }
169
- getPathComponents() {
170
- return this.getPath().split("/").slice(1);
171
- }
172
- getLastPathComponent() {
173
- return this.getPathComponents().slice(-1)[0];
174
- }
175
- getExtension() {
176
- return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
177
- }
178
- isHTML() {
179
- return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
180
- }
181
- isPrefixedBy(location) {
182
- const prefixURL = getPrefixURL(location);
183
- return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
184
- }
185
- isEqualTo(location) {
186
- return location && this.absoluteURL === location.absoluteURL;
138
+ function expandURL(locatable) {
139
+ const anchor = document.createElement("a");
140
+ anchor.href = locatable.toString();
141
+ return new URL(anchor.href);
142
+ }
143
+ function getAnchor(url) {
144
+ let anchorMatch;
145
+ if (url.hash) {
146
+ return url.hash.slice(1);
187
147
  }
188
- toCacheKey() {
189
- return this.requestURL;
148
+ else if (anchorMatch = url.href.match(/#(.*)$/)) {
149
+ return anchorMatch[1];
190
150
  }
191
- toJSON() {
192
- return this.absoluteURL;
151
+ else {
152
+ return "";
193
153
  }
194
- toString() {
195
- return this.absoluteURL;
154
+ }
155
+ function getExtension(url) {
156
+ return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
157
+ }
158
+ function isHTML(url) {
159
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
160
+ }
161
+ function isPrefixedBy(baseURL, url) {
162
+ const prefix = getPrefix(url);
163
+ return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
164
+ }
165
+ function toCacheKey(url) {
166
+ const anchorLength = url.hash.length;
167
+ if (anchorLength < 2) {
168
+ return url.href;
196
169
  }
197
- valueOf() {
198
- return this.absoluteURL;
170
+ else {
171
+ return url.href.slice(0, -anchorLength);
199
172
  }
200
173
  }
201
- function getPrefixURL(location) {
202
- return addTrailingSlash(location.getOrigin() + location.getPath());
174
+ function getPathComponents(url) {
175
+ return url.pathname.split("/").slice(1);
203
176
  }
204
- function addTrailingSlash(url) {
205
- return stringEndsWith(url, "/") ? url : url + "/";
177
+ function getLastPathComponent(url) {
178
+ return getPathComponents(url).slice(-1)[0];
206
179
  }
207
- function stringStartsWith(string, prefix) {
208
- return string.slice(0, prefix.length) === prefix;
180
+ function getPrefix(url) {
181
+ return addTrailingSlash(url.origin + url.pathname);
209
182
  }
210
- function stringEndsWith(string, suffix) {
211
- return string.slice(-suffix.length) === suffix;
183
+ function addTrailingSlash(value) {
184
+ return value.endsWith("/") ? value : value + "/";
212
185
  }
213
186
 
214
187
  class FetchResponse {
@@ -231,7 +204,7 @@ class FetchResponse {
231
204
  return this.response.redirected;
232
205
  }
233
206
  get location() {
234
- return Location.wrap(this.response.url);
207
+ return expandURL(this.response.url);
235
208
  }
236
209
  get isHTML() {
237
210
  return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
@@ -266,9 +239,15 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
266
239
  function nextAnimationFrame() {
267
240
  return new Promise(resolve => requestAnimationFrame(() => resolve()));
268
241
  }
242
+ function nextEventLoopTick() {
243
+ return new Promise(resolve => setTimeout(() => resolve(), 0));
244
+ }
269
245
  function nextMicrotask() {
270
246
  return Promise.resolve();
271
247
  }
248
+ function parseHTMLDocument(html = "") {
249
+ return new DOMParser().parseFromString(html, "text/html");
250
+ }
272
251
  function unindent(strings, ...values) {
273
252
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
274
253
  const match = lines[0].match(/^\s+/);
@@ -316,28 +295,23 @@ function fetchMethodFromString(method) {
316
295
  }
317
296
  }
318
297
  class FetchRequest {
319
- constructor(delegate, method, location, body) {
298
+ constructor(delegate, method, location, body = new URLSearchParams) {
320
299
  this.abortController = new AbortController;
321
300
  this.delegate = delegate;
322
301
  this.method = method;
323
- this.location = location;
324
- this.body = body;
325
- }
326
- get url() {
327
- const url = this.location.absoluteURL;
328
- const query = this.params.toString();
329
- if (this.isIdempotent && query.length) {
330
- return [url, query].join(url.includes("?") ? "&" : "?");
302
+ if (this.isIdempotent) {
303
+ this.url = mergeFormDataEntries(location, [...body.entries()]);
331
304
  }
332
305
  else {
333
- return url;
306
+ this.body = body;
307
+ this.url = location;
334
308
  }
335
309
  }
310
+ get location() {
311
+ return this.url;
312
+ }
336
313
  get params() {
337
- return this.entries.reduce((params, [name, value]) => {
338
- params.append(name, value.toString());
339
- return params;
340
- }, new URLSearchParams);
314
+ return this.url.searchParams;
341
315
  }
342
316
  get entries() {
343
317
  return this.body ? Array.from(this.body.entries()) : [];
@@ -350,7 +324,7 @@ class FetchRequest {
350
324
  dispatch("turbo:before-fetch-request", { detail: { fetchOptions } });
351
325
  try {
352
326
  this.delegate.requestStarted(this);
353
- const response = await fetch(this.url, fetchOptions);
327
+ const response = await fetch(this.url.href, fetchOptions);
354
328
  return await this.receive(response);
355
329
  }
356
330
  catch (error) {
@@ -381,7 +355,7 @@ class FetchRequest {
381
355
  credentials: "same-origin",
382
356
  headers: this.headers,
383
357
  redirect: "follow",
384
- body: this.isIdempotent ? undefined : this.body,
358
+ body: this.body,
385
359
  signal: this.abortSignal
386
360
  };
387
361
  }
@@ -389,19 +363,35 @@ class FetchRequest {
389
363
  return this.method == FetchMethod.get;
390
364
  }
391
365
  get headers() {
392
- return Object.assign({ "Accept": "text/html, application/xhtml+xml" }, this.additionalHeaders);
393
- }
394
- get additionalHeaders() {
395
- if (typeof this.delegate.additionalHeadersForRequest == "function") {
396
- return this.delegate.additionalHeadersForRequest(this);
397
- }
398
- else {
399
- return {};
366
+ const headers = Object.assign({}, this.defaultHeaders);
367
+ if (typeof this.delegate.prepareHeadersForRequest == "function") {
368
+ this.delegate.prepareHeadersForRequest(headers, this);
400
369
  }
370
+ return headers;
401
371
  }
402
372
  get abortSignal() {
403
373
  return this.abortController.signal;
404
374
  }
375
+ get defaultHeaders() {
376
+ return {
377
+ "Accept": "text/html, application/xhtml+xml"
378
+ };
379
+ }
380
+ }
381
+ function mergeFormDataEntries(url, entries) {
382
+ const currentSearchParams = new URLSearchParams(url.search);
383
+ for (const [name, value] of entries) {
384
+ if (value instanceof File)
385
+ continue;
386
+ if (currentSearchParams.has(name)) {
387
+ currentSearchParams.delete(name);
388
+ url.searchParams.set(name, value);
389
+ }
390
+ else {
391
+ url.searchParams.append(name, value);
392
+ }
393
+ }
394
+ return url;
405
395
  }
406
396
 
407
397
  class AppearanceObserver {
@@ -431,6 +421,42 @@ class AppearanceObserver {
431
421
  }
432
422
  }
433
423
 
424
+ class StreamMessage {
425
+ constructor(html) {
426
+ this.templateElement = document.createElement("template");
427
+ this.templateElement.innerHTML = html;
428
+ }
429
+ static wrap(message) {
430
+ if (typeof message == "string") {
431
+ return new this(message);
432
+ }
433
+ else {
434
+ return message;
435
+ }
436
+ }
437
+ get fragment() {
438
+ const fragment = document.createDocumentFragment();
439
+ for (const element of this.foreignElements) {
440
+ fragment.appendChild(document.importNode(element, true));
441
+ }
442
+ return fragment;
443
+ }
444
+ get foreignElements() {
445
+ return this.templateChildren.reduce((streamElements, child) => {
446
+ if (child.tagName.toLowerCase() == "turbo-stream") {
447
+ return [...streamElements, child];
448
+ }
449
+ else {
450
+ return streamElements;
451
+ }
452
+ }, []);
453
+ }
454
+ get templateChildren() {
455
+ return Array.from(this.templateElement.content.children);
456
+ }
457
+ }
458
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
459
+
434
460
  var FormSubmissionState;
435
461
  (function (FormSubmissionState) {
436
462
  FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
@@ -440,14 +466,27 @@ var FormSubmissionState;
440
466
  FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
441
467
  FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
442
468
  })(FormSubmissionState || (FormSubmissionState = {}));
469
+ var FormEnctype;
470
+ (function (FormEnctype) {
471
+ FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
472
+ FormEnctype["multipart"] = "multipart/form-data";
473
+ FormEnctype["plain"] = "text/plain";
474
+ })(FormEnctype || (FormEnctype = {}));
475
+ function formEnctypeFromString(encoding) {
476
+ switch (encoding.toLowerCase()) {
477
+ case FormEnctype.multipart: return FormEnctype.multipart;
478
+ case FormEnctype.plain: return FormEnctype.plain;
479
+ default: return FormEnctype.urlEncoded;
480
+ }
481
+ }
443
482
  class FormSubmission {
444
483
  constructor(delegate, formElement, submitter, mustRedirect = false) {
445
484
  this.state = FormSubmissionState.initialized;
446
485
  this.delegate = delegate;
447
486
  this.formElement = formElement;
448
- this.formData = buildFormData(formElement, submitter);
449
487
  this.submitter = submitter;
450
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.formData);
488
+ this.formData = buildFormData(formElement, submitter);
489
+ this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
451
490
  this.mustRedirect = mustRedirect;
452
491
  }
453
492
  get method() {
@@ -460,7 +499,24 @@ class FormSubmission {
460
499
  return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
461
500
  }
462
501
  get location() {
463
- return Location.wrap(this.action);
502
+ return expandURL(this.action);
503
+ }
504
+ get body() {
505
+ if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
506
+ return new URLSearchParams(this.stringFormData);
507
+ }
508
+ else {
509
+ return this.formData;
510
+ }
511
+ }
512
+ get enctype() {
513
+ var _a;
514
+ return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
515
+ }
516
+ get stringFormData() {
517
+ return [...this.formData].reduce((entries, [name, value]) => {
518
+ return entries.concat(typeof value == "string" ? [[name, value]] : []);
519
+ }, []);
464
520
  }
465
521
  async start() {
466
522
  const { initialized, requesting } = FormSubmissionState;
@@ -477,15 +533,14 @@ class FormSubmission {
477
533
  return true;
478
534
  }
479
535
  }
480
- additionalHeadersForRequest(request) {
481
- const headers = {};
482
- if (this.method != FetchMethod.get) {
536
+ prepareHeadersForRequest(headers, request) {
537
+ if (!request.isIdempotent) {
483
538
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
484
539
  if (token) {
485
540
  headers["X-CSRF-Token"] = token;
486
541
  }
542
+ headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
487
543
  }
488
- return headers;
489
544
  }
490
545
  requestStarted(request) {
491
546
  this.state = FormSubmissionState.waiting;
@@ -553,6 +608,38 @@ function responseSucceededWithoutRedirect(response) {
553
608
  return response.statusCode == 200 && !response.redirected;
554
609
  }
555
610
 
611
+ class Snapshot {
612
+ constructor(element) {
613
+ this.element = element;
614
+ }
615
+ get children() {
616
+ return [...this.element.children];
617
+ }
618
+ hasAnchor(anchor) {
619
+ return this.getElementForAnchor(anchor) != null;
620
+ }
621
+ getElementForAnchor(anchor) {
622
+ try {
623
+ return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
624
+ }
625
+ catch (_a) {
626
+ return null;
627
+ }
628
+ }
629
+ get firstAutofocusableElement() {
630
+ return this.element.querySelector("[autofocus]");
631
+ }
632
+ get permanentElements() {
633
+ return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
634
+ }
635
+ getPermanentElementById(id) {
636
+ return this.element.querySelector(`#${id}[data-turbo-permanent]`);
637
+ }
638
+ getPermanentElementsPresentInSnapshot(snapshot) {
639
+ return this.permanentElements.filter(({ id }) => snapshot.getPermanentElementById(id));
640
+ }
641
+ }
642
+
556
643
  class FormInterceptor {
557
644
  constructor(delegate, element) {
558
645
  this.submitBubbled = ((event) => {
@@ -577,6 +664,83 @@ class FormInterceptor {
577
664
  }
578
665
  }
579
666
 
667
+ class View {
668
+ constructor(delegate, element) {
669
+ this.delegate = delegate;
670
+ this.element = element;
671
+ }
672
+ scrollToAnchor(anchor) {
673
+ const element = this.snapshot.getElementForAnchor(anchor);
674
+ if (element) {
675
+ this.scrollToElement(element);
676
+ }
677
+ else {
678
+ this.scrollToPosition({ x: 0, y: 0 });
679
+ }
680
+ }
681
+ scrollToElement(element) {
682
+ element.scrollIntoView();
683
+ }
684
+ scrollToPosition({ x, y }) {
685
+ this.scrollRoot.scrollTo(x, y);
686
+ }
687
+ get scrollRoot() {
688
+ return window;
689
+ }
690
+ async render(renderer) {
691
+ if (this.renderer) {
692
+ throw new Error("rendering is already in progress");
693
+ }
694
+ const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
695
+ if (shouldRender) {
696
+ try {
697
+ this.renderer = renderer;
698
+ this.prepareToRenderSnapshot(renderer);
699
+ this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
700
+ await this.renderSnapshot(renderer);
701
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview);
702
+ this.finishRenderingSnapshot(renderer);
703
+ }
704
+ finally {
705
+ delete this.renderer;
706
+ }
707
+ }
708
+ else {
709
+ this.invalidate();
710
+ }
711
+ }
712
+ invalidate() {
713
+ this.delegate.viewInvalidated();
714
+ }
715
+ prepareToRenderSnapshot(renderer) {
716
+ this.markAsPreview(renderer.isPreview);
717
+ renderer.prepareToRender();
718
+ }
719
+ markAsPreview(isPreview) {
720
+ if (isPreview) {
721
+ this.element.setAttribute("data-turbo-preview", "");
722
+ }
723
+ else {
724
+ this.element.removeAttribute("data-turbo-preview");
725
+ }
726
+ }
727
+ async renderSnapshot(renderer) {
728
+ await renderer.render();
729
+ }
730
+ finishRenderingSnapshot(renderer) {
731
+ renderer.finishRendering();
732
+ }
733
+ }
734
+
735
+ class FrameView extends View {
736
+ invalidate() {
737
+ this.element.innerHTML = "";
738
+ }
739
+ get snapshot() {
740
+ return new Snapshot(this.element);
741
+ }
742
+ }
743
+
580
744
  class LinkInterceptor {
581
745
  constructor(delegate, element) {
582
746
  this.clickBubbled = (event) => {
@@ -623,10 +787,147 @@ class LinkInterceptor {
623
787
  }
624
788
  }
625
789
 
790
+ class Renderer {
791
+ constructor(currentSnapshot, newSnapshot, isPreview) {
792
+ this.currentSnapshot = currentSnapshot;
793
+ this.newSnapshot = newSnapshot;
794
+ this.isPreview = isPreview;
795
+ this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
796
+ }
797
+ get shouldRender() {
798
+ return true;
799
+ }
800
+ prepareToRender() {
801
+ return;
802
+ }
803
+ finishRendering() {
804
+ if (this.resolvingFunctions) {
805
+ this.resolvingFunctions.resolve();
806
+ delete this.resolvingFunctions;
807
+ }
808
+ }
809
+ createScriptElement(element) {
810
+ if (element.getAttribute("data-turbo-eval") == "false") {
811
+ return element;
812
+ }
813
+ else {
814
+ const createdScriptElement = document.createElement("script");
815
+ createdScriptElement.textContent = element.textContent;
816
+ createdScriptElement.async = false;
817
+ copyElementAttributes(createdScriptElement, element);
818
+ return createdScriptElement;
819
+ }
820
+ }
821
+ preservingPermanentElements(callback) {
822
+ const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
823
+ callback();
824
+ replacePlaceholderElementsWithClonedPermanentElements(placeholders);
825
+ }
826
+ focusFirstAutofocusableElement() {
827
+ const element = this.newSnapshot.firstAutofocusableElement;
828
+ if (elementIsFocusable(element)) {
829
+ element.focus();
830
+ }
831
+ }
832
+ get currentElement() {
833
+ return this.currentSnapshot.element;
834
+ }
835
+ get newElement() {
836
+ return this.newSnapshot.element;
837
+ }
838
+ }
839
+ function replaceElementWithElement(fromElement, toElement) {
840
+ const parentElement = fromElement.parentElement;
841
+ if (parentElement) {
842
+ return parentElement.replaceChild(toElement, fromElement);
843
+ }
844
+ }
845
+ function copyElementAttributes(destinationElement, sourceElement) {
846
+ for (const { name, value } of [...sourceElement.attributes]) {
847
+ destinationElement.setAttribute(name, value);
848
+ }
849
+ }
850
+ function createPlaceholderForPermanentElement(permanentElement) {
851
+ const element = document.createElement("meta");
852
+ element.setAttribute("name", "turbo-permanent-placeholder");
853
+ element.setAttribute("content", permanentElement.id);
854
+ return { element, permanentElement };
855
+ }
856
+ function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
857
+ for (const { element, permanentElement } of placeholders) {
858
+ const clonedElement = permanentElement.cloneNode(true);
859
+ replaceElementWithElement(element, clonedElement);
860
+ }
861
+ }
862
+ function relocatePermanentElements(currentSnapshot, newSnapshot) {
863
+ return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce((placeholders, permanentElement) => {
864
+ const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
865
+ if (newElement) {
866
+ const placeholder = createPlaceholderForPermanentElement(permanentElement);
867
+ replaceElementWithElement(permanentElement, placeholder.element);
868
+ replaceElementWithElement(newElement, permanentElement);
869
+ return [...placeholders, placeholder];
870
+ }
871
+ else {
872
+ return placeholders;
873
+ }
874
+ }, []);
875
+ }
876
+ function elementIsFocusable(element) {
877
+ return element && typeof element.focus == "function";
878
+ }
879
+
880
+ class FrameRenderer extends Renderer {
881
+ get shouldRender() {
882
+ return true;
883
+ }
884
+ async render() {
885
+ await nextAnimationFrame();
886
+ this.preservingPermanentElements(() => {
887
+ this.loadFrameElement();
888
+ });
889
+ this.scrollFrameIntoView();
890
+ await nextAnimationFrame();
891
+ this.focusFirstAutofocusableElement();
892
+ }
893
+ loadFrameElement() {
894
+ var _a;
895
+ const destinationRange = document.createRange();
896
+ destinationRange.selectNodeContents(this.currentElement);
897
+ destinationRange.deleteContents();
898
+ const frameElement = this.newElement;
899
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
900
+ if (sourceRange) {
901
+ sourceRange.selectNodeContents(frameElement);
902
+ this.currentElement.appendChild(sourceRange.extractContents());
903
+ }
904
+ }
905
+ scrollFrameIntoView() {
906
+ if (this.currentElement.autoscroll || this.newElement.autoscroll) {
907
+ const element = this.currentElement.firstElementChild;
908
+ const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
909
+ if (element) {
910
+ element.scrollIntoView({ block });
911
+ return true;
912
+ }
913
+ }
914
+ return false;
915
+ }
916
+ }
917
+ function readScrollLogicalPosition(value, defaultValue) {
918
+ if (value == "end" || value == "start" || value == "center" || value == "nearest") {
919
+ return value;
920
+ }
921
+ else {
922
+ return defaultValue;
923
+ }
924
+ }
925
+
626
926
  class FrameController {
627
927
  constructor(element) {
628
928
  this.resolveVisitPromise = () => { };
629
929
  this.element = element;
930
+ this.view = new FrameView(this, this.element);
630
931
  this.appearanceObserver = new AppearanceObserver(this, this.element);
631
932
  this.linkInterceptor = new LinkInterceptor(this, this.element);
632
933
  this.formInterceptor = new FormInterceptor(this, this.element);
@@ -671,14 +972,18 @@ class FrameController {
671
972
  }
672
973
  }
673
974
  async loadResponse(response) {
674
- const fragment = fragmentFromHTML(await response.responseHTML);
675
- if (fragment) {
676
- const element = await this.extractForeignFrameElement(fragment);
677
- await nextAnimationFrame();
678
- this.loadFrameElement(element);
679
- this.scrollFrameIntoView(element);
680
- await nextAnimationFrame();
681
- this.focusFirstAutofocusableElement();
975
+ try {
976
+ const html = await response.responseHTML;
977
+ if (html) {
978
+ const { body } = parseHTMLDocument(html);
979
+ const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
980
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
981
+ await this.view.render(renderer);
982
+ }
983
+ }
984
+ catch (error) {
985
+ console.error(error);
986
+ this.view.invalidate();
682
987
  }
683
988
  }
684
989
  elementAppearedInViewport(element) {
@@ -699,14 +1004,14 @@ class FrameController {
699
1004
  }
700
1005
  this.formSubmission = new FormSubmission(this, element, submitter);
701
1006
  if (this.formSubmission.fetchRequest.isIdempotent) {
702
- this.navigateFrame(element, this.formSubmission.fetchRequest.url);
1007
+ this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
703
1008
  }
704
1009
  else {
705
1010
  this.formSubmission.start();
706
1011
  }
707
1012
  }
708
- additionalHeadersForRequest(request) {
709
- return { "Turbo-Frame": this.id };
1013
+ prepareHeadersForRequest(headers, request) {
1014
+ headers["Turbo-Frame"] = this.id;
710
1015
  }
711
1016
  requestStarted(request) {
712
1017
  this.element.setAttribute("busy", "");
@@ -742,9 +1047,14 @@ class FrameController {
742
1047
  }
743
1048
  formSubmissionFinished(formSubmission) {
744
1049
  }
1050
+ viewWillRenderSnapshot(snapshot, isPreview) {
1051
+ }
1052
+ viewRenderedSnapshot(snapshot, isPreview) {
1053
+ }
1054
+ viewInvalidated() {
1055
+ }
745
1056
  async visit(url) {
746
- const location = Location.wrap(url);
747
- const request = new FetchRequest(this, FetchMethod.get, location);
1057
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
748
1058
  return new Promise(resolve => {
749
1059
  this.resolveVisitPromise = () => {
750
1060
  this.resolveVisitPromise = () => { };
@@ -759,7 +1069,7 @@ class FrameController {
759
1069
  }
760
1070
  findFrameElement(element) {
761
1071
  var _a;
762
- const id = element.getAttribute("data-turbo-frame");
1072
+ const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
763
1073
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
764
1074
  }
765
1075
  async extractForeignFrameElement(container) {
@@ -775,36 +1085,6 @@ class FrameController {
775
1085
  console.error(`Response has no matching <turbo-frame id="${id}"> element`);
776
1086
  return new FrameElement();
777
1087
  }
778
- loadFrameElement(frameElement) {
779
- var _a;
780
- const destinationRange = document.createRange();
781
- destinationRange.selectNodeContents(this.element);
782
- destinationRange.deleteContents();
783
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
784
- if (sourceRange) {
785
- sourceRange.selectNodeContents(frameElement);
786
- this.element.appendChild(sourceRange.extractContents());
787
- }
788
- }
789
- focusFirstAutofocusableElement() {
790
- const element = this.firstAutofocusableElement;
791
- if (element) {
792
- element.focus();
793
- return true;
794
- }
795
- return false;
796
- }
797
- scrollFrameIntoView(frame) {
798
- if (this.element.autoscroll || frame.autoscroll) {
799
- const element = this.element.firstElementChild;
800
- const block = readScrollLogicalPosition(this.element.getAttribute("data-autoscroll-block"), "end");
801
- if (element) {
802
- element.scrollIntoView({ block });
803
- return true;
804
- }
805
- }
806
- return false;
807
- }
808
1088
  shouldInterceptNavigation(element) {
809
1089
  const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
810
1090
  if (!this.enabled || id == "_top") {
@@ -818,10 +1098,6 @@ class FrameController {
818
1098
  }
819
1099
  return true;
820
1100
  }
821
- get firstAutofocusableElement() {
822
- const element = this.element.querySelector("[autofocus]");
823
- return element instanceof HTMLElement ? element : null;
824
- }
825
1101
  get id() {
826
1102
  return this.element.id;
827
1103
  }
@@ -849,20 +1125,6 @@ function getFrameElementById(id) {
849
1125
  }
850
1126
  }
851
1127
  }
852
- function readScrollLogicalPosition(value, defaultValue) {
853
- if (value == "end" || value == "start" || value == "center" || value == "nearest") {
854
- return value;
855
- }
856
- else {
857
- return defaultValue;
858
- }
859
- }
860
- function fragmentFromHTML(html) {
861
- if (html) {
862
- const foreignDocument = document.implementation.createHTMLDocument();
863
- return foreignDocument.createRange().createContextualFragment(html);
864
- }
865
- }
866
1128
  function activateElement(element) {
867
1129
  if (element && element.ownerDocument !== document) {
868
1130
  element = document.importNode(element, true);
@@ -1092,9 +1354,10 @@ class ProgressBar {
1092
1354
  }
1093
1355
  ProgressBar.animationDuration = 300;
1094
1356
 
1095
- class HeadDetails {
1096
- constructor(children) {
1097
- this.detailsByOuterHTML = children.reduce((result, element) => {
1357
+ class HeadSnapshot extends Snapshot {
1358
+ constructor() {
1359
+ super(...arguments);
1360
+ this.detailsByOuterHTML = this.children.reduce((result, element) => {
1098
1361
  const { outerHTML } = element;
1099
1362
  const details = outerHTML in result
1100
1363
  ? result[outerHTML]
@@ -1106,29 +1369,25 @@ class HeadDetails {
1106
1369
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1107
1370
  }, {});
1108
1371
  }
1109
- static fromHeadElement(headElement) {
1110
- const children = headElement ? [...headElement.children] : [];
1111
- return new this(children);
1112
- }
1113
- getTrackedElementSignature() {
1372
+ get trackedElementSignature() {
1114
1373
  return Object.keys(this.detailsByOuterHTML)
1115
1374
  .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1116
1375
  .join("");
1117
1376
  }
1118
- getScriptElementsNotInDetails(headDetails) {
1119
- return this.getElementsMatchingTypeNotInDetails("script", headDetails);
1377
+ getScriptElementsNotInSnapshot(snapshot) {
1378
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1120
1379
  }
1121
- getStylesheetElementsNotInDetails(headDetails) {
1122
- return this.getElementsMatchingTypeNotInDetails("stylesheet", headDetails);
1380
+ getStylesheetElementsNotInSnapshot(snapshot) {
1381
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1123
1382
  }
1124
- getElementsMatchingTypeNotInDetails(matchedType, headDetails) {
1383
+ getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1125
1384
  return Object.keys(this.detailsByOuterHTML)
1126
- .filter(outerHTML => !(outerHTML in headDetails.detailsByOuterHTML))
1385
+ .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1127
1386
  .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1128
1387
  .filter(({ type }) => type == matchedType)
1129
1388
  .map(({ elements: [element] }) => element);
1130
1389
  }
1131
- getProvisionalElements() {
1390
+ get provisionalElements() {
1132
1391
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1133
1392
  const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];
1134
1393
  if (type == null && !tracked) {
@@ -1178,79 +1437,46 @@ function elementIsMetaElementWithName(element, name) {
1178
1437
  const tagName = element.tagName.toLowerCase();
1179
1438
  return tagName == "meta" && element.getAttribute("name") == name;
1180
1439
  }
1181
-
1182
- class Snapshot {
1183
- constructor(headDetails, bodyElement) {
1184
- this.headDetails = headDetails;
1185
- this.bodyElement = bodyElement;
1186
- }
1187
- static wrap(value) {
1188
- if (value instanceof this) {
1189
- return value;
1190
- }
1191
- else if (typeof value == "string") {
1192
- return this.fromHTMLString(value);
1193
- }
1194
- else {
1195
- return this.fromHTMLElement(value);
1196
- }
1197
- }
1198
- static fromHTMLString(html) {
1199
- const { documentElement } = new DOMParser().parseFromString(html, "text/html");
1200
- return this.fromHTMLElement(documentElement);
1201
- }
1202
- static fromHTMLElement(htmlElement) {
1203
- const headElement = htmlElement.querySelector("head");
1204
- const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
1205
- const headDetails = HeadDetails.fromHeadElement(headElement);
1206
- return new this(headDetails, bodyElement);
1207
- }
1208
- clone() {
1209
- const { bodyElement } = Snapshot.fromHTMLString(this.bodyElement.outerHTML);
1210
- return new Snapshot(this.headDetails, bodyElement);
1211
- }
1212
- getRootLocation() {
1213
- const root = this.getSetting("root", "/");
1214
- return new Location(root);
1440
+
1441
+ class PageSnapshot extends Snapshot {
1442
+ constructor(element, headSnapshot) {
1443
+ super(element);
1444
+ this.headSnapshot = headSnapshot;
1215
1445
  }
1216
- getCacheControlValue() {
1217
- return this.getSetting("cache-control");
1446
+ static fromHTMLString(html = "") {
1447
+ return this.fromDocument(parseHTMLDocument(html));
1218
1448
  }
1219
- getElementForAnchor(anchor) {
1220
- try {
1221
- return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
1222
- }
1223
- catch (_a) {
1224
- return null;
1225
- }
1449
+ static fromElement(element) {
1450
+ return this.fromDocument(element.ownerDocument);
1226
1451
  }
1227
- getPermanentElements() {
1228
- return [...this.bodyElement.querySelectorAll("[id][data-turbo-permanent]")];
1452
+ static fromDocument({ head, body }) {
1453
+ return new this(body, new HeadSnapshot(head));
1229
1454
  }
1230
- getPermanentElementById(id) {
1231
- return this.bodyElement.querySelector(`#${id}[data-turbo-permanent]`);
1455
+ clone() {
1456
+ return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1232
1457
  }
1233
- getPermanentElementsPresentInSnapshot(snapshot) {
1234
- return this.getPermanentElements().filter(({ id }) => snapshot.getPermanentElementById(id));
1458
+ get headElement() {
1459
+ return this.headSnapshot.element;
1235
1460
  }
1236
- findFirstAutofocusableElement() {
1237
- return this.bodyElement.querySelector("[autofocus]");
1461
+ get rootLocation() {
1462
+ var _a;
1463
+ const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1464
+ return expandURL(root);
1238
1465
  }
1239
- hasAnchor(anchor) {
1240
- return this.getElementForAnchor(anchor) != null;
1466
+ get cacheControlValue() {
1467
+ return this.getSetting("cache-control");
1241
1468
  }
1242
- isPreviewable() {
1243
- return this.getCacheControlValue() != "no-preview";
1469
+ get isPreviewable() {
1470
+ return this.cacheControlValue != "no-preview";
1244
1471
  }
1245
- isCacheable() {
1246
- return this.getCacheControlValue() != "no-cache";
1472
+ get isCacheable() {
1473
+ return this.cacheControlValue != "no-cache";
1247
1474
  }
1248
- isVisitable() {
1475
+ get isVisitable() {
1249
1476
  return this.getSetting("visit-control") != "reload";
1250
1477
  }
1251
- getSetting(name, defaultValue) {
1252
- const value = this.headDetails.getMetaValue(`turbo-${name}`);
1253
- return value == null ? defaultValue : value;
1478
+ getSetting(name) {
1479
+ return this.headSnapshot.getMetaValue(`turbo-${name}`);
1254
1480
  }
1255
1481
  }
1256
1482
 
@@ -1288,17 +1514,6 @@ class Visit {
1288
1514
  this.scrolled = false;
1289
1515
  this.snapshotCached = false;
1290
1516
  this.state = VisitState.initialized;
1291
- this.performScroll = () => {
1292
- if (!this.scrolled) {
1293
- if (this.action == "restore") {
1294
- this.scrollToRestoredPosition() || this.scrollToTop();
1295
- }
1296
- else {
1297
- this.scrollToAnchor() || this.scrollToTop();
1298
- }
1299
- this.scrolled = true;
1300
- }
1301
- };
1302
1517
  this.delegate = delegate;
1303
1518
  this.location = location;
1304
1519
  this.restorationIdentifier = restorationIdentifier || uuid();
@@ -1353,8 +1568,9 @@ class Visit {
1353
1568
  }
1354
1569
  }
1355
1570
  changeHistory() {
1571
+ var _a;
1356
1572
  if (!this.historyChanged) {
1357
- const actionForHistory = this.location.isEqualTo(this.referrer) ? "replace" : this.action;
1573
+ const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1358
1574
  const method = this.getHistoryMethodForAction(actionForHistory);
1359
1575
  this.history.update(method, this.location, this.restorationIdentifier);
1360
1576
  this.historyChanged = true;
@@ -1399,15 +1615,15 @@ class Visit {
1399
1615
  loadResponse() {
1400
1616
  if (this.response) {
1401
1617
  const { statusCode, responseHTML } = this.response;
1402
- this.render(() => {
1618
+ this.render(async () => {
1403
1619
  this.cacheSnapshot();
1404
1620
  if (isSuccessful(statusCode) && responseHTML != null) {
1405
- this.view.render({ snapshot: Snapshot.fromHTMLString(responseHTML) }, this.performScroll);
1621
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1406
1622
  this.adapter.visitRendered(this);
1407
1623
  this.complete();
1408
1624
  }
1409
1625
  else {
1410
- this.view.render({ error: responseHTML }, this.performScroll);
1626
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1411
1627
  this.adapter.visitRendered(this);
1412
1628
  this.fail();
1413
1629
  }
@@ -1416,15 +1632,15 @@ class Visit {
1416
1632
  }
1417
1633
  getCachedSnapshot() {
1418
1634
  const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
1419
- if (snapshot && (!this.location.anchor || snapshot.hasAnchor(this.location.anchor))) {
1420
- if (this.action == "restore" || snapshot.isPreviewable()) {
1635
+ if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
1636
+ if (this.action == "restore" || snapshot.isPreviewable) {
1421
1637
  return snapshot;
1422
1638
  }
1423
1639
  }
1424
1640
  }
1425
1641
  getPreloadedSnapshot() {
1426
1642
  if (this.snapshotHTML) {
1427
- return Snapshot.wrap(this.snapshotHTML);
1643
+ return PageSnapshot.fromHTMLString(this.snapshotHTML);
1428
1644
  }
1429
1645
  }
1430
1646
  hasCachedSnapshot() {
@@ -1434,9 +1650,9 @@ class Visit {
1434
1650
  const snapshot = this.getCachedSnapshot();
1435
1651
  if (snapshot) {
1436
1652
  const isPreview = this.shouldIssueRequest();
1437
- this.render(() => {
1653
+ this.render(async () => {
1438
1654
  this.cacheSnapshot();
1439
- this.view.render({ snapshot, isPreview }, this.performScroll);
1655
+ await this.view.renderPage(snapshot);
1440
1656
  this.adapter.visitRendered(this);
1441
1657
  if (!isPreview) {
1442
1658
  this.complete();
@@ -1481,6 +1697,17 @@ class Visit {
1481
1697
  requestFinished() {
1482
1698
  this.finishRequest();
1483
1699
  }
1700
+ performScroll() {
1701
+ if (!this.scrolled) {
1702
+ if (this.action == "restore") {
1703
+ this.scrollToRestoredPosition() || this.scrollToTop();
1704
+ }
1705
+ else {
1706
+ this.scrollToAnchor() || this.scrollToTop();
1707
+ }
1708
+ this.scrolled = true;
1709
+ }
1710
+ }
1484
1711
  scrollToRestoredPosition() {
1485
1712
  const { scrollPosition } = this.restorationData;
1486
1713
  if (scrollPosition) {
@@ -1489,8 +1716,8 @@ class Visit {
1489
1716
  }
1490
1717
  }
1491
1718
  scrollToAnchor() {
1492
- if (this.location.anchor != null) {
1493
- this.view.scrollToAnchor(this.location.anchor);
1719
+ if (getAnchor(this.location) != null) {
1720
+ this.view.scrollToAnchor(getAnchor(this.location));
1494
1721
  return true;
1495
1722
  }
1496
1723
  }
@@ -1524,12 +1751,14 @@ class Visit {
1524
1751
  this.snapshotCached = true;
1525
1752
  }
1526
1753
  }
1527
- render(callback) {
1754
+ async render(callback) {
1528
1755
  this.cancelRender();
1529
- this.frame = requestAnimationFrame(() => {
1530
- delete this.frame;
1531
- callback.call(this);
1756
+ await new Promise(resolve => {
1757
+ this.frame = requestAnimationFrame(() => resolve());
1532
1758
  });
1759
+ callback();
1760
+ delete this.frame;
1761
+ this.performScroll();
1533
1762
  }
1534
1763
  cancelRender() {
1535
1764
  if (this.frame) {
@@ -1705,11 +1934,10 @@ class History {
1705
1934
  if (this.shouldHandlePopState()) {
1706
1935
  const { turbo } = event.state || {};
1707
1936
  if (turbo) {
1708
- const location = Location.currentLocation;
1709
- this.location = location;
1937
+ this.location = new URL(window.location.href);
1710
1938
  const { restorationIdentifier } = turbo;
1711
1939
  this.restorationIdentifier = restorationIdentifier;
1712
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
1940
+ this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
1713
1941
  }
1714
1942
  }
1715
1943
  };
@@ -1724,7 +1952,7 @@ class History {
1724
1952
  addEventListener("popstate", this.onPopState, false);
1725
1953
  addEventListener("load", this.onPageLoad, false);
1726
1954
  this.started = true;
1727
- this.replace(Location.currentLocation);
1955
+ this.replace(new URL(window.location.href));
1728
1956
  }
1729
1957
  }
1730
1958
  stop() {
@@ -1742,7 +1970,7 @@ class History {
1742
1970
  }
1743
1971
  update(method, location, restorationIdentifier = uuid()) {
1744
1972
  const state = { turbo: { restorationIdentifier } };
1745
- method.call(history, state, "", location.absoluteURL);
1973
+ method.call(history, state, "", location.href);
1746
1974
  this.location = location;
1747
1975
  this.restorationIdentifier = restorationIdentifier;
1748
1976
  }
@@ -1823,7 +2051,7 @@ class LinkClickObserver {
1823
2051
  }
1824
2052
  }
1825
2053
  getLocationForLink(link) {
1826
- return new Location(link.getAttribute("href") || "");
2054
+ return expandURL(link.getAttribute("href") || "");
1827
2055
  }
1828
2056
  }
1829
2057
 
@@ -1836,15 +2064,20 @@ class Navigator {
1836
2064
  this.delegate.visitProposedToLocation(location, options);
1837
2065
  }
1838
2066
  }
1839
- startVisit(location, restorationIdentifier, options = {}) {
2067
+ startVisit(locatable, restorationIdentifier, options = {}) {
1840
2068
  this.stop();
1841
- this.currentVisit = new Visit(this, Location.wrap(location), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2069
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
1842
2070
  this.currentVisit.start();
1843
2071
  }
1844
2072
  submitForm(form, submitter) {
1845
2073
  this.stop();
1846
2074
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1847
- this.formSubmission.start();
2075
+ if (this.formSubmission.fetchRequest.isIdempotent) {
2076
+ this.proposeVisit(this.formSubmission.fetchRequest.url);
2077
+ }
2078
+ else {
2079
+ this.formSubmission.start();
2080
+ }
1848
2081
  }
1849
2082
  stop() {
1850
2083
  if (this.formSubmission) {
@@ -1883,8 +2116,8 @@ class Navigator {
1883
2116
  async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
1884
2117
  const responseHTML = await fetchResponse.responseHTML;
1885
2118
  if (responseHTML) {
1886
- const snapshot = Snapshot.fromHTMLString(responseHTML);
1887
- this.view.render({ snapshot }, () => { });
2119
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2120
+ await this.view.renderPage(snapshot);
1888
2121
  this.view.clearSnapshotCache();
1889
2122
  }
1890
2123
  }
@@ -1992,53 +2225,10 @@ class ScrollObserver {
1992
2225
  }
1993
2226
  }
1994
2227
 
1995
- class StreamMessage {
1996
- constructor(html) {
1997
- this.templateElement = document.createElement("template");
1998
- this.templateElement.innerHTML = html;
1999
- }
2000
- static wrap(message) {
2001
- if (typeof message == "string") {
2002
- return new this(message);
2003
- }
2004
- else {
2005
- return message;
2006
- }
2007
- }
2008
- get fragment() {
2009
- const fragment = document.createDocumentFragment();
2010
- for (const element of this.foreignElements) {
2011
- fragment.appendChild(document.importNode(element, true));
2012
- }
2013
- return fragment;
2014
- }
2015
- get foreignElements() {
2016
- return this.templateChildren.reduce((streamElements, child) => {
2017
- if (child.tagName.toLowerCase() == "turbo-stream") {
2018
- return [...streamElements, child];
2019
- }
2020
- else {
2021
- return streamElements;
2022
- }
2023
- }, []);
2024
- }
2025
- get templateChildren() {
2026
- return Array.from(this.templateElement.content.children);
2027
- }
2028
- }
2029
-
2030
2228
  class StreamObserver {
2031
2229
  constructor(delegate) {
2032
2230
  this.sources = new Set;
2033
2231
  this.started = false;
2034
- this.prepareFetchRequest = ((event) => {
2035
- var _a;
2036
- const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
2037
- if (fetchOptions) {
2038
- const { headers } = fetchOptions;
2039
- headers.Accept = ["text/vnd.turbo-stream.html", headers.Accept].join(", ");
2040
- }
2041
- });
2042
2232
  this.inspectFetchResponse = ((event) => {
2043
2233
  const response = fetchResponseFromEvent(event);
2044
2234
  if (response && fetchResponseIsStream(response)) {
@@ -2056,14 +2246,12 @@ class StreamObserver {
2056
2246
  start() {
2057
2247
  if (!this.started) {
2058
2248
  this.started = true;
2059
- addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2060
2249
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2061
2250
  }
2062
2251
  }
2063
2252
  stop() {
2064
2253
  if (this.started) {
2065
2254
  this.started = false;
2066
- removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2067
2255
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2068
2256
  }
2069
2257
  }
@@ -2102,70 +2290,25 @@ function fetchResponseFromEvent(event) {
2102
2290
  function fetchResponseIsStream(response) {
2103
2291
  var _a;
2104
2292
  const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2105
- return /^text\/vnd\.turbo-stream\.html\b/.test(contentType);
2293
+ return contentType.startsWith(StreamMessage.contentType);
2106
2294
  }
2107
2295
 
2108
2296
  function isAction(action) {
2109
2297
  return action == "advance" || action == "replace" || action == "restore";
2110
2298
  }
2111
2299
 
2112
- class Renderer {
2113
- renderView(callback) {
2114
- this.delegate.viewWillRender(this.newBody);
2115
- callback();
2116
- this.delegate.viewRendered(this.newBody);
2117
- }
2118
- invalidateView() {
2119
- this.delegate.viewInvalidated();
2120
- }
2121
- createScriptElement(element) {
2122
- if (element.getAttribute("data-turbo-eval") == "false") {
2123
- return element;
2124
- }
2125
- else {
2126
- const createdScriptElement = document.createElement("script");
2127
- createdScriptElement.textContent = element.textContent;
2128
- createdScriptElement.async = false;
2129
- copyElementAttributes(createdScriptElement, element);
2130
- return createdScriptElement;
2131
- }
2132
- }
2133
- }
2134
- function copyElementAttributes(destinationElement, sourceElement) {
2135
- for (const { name, value } of [...sourceElement.attributes]) {
2136
- destinationElement.setAttribute(name, value);
2137
- }
2138
- }
2139
-
2140
2300
  class ErrorRenderer extends Renderer {
2141
- constructor(delegate, html) {
2142
- super();
2143
- this.delegate = delegate;
2144
- this.htmlElement = (() => {
2145
- const htmlElement = document.createElement("html");
2146
- htmlElement.innerHTML = html;
2147
- return htmlElement;
2148
- })();
2149
- this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
2150
- this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
2151
- }
2152
- static render(delegate, callback, html) {
2153
- return new this(delegate, html).render(callback);
2154
- }
2155
- render(callback) {
2156
- this.renderView(() => {
2157
- this.replaceHeadAndBody();
2158
- this.activateBodyScriptElements();
2159
- callback();
2160
- });
2301
+ async render() {
2302
+ this.replaceHeadAndBody();
2303
+ this.activateScriptElements();
2161
2304
  }
2162
2305
  replaceHeadAndBody() {
2163
2306
  const { documentElement, head, body } = document;
2164
2307
  documentElement.replaceChild(this.newHead, head);
2165
- documentElement.replaceChild(this.newBody, body);
2308
+ documentElement.replaceChild(this.newElement, body);
2166
2309
  }
2167
- activateBodyScriptElements() {
2168
- for (const replaceableElement of this.getScriptElements()) {
2310
+ activateScriptElements() {
2311
+ for (const replaceableElement of this.scriptElements) {
2169
2312
  const parentNode = replaceableElement.parentNode;
2170
2313
  if (parentNode) {
2171
2314
  const element = this.createScriptElement(replaceableElement);
@@ -2173,84 +2316,38 @@ class ErrorRenderer extends Renderer {
2173
2316
  }
2174
2317
  }
2175
2318
  }
2176
- getScriptElements() {
2319
+ get newHead() {
2320
+ return this.newSnapshot.headSnapshot.element;
2321
+ }
2322
+ get scriptElements() {
2177
2323
  return [...document.documentElement.querySelectorAll("script")];
2178
2324
  }
2179
2325
  }
2180
2326
 
2181
- class SnapshotCache {
2182
- constructor(size) {
2183
- this.keys = [];
2184
- this.snapshots = {};
2185
- this.size = size;
2186
- }
2187
- has(location) {
2188
- return location.toCacheKey() in this.snapshots;
2189
- }
2190
- get(location) {
2191
- if (this.has(location)) {
2192
- const snapshot = this.read(location);
2193
- this.touch(location);
2194
- return snapshot;
2195
- }
2196
- }
2197
- put(location, snapshot) {
2198
- this.write(location, snapshot);
2199
- this.touch(location);
2200
- return snapshot;
2201
- }
2202
- clear() {
2203
- this.snapshots = {};
2204
- }
2205
- read(location) {
2206
- return this.snapshots[location.toCacheKey()];
2327
+ class PageRenderer extends Renderer {
2328
+ get shouldRender() {
2329
+ return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2207
2330
  }
2208
- write(location, snapshot) {
2209
- this.snapshots[location.toCacheKey()] = snapshot;
2331
+ prepareToRender() {
2332
+ this.mergeHead();
2210
2333
  }
2211
- touch(location) {
2212
- const key = location.toCacheKey();
2213
- const index = this.keys.indexOf(key);
2214
- if (index > -1)
2215
- this.keys.splice(index, 1);
2216
- this.keys.unshift(key);
2217
- this.trim();
2334
+ async render() {
2335
+ this.replaceBody();
2218
2336
  }
2219
- trim() {
2220
- for (const key of this.keys.splice(this.size)) {
2221
- delete this.snapshots[key];
2337
+ finishRendering() {
2338
+ super.finishRendering();
2339
+ if (this.isPreview) {
2340
+ this.focusFirstAutofocusableElement();
2222
2341
  }
2223
2342
  }
2224
- }
2225
-
2226
- class SnapshotRenderer extends Renderer {
2227
- constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
2228
- super();
2229
- this.delegate = delegate;
2230
- this.currentSnapshot = currentSnapshot;
2231
- this.currentHeadDetails = currentSnapshot.headDetails;
2232
- this.newSnapshot = newSnapshot;
2233
- this.newHeadDetails = newSnapshot.headDetails;
2234
- this.newBody = newSnapshot.bodyElement;
2235
- this.isPreview = isPreview;
2343
+ get currentHeadSnapshot() {
2344
+ return this.currentSnapshot.headSnapshot;
2236
2345
  }
2237
- static render(delegate, callback, currentSnapshot, newSnapshot, isPreview) {
2238
- return new this(delegate, currentSnapshot, newSnapshot, isPreview).render(callback);
2346
+ get newHeadSnapshot() {
2347
+ return this.newSnapshot.headSnapshot;
2239
2348
  }
2240
- render(callback) {
2241
- if (this.shouldRender()) {
2242
- this.mergeHead();
2243
- this.renderView(() => {
2244
- this.replaceBody();
2245
- if (!this.isPreview) {
2246
- this.focusFirstAutofocusableElement();
2247
- }
2248
- callback();
2249
- });
2250
- }
2251
- else {
2252
- this.invalidateView();
2253
- }
2349
+ get newElement() {
2350
+ return this.newSnapshot.element;
2254
2351
  }
2255
2352
  mergeHead() {
2256
2353
  this.copyNewHeadStylesheetElements();
@@ -2259,186 +2356,147 @@ class SnapshotRenderer extends Renderer {
2259
2356
  this.copyNewHeadProvisionalElements();
2260
2357
  }
2261
2358
  replaceBody() {
2262
- const placeholders = this.relocateCurrentBodyPermanentElements();
2263
- this.activateNewBody();
2264
- this.assignNewBody();
2265
- this.replacePlaceholderElementsWithClonedPermanentElements(placeholders);
2266
- }
2267
- shouldRender() {
2268
- return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
2359
+ this.preservingPermanentElements(() => {
2360
+ this.activateNewBody();
2361
+ this.assignNewBody();
2362
+ });
2269
2363
  }
2270
- trackedElementsAreIdentical() {
2271
- return this.currentHeadDetails.getTrackedElementSignature() == this.newHeadDetails.getTrackedElementSignature();
2364
+ get trackedElementsAreIdentical() {
2365
+ return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2272
2366
  }
2273
2367
  copyNewHeadStylesheetElements() {
2274
- for (const element of this.getNewHeadStylesheetElements()) {
2368
+ for (const element of this.newHeadStylesheetElements) {
2275
2369
  document.head.appendChild(element);
2276
2370
  }
2277
2371
  }
2278
2372
  copyNewHeadScriptElements() {
2279
- for (const element of this.getNewHeadScriptElements()) {
2373
+ for (const element of this.newHeadScriptElements) {
2280
2374
  document.head.appendChild(this.createScriptElement(element));
2281
2375
  }
2282
2376
  }
2283
2377
  removeCurrentHeadProvisionalElements() {
2284
- for (const element of this.getCurrentHeadProvisionalElements()) {
2378
+ for (const element of this.currentHeadProvisionalElements) {
2285
2379
  document.head.removeChild(element);
2286
2380
  }
2287
2381
  }
2288
2382
  copyNewHeadProvisionalElements() {
2289
- for (const element of this.getNewHeadProvisionalElements()) {
2383
+ for (const element of this.newHeadProvisionalElements) {
2290
2384
  document.head.appendChild(element);
2291
2385
  }
2292
2386
  }
2293
- relocateCurrentBodyPermanentElements() {
2294
- return this.getCurrentBodyPermanentElements().reduce((placeholders, permanentElement) => {
2295
- const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
2296
- if (newElement) {
2297
- const placeholder = createPlaceholderForPermanentElement(permanentElement);
2298
- replaceElementWithElement(permanentElement, placeholder.element);
2299
- replaceElementWithElement(newElement, permanentElement);
2300
- return [...placeholders, placeholder];
2301
- }
2302
- else {
2303
- return placeholders;
2304
- }
2305
- }, []);
2306
- }
2307
- replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
2308
- for (const { element, permanentElement } of placeholders) {
2309
- const clonedElement = permanentElement.cloneNode(true);
2310
- replaceElementWithElement(element, clonedElement);
2311
- }
2312
- }
2313
2387
  activateNewBody() {
2314
- document.adoptNode(this.newBody);
2388
+ document.adoptNode(this.newElement);
2315
2389
  this.activateNewBodyScriptElements();
2316
2390
  }
2317
2391
  activateNewBodyScriptElements() {
2318
- for (const inertScriptElement of this.getNewBodyScriptElements()) {
2392
+ for (const inertScriptElement of this.newBodyScriptElements) {
2319
2393
  const activatedScriptElement = this.createScriptElement(inertScriptElement);
2320
2394
  replaceElementWithElement(inertScriptElement, activatedScriptElement);
2321
2395
  }
2322
2396
  }
2323
2397
  assignNewBody() {
2324
- if (document.body) {
2325
- replaceElementWithElement(document.body, this.newBody);
2398
+ if (document.body && this.newElement instanceof HTMLBodyElement) {
2399
+ replaceElementWithElement(document.body, this.newElement);
2326
2400
  }
2327
2401
  else {
2328
- document.documentElement.appendChild(this.newBody);
2402
+ document.documentElement.appendChild(this.newElement);
2329
2403
  }
2330
2404
  }
2331
- focusFirstAutofocusableElement() {
2332
- const element = this.newSnapshot.findFirstAutofocusableElement();
2333
- if (elementIsFocusable(element)) {
2334
- element.focus();
2335
- }
2405
+ get newHeadStylesheetElements() {
2406
+ return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2336
2407
  }
2337
- getNewHeadStylesheetElements() {
2338
- return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails);
2408
+ get newHeadScriptElements() {
2409
+ return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
2339
2410
  }
2340
- getNewHeadScriptElements() {
2341
- return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails);
2411
+ get currentHeadProvisionalElements() {
2412
+ return this.currentHeadSnapshot.provisionalElements;
2342
2413
  }
2343
- getCurrentHeadProvisionalElements() {
2344
- return this.currentHeadDetails.getProvisionalElements();
2414
+ get newHeadProvisionalElements() {
2415
+ return this.newHeadSnapshot.provisionalElements;
2345
2416
  }
2346
- getNewHeadProvisionalElements() {
2347
- return this.newHeadDetails.getProvisionalElements();
2417
+ get newBodyScriptElements() {
2418
+ return [...this.newElement.querySelectorAll("script")];
2348
2419
  }
2349
- getCurrentBodyPermanentElements() {
2350
- return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot);
2420
+ }
2421
+
2422
+ class SnapshotCache {
2423
+ constructor(size) {
2424
+ this.keys = [];
2425
+ this.snapshots = {};
2426
+ this.size = size;
2351
2427
  }
2352
- getNewBodyScriptElements() {
2353
- return [...this.newBody.querySelectorAll("script")];
2428
+ has(location) {
2429
+ return toCacheKey(location) in this.snapshots;
2354
2430
  }
2355
- }
2356
- function createPlaceholderForPermanentElement(permanentElement) {
2357
- const element = document.createElement("meta");
2358
- element.setAttribute("name", "turbo-permanent-placeholder");
2359
- element.setAttribute("content", permanentElement.id);
2360
- return { element, permanentElement };
2361
- }
2362
- function replaceElementWithElement(fromElement, toElement) {
2363
- const parentElement = fromElement.parentElement;
2364
- if (parentElement) {
2365
- return parentElement.replaceChild(toElement, fromElement);
2431
+ get(location) {
2432
+ if (this.has(location)) {
2433
+ const snapshot = this.read(location);
2434
+ this.touch(location);
2435
+ return snapshot;
2436
+ }
2437
+ }
2438
+ put(location, snapshot) {
2439
+ this.write(location, snapshot);
2440
+ this.touch(location);
2441
+ return snapshot;
2442
+ }
2443
+ clear() {
2444
+ this.snapshots = {};
2445
+ }
2446
+ read(location) {
2447
+ return this.snapshots[toCacheKey(location)];
2448
+ }
2449
+ write(location, snapshot) {
2450
+ this.snapshots[toCacheKey(location)] = snapshot;
2451
+ }
2452
+ touch(location) {
2453
+ const key = toCacheKey(location);
2454
+ const index = this.keys.indexOf(key);
2455
+ if (index > -1)
2456
+ this.keys.splice(index, 1);
2457
+ this.keys.unshift(key);
2458
+ this.trim();
2459
+ }
2460
+ trim() {
2461
+ for (const key of this.keys.splice(this.size)) {
2462
+ delete this.snapshots[key];
2463
+ }
2366
2464
  }
2367
- }
2368
- function elementIsFocusable(element) {
2369
- return element && typeof element.focus == "function";
2370
2465
  }
2371
2466
 
2372
- class View {
2373
- constructor(delegate) {
2374
- this.htmlElement = document.documentElement;
2467
+ class PageView extends View {
2468
+ constructor() {
2469
+ super(...arguments);
2375
2470
  this.snapshotCache = new SnapshotCache(10);
2376
- this.delegate = delegate;
2471
+ this.lastRenderedLocation = new URL(location.href);
2377
2472
  }
2378
- getRootLocation() {
2379
- return this.getSnapshot().getRootLocation();
2473
+ renderPage(snapshot, isPreview = false) {
2474
+ const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2475
+ return this.render(renderer);
2380
2476
  }
2381
- getElementForAnchor(anchor) {
2382
- return this.getSnapshot().getElementForAnchor(anchor);
2383
- }
2384
- getSnapshot() {
2385
- return Snapshot.fromHTMLElement(this.htmlElement);
2477
+ renderError(snapshot) {
2478
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2479
+ this.render(renderer);
2386
2480
  }
2387
2481
  clearSnapshotCache() {
2388
2482
  this.snapshotCache.clear();
2389
2483
  }
2390
- shouldCacheSnapshot() {
2391
- return this.getSnapshot().isCacheable();
2392
- }
2393
2484
  async cacheSnapshot() {
2394
- if (this.shouldCacheSnapshot()) {
2485
+ if (this.shouldCacheSnapshot) {
2395
2486
  this.delegate.viewWillCacheSnapshot();
2396
- const snapshot = this.getSnapshot();
2397
- const location = this.lastRenderedLocation || Location.currentLocation;
2398
- await nextMicrotask();
2487
+ const { snapshot, lastRenderedLocation: location } = this;
2488
+ await nextEventLoopTick();
2399
2489
  this.snapshotCache.put(location, snapshot.clone());
2400
2490
  }
2401
2491
  }
2402
2492
  getCachedSnapshotForLocation(location) {
2403
2493
  return this.snapshotCache.get(location);
2404
2494
  }
2405
- render({ snapshot, error, isPreview }, callback) {
2406
- this.markAsPreview(isPreview);
2407
- if (snapshot) {
2408
- this.renderSnapshot(snapshot, isPreview, callback);
2409
- }
2410
- else {
2411
- this.renderError(error, callback);
2412
- }
2413
- }
2414
- scrollToAnchor(anchor) {
2415
- const element = this.getElementForAnchor(anchor);
2416
- if (element) {
2417
- this.scrollToElement(element);
2418
- }
2419
- else {
2420
- this.scrollToPosition({ x: 0, y: 0 });
2421
- }
2422
- }
2423
- scrollToElement(element) {
2424
- element.scrollIntoView();
2425
- }
2426
- scrollToPosition({ x, y }) {
2427
- window.scrollTo(x, y);
2428
- }
2429
- markAsPreview(isPreview) {
2430
- if (isPreview) {
2431
- this.htmlElement.setAttribute("data-turbo-preview", "");
2432
- }
2433
- else {
2434
- this.htmlElement.removeAttribute("data-turbo-preview");
2435
- }
2436
- }
2437
- renderSnapshot(snapshot, isPreview, callback) {
2438
- SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
2495
+ get snapshot() {
2496
+ return PageSnapshot.fromElement(this.element);
2439
2497
  }
2440
- renderError(error, callback) {
2441
- ErrorRenderer.render(this.delegate, callback, error || "");
2498
+ get shouldCacheSnapshot() {
2499
+ return this.snapshot.isCacheable;
2442
2500
  }
2443
2501
  }
2444
2502
 
@@ -2446,7 +2504,7 @@ class Session {
2446
2504
  constructor() {
2447
2505
  this.navigator = new Navigator(this);
2448
2506
  this.history = new History(this);
2449
- this.view = new View(this);
2507
+ this.view = new PageView(this, document.documentElement);
2450
2508
  this.adapter = new BrowserAdapter(this);
2451
2509
  this.pageObserver = new PageObserver(this);
2452
2510
  this.linkClickObserver = new LinkClickObserver(this);
@@ -2490,7 +2548,7 @@ class Session {
2490
2548
  this.adapter = adapter;
2491
2549
  }
2492
2550
  visit(location, options = {}) {
2493
- this.navigator.proposeVisit(Location.wrap(location), options);
2551
+ this.navigator.proposeVisit(expandURL(location), options);
2494
2552
  }
2495
2553
  connectStreamSource(source) {
2496
2554
  this.streamObserver.connectStreamSource(source);
@@ -2531,15 +2589,17 @@ class Session {
2531
2589
  }
2532
2590
  followedLinkToLocation(link, location) {
2533
2591
  const action = this.getActionForLink(link);
2534
- this.visit(location, { action });
2592
+ this.visit(location.href, { action });
2535
2593
  }
2536
2594
  allowsVisitingLocation(location) {
2537
2595
  return this.applicationAllowsVisitingLocation(location);
2538
2596
  }
2539
2597
  visitProposedToLocation(location, options) {
2598
+ extendURLWithDeprecatedProperties(location);
2540
2599
  this.adapter.visitProposedToLocation(location, options);
2541
2600
  }
2542
2601
  visitStarted(visit) {
2602
+ extendURLWithDeprecatedProperties(visit.location);
2543
2603
  this.notifyApplicationAfterVisitingLocation(visit.location);
2544
2604
  }
2545
2605
  visitCompleted(visit) {
@@ -2564,19 +2624,19 @@ class Session {
2564
2624
  receivedMessageFromStream(message) {
2565
2625
  this.renderStreamMessage(message);
2566
2626
  }
2567
- viewWillRender(newBody) {
2568
- this.notifyApplicationBeforeRender(newBody);
2627
+ viewWillCacheSnapshot() {
2628
+ this.notifyApplicationBeforeCachingSnapshot();
2569
2629
  }
2570
- viewRendered() {
2630
+ viewWillRenderSnapshot({ element }, isPreview) {
2631
+ this.notifyApplicationBeforeRender(element);
2632
+ }
2633
+ viewRenderedSnapshot(snapshot, isPreview) {
2571
2634
  this.view.lastRenderedLocation = this.history.location;
2572
2635
  this.notifyApplicationAfterRender();
2573
2636
  }
2574
2637
  viewInvalidated() {
2575
2638
  this.adapter.pageInvalidated();
2576
2639
  }
2577
- viewWillCacheSnapshot() {
2578
- this.notifyApplicationBeforeCachingSnapshot();
2579
- }
2580
2640
  applicationAllowsFollowingLinkToLocation(link, location) {
2581
2641
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2582
2642
  return !event.defaultPrevented;
@@ -2586,13 +2646,13 @@ class Session {
2586
2646
  return !event.defaultPrevented;
2587
2647
  }
2588
2648
  notifyApplicationAfterClickingLinkToLocation(link, location) {
2589
- return dispatch("turbo:click", { target: link, detail: { url: location.absoluteURL }, cancelable: true });
2649
+ return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
2590
2650
  }
2591
2651
  notifyApplicationBeforeVisitingLocation(location) {
2592
- return dispatch("turbo:before-visit", { detail: { url: location.absoluteURL }, cancelable: true });
2652
+ return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2593
2653
  }
2594
2654
  notifyApplicationAfterVisitingLocation(location) {
2595
- return dispatch("turbo:visit", { detail: { url: location.absoluteURL } });
2655
+ return dispatch("turbo:visit", { detail: { url: location.href } });
2596
2656
  }
2597
2657
  notifyApplicationBeforeCachingSnapshot() {
2598
2658
  return dispatch("turbo:before-cache");
@@ -2604,7 +2664,7 @@ class Session {
2604
2664
  return dispatch("turbo:render");
2605
2665
  }
2606
2666
  notifyApplicationAfterPageLoad(timing = {}) {
2607
- return dispatch("turbo:load", { detail: { url: this.location.absoluteURL, timing } });
2667
+ return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2608
2668
  }
2609
2669
  getActionForLink(link) {
2610
2670
  const action = link.getAttribute("data-turbo-action");
@@ -2620,9 +2680,22 @@ class Session {
2620
2680
  }
2621
2681
  }
2622
2682
  locationIsVisitable(location) {
2623
- return location.isPrefixedBy(this.view.getRootLocation()) && location.isHTML();
2683
+ return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2624
2684
  }
2685
+ get snapshot() {
2686
+ return this.view.snapshot;
2687
+ }
2688
+ }
2689
+ function extendURLWithDeprecatedProperties(url) {
2690
+ Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2625
2691
  }
2692
+ const deprecatedLocationPropertyDescriptors = {
2693
+ absoluteURL: {
2694
+ get() {
2695
+ return this.toString();
2696
+ }
2697
+ }
2698
+ };
2626
2699
 
2627
2700
  const session = new Session;
2628
2701
  const { navigator } = session;