turbo-rails 0.5.6 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46e3c3f3a3e5ca9ce8fa61f1dc4736a049d042f2f88ca05a1dd2214be41dc787
4
- data.tar.gz: c8f12c5aa54df4f9bedb6e61105db5f4505e1ac233530c8438c38440ccc09664
3
+ metadata.gz: 349e14ec6f35af3029178e7ba854d16047aaf5e00ab700b1f2fb751170347518
4
+ data.tar.gz: b5177b878833a5abda62f208d787de1931d4db4cf3ba373a67458bee8c9d49a3
5
5
  SHA512:
6
- metadata.gz: 3d6f250e4f2cb9da7ce3aaba9f4635eb559759ea3bcb7da736190c916a8d62dd31d0e55f226890a7239f6ae25b1274a92e7e03cae3e34c9198e2ef1cd8c4d2ed
7
- data.tar.gz: c329e9503d82ec135ca670addadbaae27f491bb9629cf3df35325bf4e27be2ea95cd8bb981314ab6a6f0f152d53338da25743af00afb7971cb0d79214acb257d
6
+ metadata.gz: 6e0e11bc2c95419d394c758ff04e03dd734f85a45e61001e623c25bffdfc83530977637a89dbe5f173d81fd6dc4a86d81a1ff99ffedb4f23b00215e76c16ecad
7
+ data.tar.gz: 48dfec6f22f6eee01950f77c79c54f4d6a8fde629454ac2845b2d6bff6f8b464d18bcf7ede203a16a83dcc7421cbe33f92c74bc3a76124048ebbc897641ef388
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,554 @@ 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.currentURL = this.element.src;
2753
+ this.settingSourceURL = false;
2619
2754
  }
2620
- visitCompleted(visit) {
2621
- this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2755
+ get loadingStyle() {
2756
+ return this.element.loading;
2622
2757
  }
2623
- willSubmitForm(form, submitter) {
2624
- return this.elementIsNavigable(form) && this.elementIsNavigable(submitter);
2758
+ get isLoading() {
2759
+ return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
2625
2760
  }
2626
- formSubmitted(form, submitter) {
2627
- this.navigator.submitForm(form, submitter);
2761
+ get isActive() {
2762
+ return this.element.isActive && this.connected;
2628
2763
  }
2629
- pageBecameInteractive() {
2630
- this.view.lastRenderedLocation = this.location;
2631
- this.notifyApplicationAfterPageLoad();
2764
+ }
2765
+
2766
+ function getFrameElementById(id) {
2767
+ if (id != null) {
2768
+ const element = document.getElementById(id);
2769
+ if (element instanceof FrameElement) {
2770
+ return element;
2771
+ }
2632
2772
  }
2633
- pageLoaded() {
2634
- this.history.assumeControlOfScrollRestoration();
2773
+ }
2774
+
2775
+ function activateElement(element, currentURL) {
2776
+ if (element) {
2777
+ const src = element.getAttribute("src");
2778
+ if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
2779
+ throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
2780
+ }
2781
+ if (element.ownerDocument !== document) {
2782
+ element = document.importNode(element, true);
2783
+ }
2784
+ if (element instanceof FrameElement) {
2785
+ element.connectedCallback();
2786
+ return element;
2787
+ }
2635
2788
  }
2636
- pageWillUnload() {
2637
- this.history.relinquishControlOfScrollRestoration();
2789
+ }
2790
+
2791
+ const StreamActions = {
2792
+ after() {
2793
+ var _a, _b;
2794
+ (_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);
2795
+ },
2796
+ append() {
2797
+ var _a;
2798
+ this.removeDuplicateTargetChildren();
2799
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
2800
+ },
2801
+ before() {
2802
+ var _a, _b;
2803
+ (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
2804
+ },
2805
+ prepend() {
2806
+ var _a;
2807
+ this.removeDuplicateTargetChildren();
2808
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
2809
+ },
2810
+ remove() {
2811
+ var _a;
2812
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
2813
+ },
2814
+ replace() {
2815
+ var _a;
2816
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
2817
+ },
2818
+ update() {
2819
+ if (this.targetElement) {
2820
+ this.targetElement.innerHTML = "";
2821
+ this.targetElement.append(this.templateContent);
2822
+ }
2638
2823
  }
2639
- receivedMessageFromStream(message) {
2640
- this.renderStreamMessage(message);
2824
+ };
2825
+
2826
+ class StreamElement extends HTMLElement {
2827
+ async connectedCallback() {
2828
+ try {
2829
+ await this.render();
2830
+ } catch (error) {
2831
+ console.error(error);
2832
+ } finally {
2833
+ this.disconnect();
2834
+ }
2641
2835
  }
2642
- viewWillRender(newBody) {
2643
- this.notifyApplicationBeforeRender(newBody);
2836
+ async render() {
2837
+ var _a;
2838
+ return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
2839
+ if (this.dispatchEvent(this.beforeRenderEvent)) {
2840
+ await nextAnimationFrame();
2841
+ this.performAction();
2842
+ }
2843
+ })();
2644
2844
  }
2645
- viewRendered() {
2646
- this.view.lastRenderedLocation = this.history.location;
2647
- this.notifyApplicationAfterRender();
2845
+ disconnect() {
2846
+ try {
2847
+ this.remove();
2848
+ } catch (_a) {}
2648
2849
  }
2649
- viewInvalidated() {
2650
- this.adapter.pageInvalidated();
2850
+ removeDuplicateTargetChildren() {
2851
+ this.duplicateChildren.forEach((({targetChild: targetChild}) => {
2852
+ targetChild.remove();
2853
+ }));
2651
2854
  }
2652
- viewWillCacheSnapshot() {
2653
- this.notifyApplicationBeforeCachingSnapshot();
2855
+ get duplicateChildren() {
2856
+ var _a;
2857
+ return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
2858
+ let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
2859
+ return {
2860
+ targetChild: targetChild,
2861
+ templateChild: templateChild
2862
+ };
2863
+ })).filter((({targetChild: targetChild}) => targetChild));
2654
2864
  }
2655
- applicationAllowsFollowingLinkToLocation(link, location) {
2656
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2657
- return !event.defaultPrevented;
2865
+ get performAction() {
2866
+ if (this.action) {
2867
+ const actionFunction = StreamActions[this.action];
2868
+ if (actionFunction) {
2869
+ return actionFunction;
2870
+ }
2871
+ this.raise("unknown action");
2872
+ }
2873
+ this.raise("action attribute is missing");
2658
2874
  }
2659
- applicationAllowsVisitingLocation(location) {
2660
- const event = this.notifyApplicationBeforeVisitingLocation(location);
2661
- return !event.defaultPrevented;
2875
+ get targetElement() {
2876
+ var _a;
2877
+ if (this.target) {
2878
+ return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
2879
+ }
2880
+ this.raise("target attribute is missing");
2662
2881
  }
2663
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2664
- return dispatch("turbo:click", {
2665
- target: link,
2666
- detail: {
2667
- url: location.absoluteURL
2668
- },
2669
- cancelable: true
2670
- });
2882
+ get templateContent() {
2883
+ return this.templateElement.content;
2671
2884
  }
2672
- notifyApplicationBeforeVisitingLocation(location) {
2673
- return dispatch("turbo:before-visit", {
2674
- detail: {
2675
- url: location.absoluteURL
2676
- },
2677
- cancelable: true
2678
- });
2885
+ get templateElement() {
2886
+ if (this.firstElementChild instanceof HTMLTemplateElement) {
2887
+ return this.firstElementChild;
2888
+ }
2889
+ this.raise("first child element must be a <template> element");
2679
2890
  }
2680
- notifyApplicationAfterVisitingLocation(location) {
2681
- return dispatch("turbo:visit", {
2682
- detail: {
2683
- url: location.absoluteURL
2684
- }
2685
- });
2891
+ get action() {
2892
+ return this.getAttribute("action");
2686
2893
  }
2687
- notifyApplicationBeforeCachingSnapshot() {
2688
- return dispatch("turbo:before-cache");
2894
+ get target() {
2895
+ return this.getAttribute("target");
2689
2896
  }
2690
- notifyApplicationBeforeRender(newBody) {
2691
- return dispatch("turbo:before-render", {
2692
- detail: {
2693
- newBody: newBody
2694
- }
2695
- });
2897
+ raise(message) {
2898
+ throw new Error(`${this.description}: ${message}`);
2696
2899
  }
2697
- notifyApplicationAfterRender() {
2698
- return dispatch("turbo:render");
2900
+ get description() {
2901
+ var _a, _b;
2902
+ return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
2699
2903
  }
2700
- notifyApplicationAfterPageLoad(timing = {}) {
2701
- return dispatch("turbo:load", {
2702
- detail: {
2703
- url: this.location.absoluteURL,
2704
- timing: timing
2705
- }
2904
+ get beforeRenderEvent() {
2905
+ return new CustomEvent("turbo:before-stream-render", {
2906
+ bubbles: true,
2907
+ cancelable: true
2706
2908
  });
2707
2909
  }
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;
2910
+ }
2911
+
2912
+ FrameElement.delegateConstructor = FrameController;
2913
+
2914
+ customElements.define("turbo-frame", FrameElement);
2915
+
2916
+ customElements.define("turbo-stream", StreamElement);
2917
+
2918
+ (() => {
2919
+ let element = document.currentScript;
2920
+ if (!element) return;
2921
+ if (element.hasAttribute("data-turbo-suppress-warning")) return;
2922
+ while (element = element.parentElement) {
2923
+ if (element == document.body) {
2924
+ return console.warn(unindent`
2925
+ You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
2926
+
2927
+ Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
2928
+
2929
+ For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
2930
+
2931
+ ——
2932
+ Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
2933
+ `, element.outerHTML);
2718
2934
  }
2719
2935
  }
2720
- locationIsVisitable(location) {
2721
- return location.isPrefixedBy(this.view.getRootLocation()) && location.isHTML();
2722
- }
2723
- }
2936
+ })();
2724
2937
 
2725
2938
  const session = new Session;
2726
2939
 
@@ -2758,6 +2971,21 @@ function setProgressBarDelay(delay) {
2758
2971
  session.setProgressBarDelay(delay);
2759
2972
  }
2760
2973
 
2974
+ var Turbo = Object.freeze({
2975
+ __proto__: null,
2976
+ navigator: navigator,
2977
+ start: start,
2978
+ registerAdapter: registerAdapter,
2979
+ visit: visit,
2980
+ connectStreamSource: connectStreamSource,
2981
+ disconnectStreamSource: disconnectStreamSource,
2982
+ renderStreamMessage: renderStreamMessage,
2983
+ clearCache: clearCache,
2984
+ setProgressBarDelay: setProgressBarDelay
2985
+ });
2986
+
2987
+ window.Turbo = Turbo;
2988
+
2761
2989
  start();
2762
2990
 
2763
2991
  var turbo_es2017Esm = Object.freeze({
@@ -2776,17 +3004,20 @@ var turbo_es2017Esm = Object.freeze({
2776
3004
  let consumer;
2777
3005
 
2778
3006
  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());
3007
+ return consumer || setConsumer(createConsumer().then(setConsumer));
2784
3008
  }
2785
3009
 
2786
3010
  function setConsumer(newConsumer) {
2787
3011
  return consumer = newConsumer;
2788
3012
  }
2789
3013
 
3014
+ async function createConsumer() {
3015
+ const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
3016
+ return index;
3017
+ }));
3018
+ return createConsumer();
3019
+ }
3020
+
2790
3021
  async function subscribeTo(channel, mixin) {
2791
3022
  const {subscriptions: subscriptions} = await getConsumer();
2792
3023
  return subscriptions.create(channel, mixin);
@@ -2796,6 +3027,7 @@ var cable = Object.freeze({
2796
3027
  __proto__: null,
2797
3028
  getConsumer: getConsumer,
2798
3029
  setConsumer: setConsumer,
3030
+ createConsumer: createConsumer,
2799
3031
  subscribeTo: subscribeTo
2800
3032
  });
2801
3033
 
@@ -3259,7 +3491,7 @@ function createWebSocketURL(url) {
3259
3491
  }
3260
3492
  }
3261
3493
 
3262
- function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
3494
+ function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
3263
3495
  return new Consumer(url);
3264
3496
  }
3265
3497
 
@@ -3281,7 +3513,7 @@ var index = Object.freeze({
3281
3513
  adapters: adapters,
3282
3514
  createWebSocketURL: createWebSocketURL,
3283
3515
  logger: logger,
3284
- createConsumer: createConsumer,
3516
+ createConsumer: createConsumer$1,
3285
3517
  getConfig: getConfig
3286
3518
  });
3287
3519