turbo-rails 2.0.2 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +113 -7
- data/app/assets/javascripts/turbo.js +1214 -1064
- data/app/assets/javascripts/turbo.min.js +7 -7
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +34 -9
- data/app/channels/turbo/streams_channel.rb +15 -15
- data/app/controllers/turbo/frames/frame_request.rb +2 -2
- data/app/controllers/turbo/native/navigation.rb +11 -8
- data/app/helpers/turbo/drive_helper.rb +2 -2
- data/app/helpers/turbo/streams/action_helper.rb +4 -3
- data/app/helpers/turbo/streams_helper.rb +6 -0
- data/app/javascript/turbo/cable_stream_source_element.js +10 -0
- data/app/models/concerns/turbo/broadcastable.rb +50 -32
- data/app/models/turbo/streams/tag_builder.rb +32 -12
- data/app/views/layouts/turbo_rails/frame.html.erb +1 -0
- data/config/routes.rb +3 -3
- data/lib/tasks/turbo_tasks.rake +0 -22
- data/lib/turbo/engine.rb +74 -10
- data/lib/turbo/system_test_helper.rb +128 -0
- data/lib/turbo/version.rb +1 -1
- metadata +11 -24
- data/lib/install/turbo_needs_redis.rb +0 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.
|
|
3
|
-
Copyright ©
|
|
2
|
+
Turbo 8.0.13
|
|
3
|
+
Copyright © 2025 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function(prototype) {
|
|
6
6
|
if (typeof prototype.requestSubmit == "function") return;
|
|
@@ -72,7 +72,7 @@ class FrameElement extends HTMLElement {
|
|
|
72
72
|
static delegateConstructor=undefined;
|
|
73
73
|
loaded=Promise.resolve();
|
|
74
74
|
static get observedAttributes() {
|
|
75
|
-
return [ "disabled", "
|
|
75
|
+
return [ "disabled", "loading", "src" ];
|
|
76
76
|
}
|
|
77
77
|
constructor() {
|
|
78
78
|
super();
|
|
@@ -90,11 +90,9 @@ class FrameElement extends HTMLElement {
|
|
|
90
90
|
attributeChangedCallback(name) {
|
|
91
91
|
if (name == "loading") {
|
|
92
92
|
this.delegate.loadingStyleChanged();
|
|
93
|
-
} else if (name == "complete") {
|
|
94
|
-
this.delegate.completeChanged();
|
|
95
93
|
} else if (name == "src") {
|
|
96
94
|
this.delegate.sourceURLChanged();
|
|
97
|
-
} else {
|
|
95
|
+
} else if (name == "disabled") {
|
|
98
96
|
this.delegate.disabledChanged();
|
|
99
97
|
}
|
|
100
98
|
}
|
|
@@ -118,6 +116,9 @@ class FrameElement extends HTMLElement {
|
|
|
118
116
|
this.removeAttribute("refresh");
|
|
119
117
|
}
|
|
120
118
|
}
|
|
119
|
+
get shouldReloadWithMorph() {
|
|
120
|
+
return this.src && this.refresh === "morph";
|
|
121
|
+
}
|
|
121
122
|
get loading() {
|
|
122
123
|
return frameLoadingStyleFromString(this.getAttribute("loading") || "");
|
|
123
124
|
}
|
|
@@ -169,122 +170,18 @@ function frameLoadingStyleFromString(style) {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
let anchorMatch;
|
|
178
|
-
if (url.hash) {
|
|
179
|
-
return url.hash.slice(1);
|
|
180
|
-
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
|
181
|
-
return anchorMatch[1];
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function getAction$1(form, submitter) {
|
|
186
|
-
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
|
187
|
-
return expandURL(action);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function getExtension(url) {
|
|
191
|
-
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function isHTML(url) {
|
|
195
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function isPrefixedBy(baseURL, url) {
|
|
199
|
-
const prefix = getPrefix(url);
|
|
200
|
-
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function locationIsVisitable(location, rootLocation) {
|
|
204
|
-
return isPrefixedBy(location, rootLocation) && isHTML(location);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function getRequestURL(url) {
|
|
208
|
-
const anchor = getAnchor(url);
|
|
209
|
-
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function toCacheKey(url) {
|
|
213
|
-
return getRequestURL(url);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function urlsAreEqual(left, right) {
|
|
217
|
-
return expandURL(left).href == expandURL(right).href;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function getPathComponents(url) {
|
|
221
|
-
return url.pathname.split("/").slice(1);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function getLastPathComponent(url) {
|
|
225
|
-
return getPathComponents(url).slice(-1)[0];
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function getPrefix(url) {
|
|
229
|
-
return addTrailingSlash(url.origin + url.pathname);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function addTrailingSlash(value) {
|
|
233
|
-
return value.endsWith("/") ? value : value + "/";
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
class FetchResponse {
|
|
237
|
-
constructor(response) {
|
|
238
|
-
this.response = response;
|
|
239
|
-
}
|
|
240
|
-
get succeeded() {
|
|
241
|
-
return this.response.ok;
|
|
242
|
-
}
|
|
243
|
-
get failed() {
|
|
244
|
-
return !this.succeeded;
|
|
245
|
-
}
|
|
246
|
-
get clientError() {
|
|
247
|
-
return this.statusCode >= 400 && this.statusCode <= 499;
|
|
248
|
-
}
|
|
249
|
-
get serverError() {
|
|
250
|
-
return this.statusCode >= 500 && this.statusCode <= 599;
|
|
251
|
-
}
|
|
252
|
-
get redirected() {
|
|
253
|
-
return this.response.redirected;
|
|
254
|
-
}
|
|
255
|
-
get location() {
|
|
256
|
-
return expandURL(this.response.url);
|
|
257
|
-
}
|
|
258
|
-
get isHTML() {
|
|
259
|
-
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
|
260
|
-
}
|
|
261
|
-
get statusCode() {
|
|
262
|
-
return this.response.status;
|
|
263
|
-
}
|
|
264
|
-
get contentType() {
|
|
265
|
-
return this.header("Content-Type");
|
|
266
|
-
}
|
|
267
|
-
get responseText() {
|
|
268
|
-
return this.response.clone().text();
|
|
269
|
-
}
|
|
270
|
-
get responseHTML() {
|
|
271
|
-
if (this.isHTML) {
|
|
272
|
-
return this.response.clone().text();
|
|
273
|
-
} else {
|
|
274
|
-
return Promise.resolve(undefined);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
header(name) {
|
|
278
|
-
return this.response.headers.get(name);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
173
|
+
const drive = {
|
|
174
|
+
enabled: true,
|
|
175
|
+
progressBarDelay: 500,
|
|
176
|
+
unvisitableExtensions: new Set([ ".7z", ".aac", ".apk", ".avi", ".bmp", ".bz2", ".css", ".csv", ".deb", ".dmg", ".doc", ".docx", ".exe", ".gif", ".gz", ".heic", ".heif", ".ico", ".iso", ".jpeg", ".jpg", ".js", ".json", ".m4a", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi", ".ogg", ".ogv", ".pdf", ".pkg", ".png", ".ppt", ".pptx", ".rar", ".rtf", ".svg", ".tar", ".tif", ".tiff", ".txt", ".wav", ".webm", ".webp", ".wma", ".wmv", ".xls", ".xlsx", ".xml", ".zip" ])
|
|
177
|
+
};
|
|
281
178
|
|
|
282
179
|
function activateScriptElement(element) {
|
|
283
180
|
if (element.getAttribute("data-turbo-eval") == "false") {
|
|
284
181
|
return element;
|
|
285
182
|
} else {
|
|
286
183
|
const createdScriptElement = document.createElement("script");
|
|
287
|
-
const cspNonce =
|
|
184
|
+
const cspNonce = getCspNonce();
|
|
288
185
|
if (cspNonce) {
|
|
289
186
|
createdScriptElement.nonce = cspNonce;
|
|
290
187
|
}
|
|
@@ -322,6 +219,11 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
|
322
219
|
return event;
|
|
323
220
|
}
|
|
324
221
|
|
|
222
|
+
function cancelEvent(event) {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
event.stopImmediatePropagation();
|
|
225
|
+
}
|
|
226
|
+
|
|
325
227
|
function nextRepaint() {
|
|
326
228
|
if (document.visibilityState === "hidden") {
|
|
327
229
|
return nextEventLoopTick();
|
|
@@ -451,6 +353,14 @@ function getMetaContent(name) {
|
|
|
451
353
|
return element && element.content;
|
|
452
354
|
}
|
|
453
355
|
|
|
356
|
+
function getCspNonce() {
|
|
357
|
+
const element = getMetaElement("csp-nonce");
|
|
358
|
+
if (element) {
|
|
359
|
+
const {nonce: nonce, content: content} = element;
|
|
360
|
+
return nonce == "" ? content : nonce;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
454
364
|
function setMetaContent(name, content) {
|
|
455
365
|
let element = getMetaElement(name);
|
|
456
366
|
if (!element) {
|
|
@@ -485,13 +395,17 @@ async function around(callback, reader) {
|
|
|
485
395
|
return [ before, after ];
|
|
486
396
|
}
|
|
487
397
|
|
|
488
|
-
function doesNotTargetIFrame(
|
|
489
|
-
if (
|
|
490
|
-
|
|
398
|
+
function doesNotTargetIFrame(name) {
|
|
399
|
+
if (name === "_blank") {
|
|
400
|
+
return false;
|
|
401
|
+
} else if (name) {
|
|
402
|
+
for (const element of document.getElementsByName(name)) {
|
|
491
403
|
if (element instanceof HTMLIFrameElement) return false;
|
|
492
404
|
}
|
|
405
|
+
return true;
|
|
406
|
+
} else {
|
|
407
|
+
return true;
|
|
493
408
|
}
|
|
494
|
-
return true;
|
|
495
409
|
}
|
|
496
410
|
|
|
497
411
|
function findLinkFromClickTarget(target) {
|
|
@@ -511,6 +425,152 @@ function debounce(fn, delay) {
|
|
|
511
425
|
};
|
|
512
426
|
}
|
|
513
427
|
|
|
428
|
+
const submitter = {
|
|
429
|
+
"aria-disabled": {
|
|
430
|
+
beforeSubmit: submitter => {
|
|
431
|
+
submitter.setAttribute("aria-disabled", "true");
|
|
432
|
+
submitter.addEventListener("click", cancelEvent);
|
|
433
|
+
},
|
|
434
|
+
afterSubmit: submitter => {
|
|
435
|
+
submitter.removeAttribute("aria-disabled");
|
|
436
|
+
submitter.removeEventListener("click", cancelEvent);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
disabled: {
|
|
440
|
+
beforeSubmit: submitter => submitter.disabled = true,
|
|
441
|
+
afterSubmit: submitter => submitter.disabled = false
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
class Config {
|
|
446
|
+
#submitter=null;
|
|
447
|
+
constructor(config) {
|
|
448
|
+
Object.assign(this, config);
|
|
449
|
+
}
|
|
450
|
+
get submitter() {
|
|
451
|
+
return this.#submitter;
|
|
452
|
+
}
|
|
453
|
+
set submitter(value) {
|
|
454
|
+
this.#submitter = submitter[value] || value;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const forms = new Config({
|
|
459
|
+
mode: "on",
|
|
460
|
+
submitter: "disabled"
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const config = {
|
|
464
|
+
drive: drive,
|
|
465
|
+
forms: forms
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
function expandURL(locatable) {
|
|
469
|
+
return new URL(locatable.toString(), document.baseURI);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function getAnchor(url) {
|
|
473
|
+
let anchorMatch;
|
|
474
|
+
if (url.hash) {
|
|
475
|
+
return url.hash.slice(1);
|
|
476
|
+
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
|
477
|
+
return anchorMatch[1];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function getAction$1(form, submitter) {
|
|
482
|
+
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
|
483
|
+
return expandURL(action);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function getExtension(url) {
|
|
487
|
+
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function isPrefixedBy(baseURL, url) {
|
|
491
|
+
const prefix = getPrefix(url);
|
|
492
|
+
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function locationIsVisitable(location, rootLocation) {
|
|
496
|
+
return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getRequestURL(url) {
|
|
500
|
+
const anchor = getAnchor(url);
|
|
501
|
+
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function toCacheKey(url) {
|
|
505
|
+
return getRequestURL(url);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function urlsAreEqual(left, right) {
|
|
509
|
+
return expandURL(left).href == expandURL(right).href;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function getPathComponents(url) {
|
|
513
|
+
return url.pathname.split("/").slice(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getLastPathComponent(url) {
|
|
517
|
+
return getPathComponents(url).slice(-1)[0];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function getPrefix(url) {
|
|
521
|
+
return addTrailingSlash(url.origin + url.pathname);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function addTrailingSlash(value) {
|
|
525
|
+
return value.endsWith("/") ? value : value + "/";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
class FetchResponse {
|
|
529
|
+
constructor(response) {
|
|
530
|
+
this.response = response;
|
|
531
|
+
}
|
|
532
|
+
get succeeded() {
|
|
533
|
+
return this.response.ok;
|
|
534
|
+
}
|
|
535
|
+
get failed() {
|
|
536
|
+
return !this.succeeded;
|
|
537
|
+
}
|
|
538
|
+
get clientError() {
|
|
539
|
+
return this.statusCode >= 400 && this.statusCode <= 499;
|
|
540
|
+
}
|
|
541
|
+
get serverError() {
|
|
542
|
+
return this.statusCode >= 500 && this.statusCode <= 599;
|
|
543
|
+
}
|
|
544
|
+
get redirected() {
|
|
545
|
+
return this.response.redirected;
|
|
546
|
+
}
|
|
547
|
+
get location() {
|
|
548
|
+
return expandURL(this.response.url);
|
|
549
|
+
}
|
|
550
|
+
get isHTML() {
|
|
551
|
+
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
|
552
|
+
}
|
|
553
|
+
get statusCode() {
|
|
554
|
+
return this.response.status;
|
|
555
|
+
}
|
|
556
|
+
get contentType() {
|
|
557
|
+
return this.header("Content-Type");
|
|
558
|
+
}
|
|
559
|
+
get responseText() {
|
|
560
|
+
return this.response.clone().text();
|
|
561
|
+
}
|
|
562
|
+
get responseHTML() {
|
|
563
|
+
if (this.isHTML) {
|
|
564
|
+
return this.response.clone().text();
|
|
565
|
+
} else {
|
|
566
|
+
return Promise.resolve(undefined);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
header(name) {
|
|
570
|
+
return this.response.headers.get(name);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
514
574
|
class LimitedSet extends Set {
|
|
515
575
|
constructor(maxSize) {
|
|
516
576
|
super();
|
|
@@ -598,7 +658,7 @@ class FetchRequest {
|
|
|
598
658
|
this.fetchOptions = {
|
|
599
659
|
credentials: "same-origin",
|
|
600
660
|
redirect: "follow",
|
|
601
|
-
method: method,
|
|
661
|
+
method: method.toUpperCase(),
|
|
602
662
|
headers: {
|
|
603
663
|
...this.defaultHeaders
|
|
604
664
|
},
|
|
@@ -618,7 +678,7 @@ class FetchRequest {
|
|
|
618
678
|
const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
|
619
679
|
this.url = url;
|
|
620
680
|
this.fetchOptions.body = body;
|
|
621
|
-
this.fetchOptions.method = fetchMethod;
|
|
681
|
+
this.fetchOptions.method = fetchMethod.toUpperCase();
|
|
622
682
|
}
|
|
623
683
|
get headers() {
|
|
624
684
|
return this.fetchOptions.headers;
|
|
@@ -859,7 +919,7 @@ const FormSubmissionState = {
|
|
|
859
919
|
|
|
860
920
|
class FormSubmission {
|
|
861
921
|
state=FormSubmissionState.initialized;
|
|
862
|
-
static confirmMethod(message
|
|
922
|
+
static confirmMethod(message) {
|
|
863
923
|
return Promise.resolve(confirm(message));
|
|
864
924
|
}
|
|
865
925
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
|
@@ -901,7 +961,8 @@ class FormSubmission {
|
|
|
901
961
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
|
902
962
|
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
|
903
963
|
if (typeof confirmationMessage === "string") {
|
|
904
|
-
const
|
|
964
|
+
const confirmMethod = typeof config.forms.confirm === "function" ? config.forms.confirm : FormSubmission.confirmMethod;
|
|
965
|
+
const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
|
905
966
|
if (!answer) {
|
|
906
967
|
return;
|
|
907
968
|
}
|
|
@@ -932,7 +993,7 @@ class FormSubmission {
|
|
|
932
993
|
}
|
|
933
994
|
requestStarted(_request) {
|
|
934
995
|
this.state = FormSubmissionState.waiting;
|
|
935
|
-
this.submitter
|
|
996
|
+
if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);
|
|
936
997
|
this.setSubmitsWith();
|
|
937
998
|
markAsBusy(this.formElement);
|
|
938
999
|
dispatch("turbo:submit-start", {
|
|
@@ -984,7 +1045,7 @@ class FormSubmission {
|
|
|
984
1045
|
}
|
|
985
1046
|
requestFinished(_request) {
|
|
986
1047
|
this.state = FormSubmissionState.stopped;
|
|
987
|
-
this.submitter
|
|
1048
|
+
if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);
|
|
988
1049
|
this.resetSubmitterText();
|
|
989
1050
|
clearBusyState(this.formElement);
|
|
990
1051
|
dispatch("turbo:submit-end", {
|
|
@@ -1168,15 +1229,8 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
|
1168
1229
|
}
|
|
1169
1230
|
|
|
1170
1231
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
for (const element of document.getElementsByName(target)) {
|
|
1174
|
-
if (element instanceof HTMLIFrameElement) return false;
|
|
1175
|
-
}
|
|
1176
|
-
return true;
|
|
1177
|
-
} else {
|
|
1178
|
-
return true;
|
|
1179
|
-
}
|
|
1232
|
+
const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
|
1233
|
+
return doesNotTargetIFrame(target);
|
|
1180
1234
|
}
|
|
1181
1235
|
|
|
1182
1236
|
class View {
|
|
@@ -1309,14 +1363,14 @@ class LinkInterceptor {
|
|
|
1309
1363
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
|
1310
1364
|
}
|
|
1311
1365
|
clickBubbled=event => {
|
|
1312
|
-
if (this.
|
|
1366
|
+
if (this.clickEventIsSignificant(event)) {
|
|
1313
1367
|
this.clickEvent = event;
|
|
1314
1368
|
} else {
|
|
1315
1369
|
delete this.clickEvent;
|
|
1316
1370
|
}
|
|
1317
1371
|
};
|
|
1318
1372
|
linkClicked=event => {
|
|
1319
|
-
if (this.clickEvent && this.
|
|
1373
|
+
if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
|
1320
1374
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
|
1321
1375
|
this.clickEvent.preventDefault();
|
|
1322
1376
|
event.preventDefault();
|
|
@@ -1328,9 +1382,10 @@ class LinkInterceptor {
|
|
|
1328
1382
|
willVisit=_event => {
|
|
1329
1383
|
delete this.clickEvent;
|
|
1330
1384
|
};
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1385
|
+
clickEventIsSignificant(event) {
|
|
1386
|
+
const target = event.composed ? event.target?.parentElement : event.target;
|
|
1387
|
+
const element = findLinkFromClickTarget(target) || target;
|
|
1388
|
+
return element instanceof Element && element.closest("turbo-frame, html") == this.element;
|
|
1334
1389
|
}
|
|
1335
1390
|
}
|
|
1336
1391
|
|
|
@@ -1360,7 +1415,7 @@ class LinkClickObserver {
|
|
|
1360
1415
|
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
|
1361
1416
|
const target = event.composedPath && event.composedPath()[0] || event.target;
|
|
1362
1417
|
const link = findLinkFromClickTarget(target);
|
|
1363
|
-
if (link && doesNotTargetIFrame(link)) {
|
|
1418
|
+
if (link && doesNotTargetIFrame(link.target)) {
|
|
1364
1419
|
const location = getLocationForLink(link);
|
|
1365
1420
|
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
1366
1421
|
event.preventDefault();
|
|
@@ -1484,12 +1539,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
1484
1539
|
|
|
1485
1540
|
class Renderer {
|
|
1486
1541
|
#activeElement=null;
|
|
1487
|
-
|
|
1542
|
+
static renderElement(currentElement, newElement) {}
|
|
1543
|
+
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
1488
1544
|
this.currentSnapshot = currentSnapshot;
|
|
1489
1545
|
this.newSnapshot = newSnapshot;
|
|
1490
1546
|
this.isPreview = isPreview;
|
|
1491
1547
|
this.willRender = willRender;
|
|
1492
|
-
this.renderElement = renderElement;
|
|
1548
|
+
this.renderElement = this.constructor.renderElement;
|
|
1493
1549
|
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
|
1494
1550
|
resolve: resolve,
|
|
1495
1551
|
reject: reject
|
|
@@ -1498,6 +1554,9 @@ class Renderer {
|
|
|
1498
1554
|
get shouldRender() {
|
|
1499
1555
|
return true;
|
|
1500
1556
|
}
|
|
1557
|
+
get shouldAutofocus() {
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1501
1560
|
get reloadReason() {
|
|
1502
1561
|
return;
|
|
1503
1562
|
}
|
|
@@ -1515,9 +1574,11 @@ class Renderer {
|
|
|
1515
1574
|
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
|
1516
1575
|
}
|
|
1517
1576
|
focusFirstAutofocusableElement() {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
element
|
|
1577
|
+
if (this.shouldAutofocus) {
|
|
1578
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
1579
|
+
if (element) {
|
|
1580
|
+
element.focus();
|
|
1581
|
+
}
|
|
1521
1582
|
}
|
|
1522
1583
|
}
|
|
1523
1584
|
enteringBardo(currentPermanentElement) {
|
|
@@ -1625,10 +1686,675 @@ function readScrollBehavior(value, defaultValue) {
|
|
|
1625
1686
|
}
|
|
1626
1687
|
}
|
|
1627
1688
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1689
|
+
var Idiomorph = function() {
|
|
1690
|
+
const noOp = () => {};
|
|
1691
|
+
const defaults = {
|
|
1692
|
+
morphStyle: "outerHTML",
|
|
1693
|
+
callbacks: {
|
|
1694
|
+
beforeNodeAdded: noOp,
|
|
1695
|
+
afterNodeAdded: noOp,
|
|
1696
|
+
beforeNodeMorphed: noOp,
|
|
1697
|
+
afterNodeMorphed: noOp,
|
|
1698
|
+
beforeNodeRemoved: noOp,
|
|
1699
|
+
afterNodeRemoved: noOp,
|
|
1700
|
+
beforeAttributeUpdated: noOp
|
|
1701
|
+
},
|
|
1702
|
+
head: {
|
|
1703
|
+
style: "merge",
|
|
1704
|
+
shouldPreserve: elt => elt.getAttribute("im-preserve") === "true",
|
|
1705
|
+
shouldReAppend: elt => elt.getAttribute("im-re-append") === "true",
|
|
1706
|
+
shouldRemove: noOp,
|
|
1707
|
+
afterHeadMorphed: noOp
|
|
1708
|
+
},
|
|
1709
|
+
restoreFocus: true
|
|
1710
|
+
};
|
|
1711
|
+
function morph(oldNode, newContent, config = {}) {
|
|
1712
|
+
oldNode = normalizeElement(oldNode);
|
|
1713
|
+
const newNode = normalizeParent(newContent);
|
|
1714
|
+
const ctx = createMorphContext(oldNode, newNode, config);
|
|
1715
|
+
const morphedNodes = saveAndRestoreFocus(ctx, (() => withHeadBlocking(ctx, oldNode, newNode, (ctx => {
|
|
1716
|
+
if (ctx.morphStyle === "innerHTML") {
|
|
1717
|
+
morphChildren(ctx, oldNode, newNode);
|
|
1718
|
+
return Array.from(oldNode.childNodes);
|
|
1719
|
+
} else {
|
|
1720
|
+
return morphOuterHTML(ctx, oldNode, newNode);
|
|
1721
|
+
}
|
|
1722
|
+
}))));
|
|
1723
|
+
ctx.pantry.remove();
|
|
1724
|
+
return morphedNodes;
|
|
1725
|
+
}
|
|
1726
|
+
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
1727
|
+
const oldParent = normalizeParent(oldNode);
|
|
1728
|
+
let childNodes = Array.from(oldParent.childNodes);
|
|
1729
|
+
const index = childNodes.indexOf(oldNode);
|
|
1730
|
+
const rightMargin = childNodes.length - (index + 1);
|
|
1731
|
+
morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
|
|
1732
|
+
childNodes = Array.from(oldParent.childNodes);
|
|
1733
|
+
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
1734
|
+
}
|
|
1735
|
+
function saveAndRestoreFocus(ctx, fn) {
|
|
1736
|
+
if (!ctx.config.restoreFocus) return fn();
|
|
1737
|
+
let activeElement = document.activeElement;
|
|
1738
|
+
if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) {
|
|
1739
|
+
return fn();
|
|
1740
|
+
}
|
|
1741
|
+
const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
|
|
1742
|
+
const results = fn();
|
|
1743
|
+
if (activeElementId && activeElementId !== document.activeElement?.id) {
|
|
1744
|
+
activeElement = ctx.target.querySelector(`#${activeElementId}`);
|
|
1745
|
+
activeElement?.focus();
|
|
1746
|
+
}
|
|
1747
|
+
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
1748
|
+
activeElement.setSelectionRange(selectionStart, selectionEnd);
|
|
1749
|
+
}
|
|
1750
|
+
return results;
|
|
1751
|
+
}
|
|
1752
|
+
const morphChildren = function() {
|
|
1753
|
+
function morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) {
|
|
1754
|
+
if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
|
|
1755
|
+
oldParent = oldParent.content;
|
|
1756
|
+
newParent = newParent.content;
|
|
1757
|
+
}
|
|
1758
|
+
insertionPoint ||= oldParent.firstChild;
|
|
1759
|
+
for (const newChild of newParent.childNodes) {
|
|
1760
|
+
if (insertionPoint && insertionPoint != endPoint) {
|
|
1761
|
+
const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint);
|
|
1762
|
+
if (bestMatch) {
|
|
1763
|
+
if (bestMatch !== insertionPoint) {
|
|
1764
|
+
removeNodesBetween(ctx, insertionPoint, bestMatch);
|
|
1765
|
+
}
|
|
1766
|
+
morphNode(bestMatch, newChild, ctx);
|
|
1767
|
+
insertionPoint = bestMatch.nextSibling;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
|
|
1772
|
+
const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx);
|
|
1773
|
+
morphNode(movedChild, newChild, ctx);
|
|
1774
|
+
insertionPoint = movedChild.nextSibling;
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
|
|
1778
|
+
if (insertedNode) {
|
|
1779
|
+
insertionPoint = insertedNode.nextSibling;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
while (insertionPoint && insertionPoint != endPoint) {
|
|
1783
|
+
const tempNode = insertionPoint;
|
|
1784
|
+
insertionPoint = insertionPoint.nextSibling;
|
|
1785
|
+
removeNode(ctx, tempNode);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function createNode(oldParent, newChild, insertionPoint, ctx) {
|
|
1789
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
|
|
1790
|
+
if (ctx.idMap.has(newChild)) {
|
|
1791
|
+
const newEmptyChild = document.createElement(newChild.tagName);
|
|
1792
|
+
oldParent.insertBefore(newEmptyChild, insertionPoint);
|
|
1793
|
+
morphNode(newEmptyChild, newChild, ctx);
|
|
1794
|
+
ctx.callbacks.afterNodeAdded(newEmptyChild);
|
|
1795
|
+
return newEmptyChild;
|
|
1796
|
+
} else {
|
|
1797
|
+
const newClonedChild = document.importNode(newChild, true);
|
|
1798
|
+
oldParent.insertBefore(newClonedChild, insertionPoint);
|
|
1799
|
+
ctx.callbacks.afterNodeAdded(newClonedChild);
|
|
1800
|
+
return newClonedChild;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
const findBestMatch = function() {
|
|
1804
|
+
function findBestMatch(ctx, node, startPoint, endPoint) {
|
|
1805
|
+
let softMatch = null;
|
|
1806
|
+
let nextSibling = node.nextSibling;
|
|
1807
|
+
let siblingSoftMatchCount = 0;
|
|
1808
|
+
let cursor = startPoint;
|
|
1809
|
+
while (cursor && cursor != endPoint) {
|
|
1810
|
+
if (isSoftMatch(cursor, node)) {
|
|
1811
|
+
if (isIdSetMatch(ctx, cursor, node)) {
|
|
1812
|
+
return cursor;
|
|
1813
|
+
}
|
|
1814
|
+
if (softMatch === null) {
|
|
1815
|
+
if (!ctx.idMap.has(cursor)) {
|
|
1816
|
+
softMatch = cursor;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
|
|
1821
|
+
siblingSoftMatchCount++;
|
|
1822
|
+
nextSibling = nextSibling.nextSibling;
|
|
1823
|
+
if (siblingSoftMatchCount >= 2) {
|
|
1824
|
+
softMatch = undefined;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
if (cursor.contains(document.activeElement)) break;
|
|
1828
|
+
cursor = cursor.nextSibling;
|
|
1829
|
+
}
|
|
1830
|
+
return softMatch || null;
|
|
1831
|
+
}
|
|
1832
|
+
function isIdSetMatch(ctx, oldNode, newNode) {
|
|
1833
|
+
let oldSet = ctx.idMap.get(oldNode);
|
|
1834
|
+
let newSet = ctx.idMap.get(newNode);
|
|
1835
|
+
if (!newSet || !oldSet) return false;
|
|
1836
|
+
for (const id of oldSet) {
|
|
1837
|
+
if (newSet.has(id)) {
|
|
1838
|
+
return true;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
function isSoftMatch(oldNode, newNode) {
|
|
1844
|
+
const oldElt = oldNode;
|
|
1845
|
+
const newElt = newNode;
|
|
1846
|
+
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
|
|
1847
|
+
}
|
|
1848
|
+
return findBestMatch;
|
|
1849
|
+
}();
|
|
1850
|
+
function removeNode(ctx, node) {
|
|
1851
|
+
if (ctx.idMap.has(node)) {
|
|
1852
|
+
moveBefore(ctx.pantry, node, null);
|
|
1853
|
+
} else {
|
|
1854
|
+
if (ctx.callbacks.beforeNodeRemoved(node) === false) return;
|
|
1855
|
+
node.parentNode?.removeChild(node);
|
|
1856
|
+
ctx.callbacks.afterNodeRemoved(node);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
function removeNodesBetween(ctx, startInclusive, endExclusive) {
|
|
1860
|
+
let cursor = startInclusive;
|
|
1861
|
+
while (cursor && cursor !== endExclusive) {
|
|
1862
|
+
let tempNode = cursor;
|
|
1863
|
+
cursor = cursor.nextSibling;
|
|
1864
|
+
removeNode(ctx, tempNode);
|
|
1865
|
+
}
|
|
1866
|
+
return cursor;
|
|
1867
|
+
}
|
|
1868
|
+
function moveBeforeById(parentNode, id, after, ctx) {
|
|
1869
|
+
const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`);
|
|
1870
|
+
removeElementFromAncestorsIdMaps(target, ctx);
|
|
1871
|
+
moveBefore(parentNode, target, after);
|
|
1872
|
+
return target;
|
|
1873
|
+
}
|
|
1874
|
+
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
1875
|
+
const id = element.id;
|
|
1876
|
+
while (element = element.parentNode) {
|
|
1877
|
+
let idSet = ctx.idMap.get(element);
|
|
1878
|
+
if (idSet) {
|
|
1879
|
+
idSet.delete(id);
|
|
1880
|
+
if (!idSet.size) {
|
|
1881
|
+
ctx.idMap.delete(element);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
function moveBefore(parentNode, element, after) {
|
|
1887
|
+
if (parentNode.moveBefore) {
|
|
1888
|
+
try {
|
|
1889
|
+
parentNode.moveBefore(element, after);
|
|
1890
|
+
} catch (e) {
|
|
1891
|
+
parentNode.insertBefore(element, after);
|
|
1892
|
+
}
|
|
1893
|
+
} else {
|
|
1894
|
+
parentNode.insertBefore(element, after);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return morphChildren;
|
|
1898
|
+
}();
|
|
1899
|
+
const morphNode = function() {
|
|
1900
|
+
function morphNode(oldNode, newContent, ctx) {
|
|
1901
|
+
if (ctx.ignoreActive && oldNode === document.activeElement) {
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {
|
|
1905
|
+
return oldNode;
|
|
1906
|
+
}
|
|
1907
|
+
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
|
1908
|
+
handleHeadElement(oldNode, newContent, ctx);
|
|
1909
|
+
} else {
|
|
1910
|
+
morphAttributes(oldNode, newContent, ctx);
|
|
1911
|
+
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
|
1912
|
+
morphChildren(ctx, oldNode, newContent);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
|
1916
|
+
return oldNode;
|
|
1917
|
+
}
|
|
1918
|
+
function morphAttributes(oldNode, newNode, ctx) {
|
|
1919
|
+
let type = newNode.nodeType;
|
|
1920
|
+
if (type === 1) {
|
|
1921
|
+
const oldElt = oldNode;
|
|
1922
|
+
const newElt = newNode;
|
|
1923
|
+
const oldAttributes = oldElt.attributes;
|
|
1924
|
+
const newAttributes = newElt.attributes;
|
|
1925
|
+
for (const newAttribute of newAttributes) {
|
|
1926
|
+
if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
|
|
1930
|
+
oldElt.setAttribute(newAttribute.name, newAttribute.value);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
for (let i = oldAttributes.length - 1; 0 <= i; i--) {
|
|
1934
|
+
const oldAttribute = oldAttributes[i];
|
|
1935
|
+
if (!oldAttribute) continue;
|
|
1936
|
+
if (!newElt.hasAttribute(oldAttribute.name)) {
|
|
1937
|
+
if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) {
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
oldElt.removeAttribute(oldAttribute.name);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (!ignoreValueOfActiveElement(oldElt, ctx)) {
|
|
1944
|
+
syncInputValue(oldElt, newElt, ctx);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (type === 8 || type === 3) {
|
|
1948
|
+
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
1949
|
+
oldNode.nodeValue = newNode.nodeValue;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function syncInputValue(oldElement, newElement, ctx) {
|
|
1954
|
+
if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") {
|
|
1955
|
+
let newValue = newElement.value;
|
|
1956
|
+
let oldValue = oldElement.value;
|
|
1957
|
+
syncBooleanAttribute(oldElement, newElement, "checked", ctx);
|
|
1958
|
+
syncBooleanAttribute(oldElement, newElement, "disabled", ctx);
|
|
1959
|
+
if (!newElement.hasAttribute("value")) {
|
|
1960
|
+
if (!ignoreAttribute("value", oldElement, "remove", ctx)) {
|
|
1961
|
+
oldElement.value = "";
|
|
1962
|
+
oldElement.removeAttribute("value");
|
|
1963
|
+
}
|
|
1964
|
+
} else if (oldValue !== newValue) {
|
|
1965
|
+
if (!ignoreAttribute("value", oldElement, "update", ctx)) {
|
|
1966
|
+
oldElement.setAttribute("value", newValue);
|
|
1967
|
+
oldElement.value = newValue;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
} else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) {
|
|
1971
|
+
syncBooleanAttribute(oldElement, newElement, "selected", ctx);
|
|
1972
|
+
} else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
|
|
1973
|
+
let newValue = newElement.value;
|
|
1974
|
+
let oldValue = oldElement.value;
|
|
1975
|
+
if (ignoreAttribute("value", oldElement, "update", ctx)) {
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
if (newValue !== oldValue) {
|
|
1979
|
+
oldElement.value = newValue;
|
|
1980
|
+
}
|
|
1981
|
+
if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
|
|
1982
|
+
oldElement.firstChild.nodeValue = newValue;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {
|
|
1987
|
+
const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName];
|
|
1988
|
+
if (newLiveValue !== oldLiveValue) {
|
|
1989
|
+
const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
|
|
1990
|
+
if (!ignoreUpdate) {
|
|
1991
|
+
oldElement[attributeName] = newElement[attributeName];
|
|
1992
|
+
}
|
|
1993
|
+
if (newLiveValue) {
|
|
1994
|
+
if (!ignoreUpdate) {
|
|
1995
|
+
oldElement.setAttribute(attributeName, "");
|
|
1996
|
+
}
|
|
1997
|
+
} else {
|
|
1998
|
+
if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) {
|
|
1999
|
+
oldElement.removeAttribute(attributeName);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
function ignoreAttribute(attr, element, updateType, ctx) {
|
|
2005
|
+
if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false;
|
|
2009
|
+
}
|
|
2010
|
+
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
|
2011
|
+
return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
|
2012
|
+
}
|
|
2013
|
+
return morphNode;
|
|
2014
|
+
}();
|
|
2015
|
+
function withHeadBlocking(ctx, oldNode, newNode, callback) {
|
|
2016
|
+
if (ctx.head.block) {
|
|
2017
|
+
const oldHead = oldNode.querySelector("head");
|
|
2018
|
+
const newHead = newNode.querySelector("head");
|
|
2019
|
+
if (oldHead && newHead) {
|
|
2020
|
+
const promises = handleHeadElement(oldHead, newHead, ctx);
|
|
2021
|
+
return Promise.all(promises).then((() => {
|
|
2022
|
+
const newCtx = Object.assign(ctx, {
|
|
2023
|
+
head: {
|
|
2024
|
+
block: false,
|
|
2025
|
+
ignore: true
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
return callback(newCtx);
|
|
2029
|
+
}));
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return callback(ctx);
|
|
2033
|
+
}
|
|
2034
|
+
function handleHeadElement(oldHead, newHead, ctx) {
|
|
2035
|
+
let added = [];
|
|
2036
|
+
let removed = [];
|
|
2037
|
+
let preserved = [];
|
|
2038
|
+
let nodesToAppend = [];
|
|
2039
|
+
let srcToNewHeadNodes = new Map;
|
|
2040
|
+
for (const newHeadChild of newHead.children) {
|
|
2041
|
+
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
|
2042
|
+
}
|
|
2043
|
+
for (const currentHeadElt of oldHead.children) {
|
|
2044
|
+
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
|
2045
|
+
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
|
2046
|
+
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
|
2047
|
+
if (inNewContent || isPreserved) {
|
|
2048
|
+
if (isReAppended) {
|
|
2049
|
+
removed.push(currentHeadElt);
|
|
2050
|
+
} else {
|
|
2051
|
+
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
|
2052
|
+
preserved.push(currentHeadElt);
|
|
2053
|
+
}
|
|
2054
|
+
} else {
|
|
2055
|
+
if (ctx.head.style === "append") {
|
|
2056
|
+
if (isReAppended) {
|
|
2057
|
+
removed.push(currentHeadElt);
|
|
2058
|
+
nodesToAppend.push(currentHeadElt);
|
|
2059
|
+
}
|
|
2060
|
+
} else {
|
|
2061
|
+
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
|
2062
|
+
removed.push(currentHeadElt);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
nodesToAppend.push(...srcToNewHeadNodes.values());
|
|
2068
|
+
let promises = [];
|
|
2069
|
+
for (const newNode of nodesToAppend) {
|
|
2070
|
+
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
|
2071
|
+
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
|
2072
|
+
if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
|
|
2073
|
+
let resolve;
|
|
2074
|
+
let promise = new Promise((function(_resolve) {
|
|
2075
|
+
resolve = _resolve;
|
|
2076
|
+
}));
|
|
2077
|
+
newElt.addEventListener("load", (function() {
|
|
2078
|
+
resolve();
|
|
2079
|
+
}));
|
|
2080
|
+
promises.push(promise);
|
|
2081
|
+
}
|
|
2082
|
+
oldHead.appendChild(newElt);
|
|
2083
|
+
ctx.callbacks.afterNodeAdded(newElt);
|
|
2084
|
+
added.push(newElt);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
for (const removedElement of removed) {
|
|
2088
|
+
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
|
2089
|
+
oldHead.removeChild(removedElement);
|
|
2090
|
+
ctx.callbacks.afterNodeRemoved(removedElement);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
ctx.head.afterHeadMorphed(oldHead, {
|
|
2094
|
+
added: added,
|
|
2095
|
+
kept: preserved,
|
|
2096
|
+
removed: removed
|
|
2097
|
+
});
|
|
2098
|
+
return promises;
|
|
2099
|
+
}
|
|
2100
|
+
const createMorphContext = function() {
|
|
2101
|
+
function createMorphContext(oldNode, newContent, config) {
|
|
2102
|
+
const {persistentIds: persistentIds, idMap: idMap} = createIdMaps(oldNode, newContent);
|
|
2103
|
+
const mergedConfig = mergeDefaults(config);
|
|
2104
|
+
const morphStyle = mergedConfig.morphStyle || "outerHTML";
|
|
2105
|
+
if (![ "innerHTML", "outerHTML" ].includes(morphStyle)) {
|
|
2106
|
+
throw `Do not understand how to morph style ${morphStyle}`;
|
|
2107
|
+
}
|
|
2108
|
+
return {
|
|
2109
|
+
target: oldNode,
|
|
2110
|
+
newContent: newContent,
|
|
2111
|
+
config: mergedConfig,
|
|
2112
|
+
morphStyle: morphStyle,
|
|
2113
|
+
ignoreActive: mergedConfig.ignoreActive,
|
|
2114
|
+
ignoreActiveValue: mergedConfig.ignoreActiveValue,
|
|
2115
|
+
restoreFocus: mergedConfig.restoreFocus,
|
|
2116
|
+
idMap: idMap,
|
|
2117
|
+
persistentIds: persistentIds,
|
|
2118
|
+
pantry: createPantry(),
|
|
2119
|
+
callbacks: mergedConfig.callbacks,
|
|
2120
|
+
head: mergedConfig.head
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
function mergeDefaults(config) {
|
|
2124
|
+
let finalConfig = Object.assign({}, defaults);
|
|
2125
|
+
Object.assign(finalConfig, config);
|
|
2126
|
+
finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
|
|
2127
|
+
finalConfig.head = Object.assign({}, defaults.head, config.head);
|
|
2128
|
+
return finalConfig;
|
|
2129
|
+
}
|
|
2130
|
+
function createPantry() {
|
|
2131
|
+
const pantry = document.createElement("div");
|
|
2132
|
+
pantry.hidden = true;
|
|
2133
|
+
document.body.insertAdjacentElement("afterend", pantry);
|
|
2134
|
+
return pantry;
|
|
2135
|
+
}
|
|
2136
|
+
function findIdElements(root) {
|
|
2137
|
+
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
2138
|
+
if (root.id) {
|
|
2139
|
+
elements.push(root);
|
|
2140
|
+
}
|
|
2141
|
+
return elements;
|
|
2142
|
+
}
|
|
2143
|
+
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
2144
|
+
for (const elt of elements) {
|
|
2145
|
+
if (persistentIds.has(elt.id)) {
|
|
2146
|
+
let current = elt;
|
|
2147
|
+
while (current) {
|
|
2148
|
+
let idSet = idMap.get(current);
|
|
2149
|
+
if (idSet == null) {
|
|
2150
|
+
idSet = new Set;
|
|
2151
|
+
idMap.set(current, idSet);
|
|
2152
|
+
}
|
|
2153
|
+
idSet.add(elt.id);
|
|
2154
|
+
if (current === root) break;
|
|
2155
|
+
current = current.parentElement;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
function createIdMaps(oldContent, newContent) {
|
|
2161
|
+
const oldIdElements = findIdElements(oldContent);
|
|
2162
|
+
const newIdElements = findIdElements(newContent);
|
|
2163
|
+
const persistentIds = createPersistentIds(oldIdElements, newIdElements);
|
|
2164
|
+
let idMap = new Map;
|
|
2165
|
+
populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
|
|
2166
|
+
const newRoot = newContent.__idiomorphRoot || newContent;
|
|
2167
|
+
populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
|
|
2168
|
+
return {
|
|
2169
|
+
persistentIds: persistentIds,
|
|
2170
|
+
idMap: idMap
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
function createPersistentIds(oldIdElements, newIdElements) {
|
|
2174
|
+
let duplicateIds = new Set;
|
|
2175
|
+
let oldIdTagNameMap = new Map;
|
|
2176
|
+
for (const {id: id, tagName: tagName} of oldIdElements) {
|
|
2177
|
+
if (oldIdTagNameMap.has(id)) {
|
|
2178
|
+
duplicateIds.add(id);
|
|
2179
|
+
} else {
|
|
2180
|
+
oldIdTagNameMap.set(id, tagName);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
let persistentIds = new Set;
|
|
2184
|
+
for (const {id: id, tagName: tagName} of newIdElements) {
|
|
2185
|
+
if (persistentIds.has(id)) {
|
|
2186
|
+
duplicateIds.add(id);
|
|
2187
|
+
} else if (oldIdTagNameMap.get(id) === tagName) {
|
|
2188
|
+
persistentIds.add(id);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
for (const id of duplicateIds) {
|
|
2192
|
+
persistentIds.delete(id);
|
|
2193
|
+
}
|
|
2194
|
+
return persistentIds;
|
|
2195
|
+
}
|
|
2196
|
+
return createMorphContext;
|
|
2197
|
+
}();
|
|
2198
|
+
const {normalizeElement: normalizeElement, normalizeParent: normalizeParent} = function() {
|
|
2199
|
+
const generatedByIdiomorph = new WeakSet;
|
|
2200
|
+
function normalizeElement(content) {
|
|
2201
|
+
if (content instanceof Document) {
|
|
2202
|
+
return content.documentElement;
|
|
2203
|
+
} else {
|
|
2204
|
+
return content;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function normalizeParent(newContent) {
|
|
2208
|
+
if (newContent == null) {
|
|
2209
|
+
return document.createElement("div");
|
|
2210
|
+
} else if (typeof newContent === "string") {
|
|
2211
|
+
return normalizeParent(parseContent(newContent));
|
|
2212
|
+
} else if (generatedByIdiomorph.has(newContent)) {
|
|
2213
|
+
return newContent;
|
|
2214
|
+
} else if (newContent instanceof Node) {
|
|
2215
|
+
if (newContent.parentNode) {
|
|
2216
|
+
return createDuckTypedParent(newContent);
|
|
2217
|
+
} else {
|
|
2218
|
+
const dummyParent = document.createElement("div");
|
|
2219
|
+
dummyParent.append(newContent);
|
|
2220
|
+
return dummyParent;
|
|
2221
|
+
}
|
|
2222
|
+
} else {
|
|
2223
|
+
const dummyParent = document.createElement("div");
|
|
2224
|
+
for (const elt of [ ...newContent ]) {
|
|
2225
|
+
dummyParent.append(elt);
|
|
2226
|
+
}
|
|
2227
|
+
return dummyParent;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function createDuckTypedParent(newContent) {
|
|
2231
|
+
return {
|
|
2232
|
+
childNodes: [ newContent ],
|
|
2233
|
+
querySelectorAll: s => {
|
|
2234
|
+
const elements = newContent.querySelectorAll(s);
|
|
2235
|
+
return newContent.matches(s) ? [ newContent, ...elements ] : elements;
|
|
2236
|
+
},
|
|
2237
|
+
insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
|
|
2238
|
+
moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
|
|
2239
|
+
get __idiomorphRoot() {
|
|
2240
|
+
return newContent;
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
function parseContent(newContent) {
|
|
2245
|
+
let parser = new DOMParser;
|
|
2246
|
+
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
|
2247
|
+
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
|
2248
|
+
let content = parser.parseFromString(newContent, "text/html");
|
|
2249
|
+
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
|
2250
|
+
generatedByIdiomorph.add(content);
|
|
2251
|
+
return content;
|
|
2252
|
+
} else {
|
|
2253
|
+
let htmlElement = content.firstChild;
|
|
2254
|
+
if (htmlElement) {
|
|
2255
|
+
generatedByIdiomorph.add(htmlElement);
|
|
2256
|
+
}
|
|
2257
|
+
return htmlElement;
|
|
2258
|
+
}
|
|
2259
|
+
} else {
|
|
2260
|
+
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
|
2261
|
+
let content = responseDoc.body.querySelector("template").content;
|
|
2262
|
+
generatedByIdiomorph.add(content);
|
|
2263
|
+
return content;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return {
|
|
2267
|
+
normalizeElement: normalizeElement,
|
|
2268
|
+
normalizeParent: normalizeParent
|
|
2269
|
+
};
|
|
2270
|
+
}();
|
|
2271
|
+
return {
|
|
2272
|
+
morph: morph,
|
|
2273
|
+
defaults: defaults
|
|
2274
|
+
};
|
|
2275
|
+
}();
|
|
2276
|
+
|
|
2277
|
+
function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
|
|
2278
|
+
Idiomorph.morph(currentElement, newElement, {
|
|
2279
|
+
...options,
|
|
2280
|
+
callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
function morphChildren(currentElement, newElement) {
|
|
2285
|
+
morphElements(currentElement, newElement.childNodes, {
|
|
2286
|
+
morphStyle: "innerHTML"
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
class DefaultIdiomorphCallbacks {
|
|
2291
|
+
#beforeNodeMorphed;
|
|
2292
|
+
constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
|
|
2293
|
+
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
|
2294
|
+
}
|
|
2295
|
+
beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
|
2296
|
+
beforeNodeMorphed=(currentElement, newElement) => {
|
|
2297
|
+
if (currentElement instanceof Element) {
|
|
2298
|
+
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
|
2299
|
+
const event = dispatch("turbo:before-morph-element", {
|
|
2300
|
+
cancelable: true,
|
|
2301
|
+
target: currentElement,
|
|
2302
|
+
detail: {
|
|
2303
|
+
currentElement: currentElement,
|
|
2304
|
+
newElement: newElement
|
|
2305
|
+
}
|
|
2306
|
+
});
|
|
2307
|
+
return !event.defaultPrevented;
|
|
2308
|
+
} else {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
beforeAttributeUpdated=(attributeName, target, mutationType) => {
|
|
2314
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
|
2315
|
+
cancelable: true,
|
|
2316
|
+
target: target,
|
|
2317
|
+
detail: {
|
|
2318
|
+
attributeName: attributeName,
|
|
2319
|
+
mutationType: mutationType
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
return !event.defaultPrevented;
|
|
2323
|
+
};
|
|
2324
|
+
beforeNodeRemoved=node => this.beforeNodeMorphed(node);
|
|
2325
|
+
afterNodeMorphed=(currentElement, newElement) => {
|
|
2326
|
+
if (currentElement instanceof Element) {
|
|
2327
|
+
dispatch("turbo:morph-element", {
|
|
2328
|
+
target: currentElement,
|
|
2329
|
+
detail: {
|
|
2330
|
+
currentElement: currentElement,
|
|
2331
|
+
newElement: newElement
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
class MorphingFrameRenderer extends FrameRenderer {
|
|
2339
|
+
static renderElement(currentElement, newElement) {
|
|
2340
|
+
dispatch("turbo:before-frame-morph", {
|
|
2341
|
+
target: currentElement,
|
|
2342
|
+
detail: {
|
|
2343
|
+
currentElement: currentElement,
|
|
2344
|
+
newElement: newElement
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
morphChildren(currentElement, newElement);
|
|
2348
|
+
}
|
|
2349
|
+
async preservingPermanentElements(callback) {
|
|
2350
|
+
return await callback();
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
class ProgressBar {
|
|
2355
|
+
static animationDuration=300;
|
|
2356
|
+
static get defaultCSS() {
|
|
2357
|
+
return unindent`
|
|
1632
2358
|
.turbo-progress-bar {
|
|
1633
2359
|
position: fixed;
|
|
1634
2360
|
display: block;
|
|
@@ -1714,8 +2440,9 @@ class ProgressBar {
|
|
|
1714
2440
|
const element = document.createElement("style");
|
|
1715
2441
|
element.type = "text/css";
|
|
1716
2442
|
element.textContent = ProgressBar.defaultCSS;
|
|
1717
|
-
|
|
1718
|
-
|
|
2443
|
+
const cspNonce = getCspNonce();
|
|
2444
|
+
if (cspNonce) {
|
|
2445
|
+
element.nonce = cspNonce;
|
|
1719
2446
|
}
|
|
1720
2447
|
return element;
|
|
1721
2448
|
}
|
|
@@ -1724,9 +2451,6 @@ class ProgressBar {
|
|
|
1724
2451
|
element.className = "turbo-progress-bar";
|
|
1725
2452
|
return element;
|
|
1726
2453
|
}
|
|
1727
|
-
get cspNonce() {
|
|
1728
|
-
return getMetaContent("csp-nonce");
|
|
1729
|
-
}
|
|
1730
2454
|
}
|
|
1731
2455
|
|
|
1732
2456
|
class HeadSnapshot extends Snapshot {
|
|
@@ -2229,16 +2953,6 @@ class Visit {
|
|
|
2229
2953
|
...this.timingMetrics
|
|
2230
2954
|
};
|
|
2231
2955
|
}
|
|
2232
|
-
getHistoryMethodForAction(action) {
|
|
2233
|
-
switch (action) {
|
|
2234
|
-
case "replace":
|
|
2235
|
-
return history.replaceState;
|
|
2236
|
-
|
|
2237
|
-
case "advance":
|
|
2238
|
-
case "restore":
|
|
2239
|
-
return history.pushState;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
2956
|
hasPreloadedResponse() {
|
|
2243
2957
|
return typeof this.response == "object";
|
|
2244
2958
|
}
|
|
@@ -2259,7 +2973,9 @@ class Visit {
|
|
|
2259
2973
|
}
|
|
2260
2974
|
async render(callback) {
|
|
2261
2975
|
this.cancelRender();
|
|
2262
|
-
|
|
2976
|
+
await new Promise((resolve => {
|
|
2977
|
+
this.frame = document.visibilityState === "hidden" ? setTimeout((() => resolve()), 0) : requestAnimationFrame((() => resolve()));
|
|
2978
|
+
}));
|
|
2263
2979
|
await callback();
|
|
2264
2980
|
delete this.frame;
|
|
2265
2981
|
}
|
|
@@ -2339,6 +3055,9 @@ class BrowserAdapter {
|
|
|
2339
3055
|
this.hideVisitProgressBar();
|
|
2340
3056
|
}
|
|
2341
3057
|
visitRendered(_visit) {}
|
|
3058
|
+
linkPrefetchingIsEnabledForLocation(location) {
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
2342
3061
|
formSubmissionStarted(_formSubmission) {
|
|
2343
3062
|
this.progressBar.setValue(0);
|
|
2344
3063
|
this.showFormProgressBarAfterDelay();
|
|
@@ -2631,7 +3350,7 @@ class LinkPrefetchObserver {
|
|
|
2631
3350
|
this.#prefetchedLink = null;
|
|
2632
3351
|
};
|
|
2633
3352
|
#tryToUsePrefetchedRequest=event => {
|
|
2634
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "
|
|
3353
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
2635
3354
|
const cached = prefetchCache.get(event.detail.url.toString());
|
|
2636
3355
|
if (cached) {
|
|
2637
3356
|
event.detail.fetchRequest = cached;
|
|
@@ -2795,11 +3514,18 @@ class Navigator {
|
|
|
2795
3514
|
this.adapter.formSubmissionFinished(formSubmission);
|
|
2796
3515
|
}
|
|
2797
3516
|
}
|
|
3517
|
+
linkPrefetchingIsEnabledForLocation(location) {
|
|
3518
|
+
if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") {
|
|
3519
|
+
return this.adapter.linkPrefetchingIsEnabledForLocation(location);
|
|
3520
|
+
}
|
|
3521
|
+
return true;
|
|
3522
|
+
}
|
|
2798
3523
|
visitStarted(visit) {
|
|
2799
3524
|
this.delegate.visitStarted(visit);
|
|
2800
3525
|
}
|
|
2801
3526
|
visitCompleted(visit) {
|
|
2802
3527
|
this.delegate.visitCompleted(visit);
|
|
3528
|
+
delete this.currentVisit;
|
|
2803
3529
|
}
|
|
2804
3530
|
locationWithActionIsSamePage(location, action) {
|
|
2805
3531
|
const anchor = getAnchor(location);
|
|
@@ -2816,819 +3542,279 @@ class Navigator {
|
|
|
2816
3542
|
get restorationIdentifier() {
|
|
2817
3543
|
return this.history.restorationIdentifier;
|
|
2818
3544
|
}
|
|
2819
|
-
#getActionForFormSubmission(formSubmission, fetchResponse) {
|
|
2820
|
-
const {submitter: submitter, formElement: formElement} = formSubmission;
|
|
2821
|
-
return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
|
|
2822
|
-
}
|
|
2823
|
-
#getDefaultAction(fetchResponse) {
|
|
2824
|
-
const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
|
2825
|
-
return sameLocationRedirect ? "replace" : "advance";
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
const PageStage = {
|
|
2830
|
-
initial: 0,
|
|
2831
|
-
loading: 1,
|
|
2832
|
-
interactive: 2,
|
|
2833
|
-
complete: 3
|
|
2834
|
-
};
|
|
2835
|
-
|
|
2836
|
-
class PageObserver {
|
|
2837
|
-
stage=PageStage.initial;
|
|
2838
|
-
started=false;
|
|
2839
|
-
constructor(delegate) {
|
|
2840
|
-
this.delegate = delegate;
|
|
2841
|
-
}
|
|
2842
|
-
start() {
|
|
2843
|
-
if (!this.started) {
|
|
2844
|
-
if (this.stage == PageStage.initial) {
|
|
2845
|
-
this.stage = PageStage.loading;
|
|
2846
|
-
}
|
|
2847
|
-
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
|
2848
|
-
addEventListener("pagehide", this.pageWillUnload, false);
|
|
2849
|
-
this.started = true;
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
stop() {
|
|
2853
|
-
if (this.started) {
|
|
2854
|
-
document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
|
2855
|
-
removeEventListener("pagehide", this.pageWillUnload, false);
|
|
2856
|
-
this.started = false;
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
interpretReadyState=() => {
|
|
2860
|
-
const {readyState: readyState} = this;
|
|
2861
|
-
if (readyState == "interactive") {
|
|
2862
|
-
this.pageIsInteractive();
|
|
2863
|
-
} else if (readyState == "complete") {
|
|
2864
|
-
this.pageIsComplete();
|
|
2865
|
-
}
|
|
2866
|
-
};
|
|
2867
|
-
pageIsInteractive() {
|
|
2868
|
-
if (this.stage == PageStage.loading) {
|
|
2869
|
-
this.stage = PageStage.interactive;
|
|
2870
|
-
this.delegate.pageBecameInteractive();
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
pageIsComplete() {
|
|
2874
|
-
this.pageIsInteractive();
|
|
2875
|
-
if (this.stage == PageStage.interactive) {
|
|
2876
|
-
this.stage = PageStage.complete;
|
|
2877
|
-
this.delegate.pageLoaded();
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
pageWillUnload=() => {
|
|
2881
|
-
this.delegate.pageWillUnload();
|
|
2882
|
-
};
|
|
2883
|
-
get readyState() {
|
|
2884
|
-
return document.readyState;
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
class ScrollObserver {
|
|
2889
|
-
started=false;
|
|
2890
|
-
constructor(delegate) {
|
|
2891
|
-
this.delegate = delegate;
|
|
2892
|
-
}
|
|
2893
|
-
start() {
|
|
2894
|
-
if (!this.started) {
|
|
2895
|
-
addEventListener("scroll", this.onScroll, false);
|
|
2896
|
-
this.onScroll();
|
|
2897
|
-
this.started = true;
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
stop() {
|
|
2901
|
-
if (this.started) {
|
|
2902
|
-
removeEventListener("scroll", this.onScroll, false);
|
|
2903
|
-
this.started = false;
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
2906
|
-
onScroll=() => {
|
|
2907
|
-
this.updatePosition({
|
|
2908
|
-
x: window.pageXOffset,
|
|
2909
|
-
y: window.pageYOffset
|
|
2910
|
-
});
|
|
2911
|
-
};
|
|
2912
|
-
updatePosition(position) {
|
|
2913
|
-
this.delegate.scrollPositionChanged(position);
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
class StreamMessageRenderer {
|
|
2918
|
-
render({fragment: fragment}) {
|
|
2919
|
-
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
|
2920
|
-
withAutofocusFromFragment(fragment, (() => {
|
|
2921
|
-
withPreservedFocus((() => {
|
|
2922
|
-
document.documentElement.appendChild(fragment);
|
|
2923
|
-
}));
|
|
2924
|
-
}));
|
|
2925
|
-
}));
|
|
2926
|
-
}
|
|
2927
|
-
enteringBardo(currentPermanentElement, newPermanentElement) {
|
|
2928
|
-
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
|
2929
|
-
}
|
|
2930
|
-
leavingBardo() {}
|
|
2931
|
-
}
|
|
2932
|
-
|
|
2933
|
-
function getPermanentElementMapForFragment(fragment) {
|
|
2934
|
-
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
|
2935
|
-
const permanentElementMap = {};
|
|
2936
|
-
for (const permanentElementInDocument of permanentElementsInDocument) {
|
|
2937
|
-
const {id: id} = permanentElementInDocument;
|
|
2938
|
-
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
|
2939
|
-
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
|
2940
|
-
if (elementInStream) {
|
|
2941
|
-
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
return permanentElementMap;
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
async function withAutofocusFromFragment(fragment, callback) {
|
|
2949
|
-
const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
|
2950
|
-
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
|
2951
|
-
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
|
2952
|
-
let willAutofocusId = null;
|
|
2953
|
-
if (elementWithAutofocus) {
|
|
2954
|
-
if (elementWithAutofocus.id) {
|
|
2955
|
-
willAutofocusId = elementWithAutofocus.id;
|
|
2956
|
-
} else {
|
|
2957
|
-
willAutofocusId = generatedID;
|
|
2958
|
-
}
|
|
2959
|
-
elementWithAutofocus.id = willAutofocusId;
|
|
2960
|
-
}
|
|
2961
|
-
callback();
|
|
2962
|
-
await nextRepaint();
|
|
2963
|
-
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
|
2964
|
-
if (hasNoActiveElement && willAutofocusId) {
|
|
2965
|
-
const elementToAutofocus = document.getElementById(willAutofocusId);
|
|
2966
|
-
if (elementIsFocusable(elementToAutofocus)) {
|
|
2967
|
-
elementToAutofocus.focus();
|
|
2968
|
-
}
|
|
2969
|
-
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
|
2970
|
-
elementToAutofocus.removeAttribute("id");
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
|
-
async function withPreservedFocus(callback) {
|
|
2976
|
-
const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
|
|
2977
|
-
const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
|
2978
|
-
if (restoreFocusTo) {
|
|
2979
|
-
const elementToFocus = document.getElementById(restoreFocusTo);
|
|
2980
|
-
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
|
2981
|
-
elementToFocus.focus();
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
|
2987
|
-
for (const streamElement of nodeListOfStreamElements) {
|
|
2988
|
-
const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
|
2989
|
-
if (elementWithAutofocus) return elementWithAutofocus;
|
|
2990
|
-
}
|
|
2991
|
-
return null;
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
class StreamObserver {
|
|
2995
|
-
sources=new Set;
|
|
2996
|
-
#started=false;
|
|
2997
|
-
constructor(delegate) {
|
|
2998
|
-
this.delegate = delegate;
|
|
2999
|
-
}
|
|
3000
|
-
start() {
|
|
3001
|
-
if (!this.#started) {
|
|
3002
|
-
this.#started = true;
|
|
3003
|
-
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
stop() {
|
|
3007
|
-
if (this.#started) {
|
|
3008
|
-
this.#started = false;
|
|
3009
|
-
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
connectStreamSource(source) {
|
|
3013
|
-
if (!this.streamSourceIsConnected(source)) {
|
|
3014
|
-
this.sources.add(source);
|
|
3015
|
-
source.addEventListener("message", this.receiveMessageEvent, false);
|
|
3016
|
-
}
|
|
3017
|
-
}
|
|
3018
|
-
disconnectStreamSource(source) {
|
|
3019
|
-
if (this.streamSourceIsConnected(source)) {
|
|
3020
|
-
this.sources.delete(source);
|
|
3021
|
-
source.removeEventListener("message", this.receiveMessageEvent, false);
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
streamSourceIsConnected(source) {
|
|
3025
|
-
return this.sources.has(source);
|
|
3026
|
-
}
|
|
3027
|
-
inspectFetchResponse=event => {
|
|
3028
|
-
const response = fetchResponseFromEvent(event);
|
|
3029
|
-
if (response && fetchResponseIsStream(response)) {
|
|
3030
|
-
event.preventDefault();
|
|
3031
|
-
this.receiveMessageResponse(response);
|
|
3032
|
-
}
|
|
3033
|
-
};
|
|
3034
|
-
receiveMessageEvent=event => {
|
|
3035
|
-
if (this.#started && typeof event.data == "string") {
|
|
3036
|
-
this.receiveMessageHTML(event.data);
|
|
3037
|
-
}
|
|
3038
|
-
};
|
|
3039
|
-
async receiveMessageResponse(response) {
|
|
3040
|
-
const html = await response.responseHTML;
|
|
3041
|
-
if (html) {
|
|
3042
|
-
this.receiveMessageHTML(html);
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
receiveMessageHTML(html) {
|
|
3046
|
-
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
|
3545
|
+
#getActionForFormSubmission(formSubmission, fetchResponse) {
|
|
3546
|
+
const {submitter: submitter, formElement: formElement} = formSubmission;
|
|
3547
|
+
return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
|
|
3047
3548
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const fetchResponse = event.detail?.fetchResponse;
|
|
3052
|
-
if (fetchResponse instanceof FetchResponse) {
|
|
3053
|
-
return fetchResponse;
|
|
3549
|
+
#getDefaultAction(fetchResponse) {
|
|
3550
|
+
const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
|
3551
|
+
return sameLocationRedirect ? "replace" : "advance";
|
|
3054
3552
|
}
|
|
3055
3553
|
}
|
|
3056
3554
|
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3555
|
+
const PageStage = {
|
|
3556
|
+
initial: 0,
|
|
3557
|
+
loading: 1,
|
|
3558
|
+
interactive: 2,
|
|
3559
|
+
complete: 3
|
|
3560
|
+
};
|
|
3061
3561
|
|
|
3062
|
-
class
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
async render() {
|
|
3068
|
-
this.replaceHeadAndBody();
|
|
3069
|
-
this.activateScriptElements();
|
|
3070
|
-
}
|
|
3071
|
-
replaceHeadAndBody() {
|
|
3072
|
-
const {documentElement: documentElement, head: head} = document;
|
|
3073
|
-
documentElement.replaceChild(this.newHead, head);
|
|
3074
|
-
this.renderElement(this.currentElement, this.newElement);
|
|
3562
|
+
class PageObserver {
|
|
3563
|
+
stage=PageStage.initial;
|
|
3564
|
+
started=false;
|
|
3565
|
+
constructor(delegate) {
|
|
3566
|
+
this.delegate = delegate;
|
|
3075
3567
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
const element = activateScriptElement(replaceableElement);
|
|
3081
|
-
parentNode.replaceChild(element, replaceableElement);
|
|
3568
|
+
start() {
|
|
3569
|
+
if (!this.started) {
|
|
3570
|
+
if (this.stage == PageStage.initial) {
|
|
3571
|
+
this.stage = PageStage.loading;
|
|
3082
3572
|
}
|
|
3573
|
+
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
|
3574
|
+
addEventListener("pagehide", this.pageWillUnload, false);
|
|
3575
|
+
this.started = true;
|
|
3083
3576
|
}
|
|
3084
3577
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3578
|
+
stop() {
|
|
3579
|
+
if (this.started) {
|
|
3580
|
+
document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
|
3581
|
+
removeEventListener("pagehide", this.pageWillUnload, false);
|
|
3582
|
+
this.started = false;
|
|
3583
|
+
}
|
|
3090
3584
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
callbacks: {
|
|
3098
|
-
beforeNodeAdded: noOp,
|
|
3099
|
-
afterNodeAdded: noOp,
|
|
3100
|
-
beforeNodeMorphed: noOp,
|
|
3101
|
-
afterNodeMorphed: noOp,
|
|
3102
|
-
beforeNodeRemoved: noOp,
|
|
3103
|
-
afterNodeRemoved: noOp,
|
|
3104
|
-
beforeAttributeUpdated: noOp
|
|
3105
|
-
},
|
|
3106
|
-
head: {
|
|
3107
|
-
style: "merge",
|
|
3108
|
-
shouldPreserve: function(elt) {
|
|
3109
|
-
return elt.getAttribute("im-preserve") === "true";
|
|
3110
|
-
},
|
|
3111
|
-
shouldReAppend: function(elt) {
|
|
3112
|
-
return elt.getAttribute("im-re-append") === "true";
|
|
3113
|
-
},
|
|
3114
|
-
shouldRemove: noOp,
|
|
3115
|
-
afterHeadMorphed: noOp
|
|
3585
|
+
interpretReadyState=() => {
|
|
3586
|
+
const {readyState: readyState} = this;
|
|
3587
|
+
if (readyState == "interactive") {
|
|
3588
|
+
this.pageIsInteractive();
|
|
3589
|
+
} else if (readyState == "complete") {
|
|
3590
|
+
this.pageIsComplete();
|
|
3116
3591
|
}
|
|
3117
3592
|
};
|
|
3118
|
-
|
|
3119
|
-
if (
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
if (typeof newContent === "string") {
|
|
3123
|
-
newContent = parseContent(newContent);
|
|
3593
|
+
pageIsInteractive() {
|
|
3594
|
+
if (this.stage == PageStage.loading) {
|
|
3595
|
+
this.stage = PageStage.interactive;
|
|
3596
|
+
this.delegate.pageBecameInteractive();
|
|
3124
3597
|
}
|
|
3125
|
-
let normalizedContent = normalizeContent(newContent);
|
|
3126
|
-
let ctx = createMorphContext(oldNode, normalizedContent, config);
|
|
3127
|
-
return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
|
3128
3598
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
let promises = handleHeadElement(newHead, oldHead, ctx);
|
|
3135
|
-
Promise.all(promises).then((function() {
|
|
3136
|
-
morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
|
3137
|
-
head: {
|
|
3138
|
-
block: false,
|
|
3139
|
-
ignore: true
|
|
3140
|
-
}
|
|
3141
|
-
}));
|
|
3142
|
-
}));
|
|
3143
|
-
return;
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
|
-
if (ctx.morphStyle === "innerHTML") {
|
|
3147
|
-
morphChildren(normalizedNewContent, oldNode, ctx);
|
|
3148
|
-
return oldNode.children;
|
|
3149
|
-
} else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
|
3150
|
-
let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
|
3151
|
-
let previousSibling = bestMatch?.previousSibling;
|
|
3152
|
-
let nextSibling = bestMatch?.nextSibling;
|
|
3153
|
-
let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
|
3154
|
-
if (bestMatch) {
|
|
3155
|
-
return insertSiblings(previousSibling, morphedNode, nextSibling);
|
|
3156
|
-
} else {
|
|
3157
|
-
return [];
|
|
3158
|
-
}
|
|
3159
|
-
} else {
|
|
3160
|
-
throw "Do not understand how to morph style " + ctx.morphStyle;
|
|
3599
|
+
pageIsComplete() {
|
|
3600
|
+
this.pageIsInteractive();
|
|
3601
|
+
if (this.stage == PageStage.interactive) {
|
|
3602
|
+
this.stage = PageStage.complete;
|
|
3603
|
+
this.delegate.pageLoaded();
|
|
3161
3604
|
}
|
|
3162
3605
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3606
|
+
pageWillUnload=() => {
|
|
3607
|
+
this.delegate.pageWillUnload();
|
|
3608
|
+
};
|
|
3609
|
+
get readyState() {
|
|
3610
|
+
return document.readyState;
|
|
3165
3611
|
}
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
} else if (!isSoftMatch(oldNode, newContent)) {
|
|
3173
|
-
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
|
3174
|
-
if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
|
|
3175
|
-
oldNode.parentElement.replaceChild(newContent, oldNode);
|
|
3176
|
-
ctx.callbacks.afterNodeAdded(newContent);
|
|
3177
|
-
ctx.callbacks.afterNodeRemoved(oldNode);
|
|
3178
|
-
return newContent;
|
|
3179
|
-
} else {
|
|
3180
|
-
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
|
|
3181
|
-
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
|
3182
|
-
handleHeadElement(newContent, oldNode, ctx);
|
|
3183
|
-
} else {
|
|
3184
|
-
syncNodeFrom(newContent, oldNode, ctx);
|
|
3185
|
-
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
|
3186
|
-
morphChildren(newContent, oldNode, ctx);
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
|
3190
|
-
return oldNode;
|
|
3191
|
-
}
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
class ScrollObserver {
|
|
3615
|
+
started=false;
|
|
3616
|
+
constructor(delegate) {
|
|
3617
|
+
this.delegate = delegate;
|
|
3192
3618
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
newChild = nextNewChild;
|
|
3199
|
-
nextNewChild = newChild.nextSibling;
|
|
3200
|
-
if (insertionPoint == null) {
|
|
3201
|
-
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
|
3202
|
-
oldParent.appendChild(newChild);
|
|
3203
|
-
ctx.callbacks.afterNodeAdded(newChild);
|
|
3204
|
-
removeIdsFromConsideration(ctx, newChild);
|
|
3205
|
-
continue;
|
|
3206
|
-
}
|
|
3207
|
-
if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
|
3208
|
-
morphOldNodeTo(insertionPoint, newChild, ctx);
|
|
3209
|
-
insertionPoint = insertionPoint.nextSibling;
|
|
3210
|
-
removeIdsFromConsideration(ctx, newChild);
|
|
3211
|
-
continue;
|
|
3212
|
-
}
|
|
3213
|
-
let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
|
3214
|
-
if (idSetMatch) {
|
|
3215
|
-
insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
|
3216
|
-
morphOldNodeTo(idSetMatch, newChild, ctx);
|
|
3217
|
-
removeIdsFromConsideration(ctx, newChild);
|
|
3218
|
-
continue;
|
|
3219
|
-
}
|
|
3220
|
-
let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
|
3221
|
-
if (softMatch) {
|
|
3222
|
-
insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
|
3223
|
-
morphOldNodeTo(softMatch, newChild, ctx);
|
|
3224
|
-
removeIdsFromConsideration(ctx, newChild);
|
|
3225
|
-
continue;
|
|
3226
|
-
}
|
|
3227
|
-
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
|
3228
|
-
oldParent.insertBefore(newChild, insertionPoint);
|
|
3229
|
-
ctx.callbacks.afterNodeAdded(newChild);
|
|
3230
|
-
removeIdsFromConsideration(ctx, newChild);
|
|
3231
|
-
}
|
|
3232
|
-
while (insertionPoint !== null) {
|
|
3233
|
-
let tempNode = insertionPoint;
|
|
3234
|
-
insertionPoint = insertionPoint.nextSibling;
|
|
3235
|
-
removeNode(tempNode, ctx);
|
|
3619
|
+
start() {
|
|
3620
|
+
if (!this.started) {
|
|
3621
|
+
addEventListener("scroll", this.onScroll, false);
|
|
3622
|
+
this.onScroll();
|
|
3623
|
+
this.started = true;
|
|
3236
3624
|
}
|
|
3237
3625
|
}
|
|
3238
|
-
|
|
3239
|
-
if (
|
|
3240
|
-
|
|
3626
|
+
stop() {
|
|
3627
|
+
if (this.started) {
|
|
3628
|
+
removeEventListener("scroll", this.onScroll, false);
|
|
3629
|
+
this.started = false;
|
|
3241
3630
|
}
|
|
3242
|
-
return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
|
|
3243
3631
|
}
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
}
|
|
3253
|
-
if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
|
|
3254
|
-
to.setAttribute(fromAttribute.name, fromAttribute.value);
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
for (let i = toAttributes.length - 1; 0 <= i; i--) {
|
|
3258
|
-
const toAttribute = toAttributes[i];
|
|
3259
|
-
if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
|
|
3260
|
-
continue;
|
|
3261
|
-
}
|
|
3262
|
-
if (!from.hasAttribute(toAttribute.name)) {
|
|
3263
|
-
to.removeAttribute(toAttribute.name);
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
if (type === 8 || type === 3) {
|
|
3268
|
-
if (to.nodeValue !== from.nodeValue) {
|
|
3269
|
-
to.nodeValue = from.nodeValue;
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
if (!ignoreValueOfActiveElement(to, ctx)) {
|
|
3273
|
-
syncInputValue(from, to, ctx);
|
|
3274
|
-
}
|
|
3632
|
+
onScroll=() => {
|
|
3633
|
+
this.updatePosition({
|
|
3634
|
+
x: window.pageXOffset,
|
|
3635
|
+
y: window.pageYOffset
|
|
3636
|
+
});
|
|
3637
|
+
};
|
|
3638
|
+
updatePosition(position) {
|
|
3639
|
+
this.delegate.scrollPositionChanged(position);
|
|
3275
3640
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
|
|
3288
|
-
to.removeAttribute(attributeName);
|
|
3289
|
-
}
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
class StreamMessageRenderer {
|
|
3644
|
+
render({fragment: fragment}) {
|
|
3645
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
|
3646
|
+
withAutofocusFromFragment(fragment, (() => {
|
|
3647
|
+
withPreservedFocus((() => {
|
|
3648
|
+
document.documentElement.appendChild(fragment);
|
|
3649
|
+
}));
|
|
3650
|
+
}));
|
|
3651
|
+
}));
|
|
3292
3652
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
let fromValue = from.value;
|
|
3296
|
-
let toValue = to.value;
|
|
3297
|
-
syncBooleanAttribute(from, to, "checked", ctx);
|
|
3298
|
-
syncBooleanAttribute(from, to, "disabled", ctx);
|
|
3299
|
-
if (!from.hasAttribute("value")) {
|
|
3300
|
-
if (!ignoreAttribute("value", to, "remove", ctx)) {
|
|
3301
|
-
to.value = "";
|
|
3302
|
-
to.removeAttribute("value");
|
|
3303
|
-
}
|
|
3304
|
-
} else if (fromValue !== toValue) {
|
|
3305
|
-
if (!ignoreAttribute("value", to, "update", ctx)) {
|
|
3306
|
-
to.setAttribute("value", fromValue);
|
|
3307
|
-
to.value = fromValue;
|
|
3308
|
-
}
|
|
3309
|
-
}
|
|
3310
|
-
} else if (from instanceof HTMLOptionElement) {
|
|
3311
|
-
syncBooleanAttribute(from, to, "selected", ctx);
|
|
3312
|
-
} else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
|
3313
|
-
let fromValue = from.value;
|
|
3314
|
-
let toValue = to.value;
|
|
3315
|
-
if (ignoreAttribute("value", to, "update", ctx)) {
|
|
3316
|
-
return;
|
|
3317
|
-
}
|
|
3318
|
-
if (fromValue !== toValue) {
|
|
3319
|
-
to.value = fromValue;
|
|
3320
|
-
}
|
|
3321
|
-
if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
|
3322
|
-
to.firstChild.nodeValue = fromValue;
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3653
|
+
enteringBardo(currentPermanentElement, newPermanentElement) {
|
|
3654
|
+
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
|
3325
3655
|
}
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
|
3339
|
-
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
|
3340
|
-
if (inNewContent || isPreserved) {
|
|
3341
|
-
if (isReAppended) {
|
|
3342
|
-
removed.push(currentHeadElt);
|
|
3343
|
-
} else {
|
|
3344
|
-
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
|
3345
|
-
preserved.push(currentHeadElt);
|
|
3346
|
-
}
|
|
3347
|
-
} else {
|
|
3348
|
-
if (headMergeStyle === "append") {
|
|
3349
|
-
if (isReAppended) {
|
|
3350
|
-
removed.push(currentHeadElt);
|
|
3351
|
-
nodesToAppend.push(currentHeadElt);
|
|
3352
|
-
}
|
|
3353
|
-
} else {
|
|
3354
|
-
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
|
3355
|
-
removed.push(currentHeadElt);
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
nodesToAppend.push(...srcToNewHeadNodes.values());
|
|
3361
|
-
let promises = [];
|
|
3362
|
-
for (const newNode of nodesToAppend) {
|
|
3363
|
-
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
|
3364
|
-
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
|
3365
|
-
if (newElt.href || newElt.src) {
|
|
3366
|
-
let resolve = null;
|
|
3367
|
-
let promise = new Promise((function(_resolve) {
|
|
3368
|
-
resolve = _resolve;
|
|
3369
|
-
}));
|
|
3370
|
-
newElt.addEventListener("load", (function() {
|
|
3371
|
-
resolve();
|
|
3372
|
-
}));
|
|
3373
|
-
promises.push(promise);
|
|
3374
|
-
}
|
|
3375
|
-
currentHead.appendChild(newElt);
|
|
3376
|
-
ctx.callbacks.afterNodeAdded(newElt);
|
|
3377
|
-
added.push(newElt);
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
for (const removedElement of removed) {
|
|
3381
|
-
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
|
3382
|
-
currentHead.removeChild(removedElement);
|
|
3383
|
-
ctx.callbacks.afterNodeRemoved(removedElement);
|
|
3656
|
+
leavingBardo() {}
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
function getPermanentElementMapForFragment(fragment) {
|
|
3660
|
+
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
|
3661
|
+
const permanentElementMap = {};
|
|
3662
|
+
for (const permanentElementInDocument of permanentElementsInDocument) {
|
|
3663
|
+
const {id: id} = permanentElementInDocument;
|
|
3664
|
+
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
|
3665
|
+
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
|
3666
|
+
if (elementInStream) {
|
|
3667
|
+
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
|
3384
3668
|
}
|
|
3385
3669
|
}
|
|
3386
|
-
ctx.head.afterHeadMorphed(currentHead, {
|
|
3387
|
-
added: added,
|
|
3388
|
-
kept: preserved,
|
|
3389
|
-
removed: removed
|
|
3390
|
-
});
|
|
3391
|
-
return promises;
|
|
3392
3670
|
}
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
return {
|
|
3409
|
-
target: oldNode,
|
|
3410
|
-
newContent: newContent,
|
|
3411
|
-
config: config,
|
|
3412
|
-
morphStyle: config.morphStyle,
|
|
3413
|
-
ignoreActive: config.ignoreActive,
|
|
3414
|
-
ignoreActiveValue: config.ignoreActiveValue,
|
|
3415
|
-
idMap: createIdMap(oldNode, newContent),
|
|
3416
|
-
deadIds: new Set,
|
|
3417
|
-
callbacks: config.callbacks,
|
|
3418
|
-
head: config.head
|
|
3419
|
-
};
|
|
3671
|
+
return permanentElementMap;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
async function withAutofocusFromFragment(fragment, callback) {
|
|
3675
|
+
const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
|
3676
|
+
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
|
3677
|
+
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
|
3678
|
+
let willAutofocusId = null;
|
|
3679
|
+
if (elementWithAutofocus) {
|
|
3680
|
+
if (elementWithAutofocus.id) {
|
|
3681
|
+
willAutofocusId = elementWithAutofocus.id;
|
|
3682
|
+
} else {
|
|
3683
|
+
willAutofocusId = generatedID;
|
|
3684
|
+
}
|
|
3685
|
+
elementWithAutofocus.id = willAutofocusId;
|
|
3420
3686
|
}
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3687
|
+
callback();
|
|
3688
|
+
await nextRepaint();
|
|
3689
|
+
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
|
3690
|
+
if (hasNoActiveElement && willAutofocusId) {
|
|
3691
|
+
const elementToAutofocus = document.getElementById(willAutofocusId);
|
|
3692
|
+
if (elementIsFocusable(elementToAutofocus)) {
|
|
3693
|
+
elementToAutofocus.focus();
|
|
3424
3694
|
}
|
|
3425
|
-
if (
|
|
3426
|
-
|
|
3427
|
-
return true;
|
|
3428
|
-
} else {
|
|
3429
|
-
return getIdIntersectionCount(ctx, node1, node2) > 0;
|
|
3430
|
-
}
|
|
3695
|
+
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
|
3696
|
+
elementToAutofocus.removeAttribute("id");
|
|
3431
3697
|
}
|
|
3432
|
-
return false;
|
|
3433
3698
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
async function withPreservedFocus(callback) {
|
|
3702
|
+
const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
|
|
3703
|
+
const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
|
3704
|
+
if (restoreFocusTo) {
|
|
3705
|
+
const elementToFocus = document.getElementById(restoreFocusTo);
|
|
3706
|
+
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
|
3707
|
+
elementToFocus.focus();
|
|
3437
3708
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
|
|
3460
|
-
if (otherMatchCount > newChildPotentialIdCount) {
|
|
3461
|
-
return null;
|
|
3462
|
-
}
|
|
3463
|
-
potentialMatch = potentialMatch.nextSibling;
|
|
3464
|
-
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
|
3713
|
+
for (const streamElement of nodeListOfStreamElements) {
|
|
3714
|
+
const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
|
3715
|
+
if (elementWithAutofocus) return elementWithAutofocus;
|
|
3716
|
+
}
|
|
3717
|
+
return null;
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
class StreamObserver {
|
|
3721
|
+
sources=new Set;
|
|
3722
|
+
#started=false;
|
|
3723
|
+
constructor(delegate) {
|
|
3724
|
+
this.delegate = delegate;
|
|
3725
|
+
}
|
|
3726
|
+
start() {
|
|
3727
|
+
if (!this.#started) {
|
|
3728
|
+
this.#started = true;
|
|
3729
|
+
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
3465
3730
|
}
|
|
3466
|
-
return potentialMatch;
|
|
3467
3731
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
while (potentialSoftMatch != null) {
|
|
3473
|
-
if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
|
3474
|
-
return null;
|
|
3475
|
-
}
|
|
3476
|
-
if (isSoftMatch(newChild, potentialSoftMatch)) {
|
|
3477
|
-
return potentialSoftMatch;
|
|
3478
|
-
}
|
|
3479
|
-
if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
|
3480
|
-
siblingSoftMatchCount++;
|
|
3481
|
-
nextSibling = nextSibling.nextSibling;
|
|
3482
|
-
if (siblingSoftMatchCount >= 2) {
|
|
3483
|
-
return null;
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
|
-
potentialSoftMatch = potentialSoftMatch.nextSibling;
|
|
3732
|
+
stop() {
|
|
3733
|
+
if (this.#started) {
|
|
3734
|
+
this.#started = false;
|
|
3735
|
+
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
3487
3736
|
}
|
|
3488
|
-
return potentialSoftMatch;
|
|
3489
3737
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
let content = parser.parseFromString(newContent, "text/html");
|
|
3495
|
-
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
|
3496
|
-
content.generatedByIdiomorph = true;
|
|
3497
|
-
return content;
|
|
3498
|
-
} else {
|
|
3499
|
-
let htmlElement = content.firstChild;
|
|
3500
|
-
if (htmlElement) {
|
|
3501
|
-
htmlElement.generatedByIdiomorph = true;
|
|
3502
|
-
return htmlElement;
|
|
3503
|
-
} else {
|
|
3504
|
-
return null;
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
} else {
|
|
3508
|
-
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
|
3509
|
-
let content = responseDoc.body.querySelector("template").content;
|
|
3510
|
-
content.generatedByIdiomorph = true;
|
|
3511
|
-
return content;
|
|
3512
|
-
}
|
|
3513
|
-
}
|
|
3514
|
-
function normalizeContent(newContent) {
|
|
3515
|
-
if (newContent == null) {
|
|
3516
|
-
const dummyParent = document.createElement("div");
|
|
3517
|
-
return dummyParent;
|
|
3518
|
-
} else if (newContent.generatedByIdiomorph) {
|
|
3519
|
-
return newContent;
|
|
3520
|
-
} else if (newContent instanceof Node) {
|
|
3521
|
-
const dummyParent = document.createElement("div");
|
|
3522
|
-
dummyParent.append(newContent);
|
|
3523
|
-
return dummyParent;
|
|
3524
|
-
} else {
|
|
3525
|
-
const dummyParent = document.createElement("div");
|
|
3526
|
-
for (const elt of [ ...newContent ]) {
|
|
3527
|
-
dummyParent.append(elt);
|
|
3528
|
-
}
|
|
3529
|
-
return dummyParent;
|
|
3738
|
+
connectStreamSource(source) {
|
|
3739
|
+
if (!this.streamSourceIsConnected(source)) {
|
|
3740
|
+
this.sources.add(source);
|
|
3741
|
+
source.addEventListener("message", this.receiveMessageEvent, false);
|
|
3530
3742
|
}
|
|
3531
3743
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
stack.push(previousSibling);
|
|
3537
|
-
previousSibling = previousSibling.previousSibling;
|
|
3538
|
-
}
|
|
3539
|
-
while (stack.length > 0) {
|
|
3540
|
-
let node = stack.pop();
|
|
3541
|
-
added.push(node);
|
|
3542
|
-
morphedNode.parentElement.insertBefore(node, morphedNode);
|
|
3543
|
-
}
|
|
3544
|
-
added.push(morphedNode);
|
|
3545
|
-
while (nextSibling != null) {
|
|
3546
|
-
stack.push(nextSibling);
|
|
3547
|
-
added.push(nextSibling);
|
|
3548
|
-
nextSibling = nextSibling.nextSibling;
|
|
3549
|
-
}
|
|
3550
|
-
while (stack.length > 0) {
|
|
3551
|
-
morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
|
|
3552
|
-
}
|
|
3553
|
-
return added;
|
|
3554
|
-
}
|
|
3555
|
-
function findBestNodeMatch(newContent, oldNode, ctx) {
|
|
3556
|
-
let currentElement;
|
|
3557
|
-
currentElement = newContent.firstChild;
|
|
3558
|
-
let bestElement = currentElement;
|
|
3559
|
-
let score = 0;
|
|
3560
|
-
while (currentElement) {
|
|
3561
|
-
let newScore = scoreElement(currentElement, oldNode, ctx);
|
|
3562
|
-
if (newScore > score) {
|
|
3563
|
-
bestElement = currentElement;
|
|
3564
|
-
score = newScore;
|
|
3565
|
-
}
|
|
3566
|
-
currentElement = currentElement.nextSibling;
|
|
3744
|
+
disconnectStreamSource(source) {
|
|
3745
|
+
if (this.streamSourceIsConnected(source)) {
|
|
3746
|
+
this.sources.delete(source);
|
|
3747
|
+
source.removeEventListener("message", this.receiveMessageEvent, false);
|
|
3567
3748
|
}
|
|
3568
|
-
return bestElement;
|
|
3569
3749
|
}
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3750
|
+
streamSourceIsConnected(source) {
|
|
3751
|
+
return this.sources.has(source);
|
|
3752
|
+
}
|
|
3753
|
+
inspectFetchResponse=event => {
|
|
3754
|
+
const response = fetchResponseFromEvent(event);
|
|
3755
|
+
if (response && fetchResponseIsStream(response)) {
|
|
3756
|
+
event.preventDefault();
|
|
3757
|
+
this.receiveMessageResponse(response);
|
|
3573
3758
|
}
|
|
3574
|
-
|
|
3759
|
+
};
|
|
3760
|
+
receiveMessageEvent=event => {
|
|
3761
|
+
if (this.#started && typeof event.data == "string") {
|
|
3762
|
+
this.receiveMessageHTML(event.data);
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
async receiveMessageResponse(response) {
|
|
3766
|
+
const html = await response.responseHTML;
|
|
3767
|
+
if (html) {
|
|
3768
|
+
this.receiveMessageHTML(html);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
receiveMessageHTML(html) {
|
|
3772
|
+
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
|
3575
3773
|
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
function fetchResponseFromEvent(event) {
|
|
3777
|
+
const fetchResponse = event.detail?.fetchResponse;
|
|
3778
|
+
if (fetchResponse instanceof FetchResponse) {
|
|
3779
|
+
return fetchResponse;
|
|
3581
3780
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
function fetchResponseIsStream(response) {
|
|
3784
|
+
const contentType = response.contentType ?? "";
|
|
3785
|
+
return contentType.startsWith(StreamMessage.contentType);
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
class ErrorRenderer extends Renderer {
|
|
3789
|
+
static renderElement(currentElement, newElement) {
|
|
3790
|
+
const {documentElement: documentElement, body: body} = document;
|
|
3791
|
+
documentElement.replaceChild(newElement, body);
|
|
3584
3792
|
}
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3793
|
+
async render() {
|
|
3794
|
+
this.replaceHeadAndBody();
|
|
3795
|
+
this.activateScriptElements();
|
|
3588
3796
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
}
|
|
3797
|
+
replaceHeadAndBody() {
|
|
3798
|
+
const {documentElement: documentElement, head: head} = document;
|
|
3799
|
+
documentElement.replaceChild(this.newHead, head);
|
|
3800
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
3594
3801
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
return matchCount;
|
|
3604
|
-
}
|
|
3605
|
-
function populateIdMapForNode(node, idMap) {
|
|
3606
|
-
let nodeParent = node.parentElement;
|
|
3607
|
-
let idElements = node.querySelectorAll("[id]");
|
|
3608
|
-
for (const elt of idElements) {
|
|
3609
|
-
let current = elt;
|
|
3610
|
-
while (current !== nodeParent && current != null) {
|
|
3611
|
-
let idSet = idMap.get(current);
|
|
3612
|
-
if (idSet == null) {
|
|
3613
|
-
idSet = new Set;
|
|
3614
|
-
idMap.set(current, idSet);
|
|
3615
|
-
}
|
|
3616
|
-
idSet.add(elt.id);
|
|
3617
|
-
current = current.parentElement;
|
|
3802
|
+
activateScriptElements() {
|
|
3803
|
+
for (const replaceableElement of this.scriptElements) {
|
|
3804
|
+
const parentNode = replaceableElement.parentNode;
|
|
3805
|
+
if (parentNode) {
|
|
3806
|
+
const element = activateScriptElement(replaceableElement);
|
|
3807
|
+
parentNode.replaceChild(element, replaceableElement);
|
|
3618
3808
|
}
|
|
3619
3809
|
}
|
|
3620
3810
|
}
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
populateIdMapForNode(oldContent, idMap);
|
|
3624
|
-
populateIdMapForNode(newContent, idMap);
|
|
3625
|
-
return idMap;
|
|
3811
|
+
get newHead() {
|
|
3812
|
+
return this.newSnapshot.headSnapshot.element;
|
|
3626
3813
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
}();
|
|
3814
|
+
get scriptElements() {
|
|
3815
|
+
return document.documentElement.querySelectorAll("script");
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3632
3818
|
|
|
3633
3819
|
class PageRenderer extends Renderer {
|
|
3634
3820
|
static renderElement(currentElement, newElement) {
|
|
@@ -3798,109 +3984,38 @@ class PageRenderer extends Renderer {
|
|
|
3798
3984
|
}
|
|
3799
3985
|
}
|
|
3800
3986
|
|
|
3801
|
-
class
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
}
|
|
3805
|
-
get renderMethod() {
|
|
3806
|
-
return "morph";
|
|
3807
|
-
}
|
|
3808
|
-
async #morphBody() {
|
|
3809
|
-
this.#morphElements(this.currentElement, this.newElement);
|
|
3810
|
-
this.#reloadRemoteFrames();
|
|
3811
|
-
dispatch("turbo:morph", {
|
|
3812
|
-
detail: {
|
|
3813
|
-
currentElement: this.currentElement,
|
|
3814
|
-
newElement: this.newElement
|
|
3815
|
-
}
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3818
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
3819
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
3820
|
-
Idiomorph.morph(currentElement, newElement, {
|
|
3821
|
-
ignoreActiveValue: true,
|
|
3822
|
-
morphStyle: morphStyle,
|
|
3987
|
+
class MorphingPageRenderer extends PageRenderer {
|
|
3988
|
+
static renderElement(currentElement, newElement) {
|
|
3989
|
+
morphElements(currentElement, newElement, {
|
|
3823
3990
|
callbacks: {
|
|
3824
|
-
|
|
3825
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
|
3826
|
-
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
|
3827
|
-
beforeNodeRemoved: this.#shouldRemoveElement,
|
|
3828
|
-
afterNodeMorphed: this.#didMorphElement
|
|
3829
|
-
}
|
|
3830
|
-
});
|
|
3831
|
-
}
|
|
3832
|
-
#shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
|
3833
|
-
#shouldMorphElement=(oldNode, newNode) => {
|
|
3834
|
-
if (oldNode instanceof HTMLElement) {
|
|
3835
|
-
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
|
3836
|
-
const event = dispatch("turbo:before-morph-element", {
|
|
3837
|
-
cancelable: true,
|
|
3838
|
-
target: oldNode,
|
|
3839
|
-
detail: {
|
|
3840
|
-
newElement: newNode
|
|
3841
|
-
}
|
|
3842
|
-
});
|
|
3843
|
-
return !event.defaultPrevented;
|
|
3844
|
-
} else {
|
|
3845
|
-
return false;
|
|
3846
|
-
}
|
|
3847
|
-
}
|
|
3848
|
-
};
|
|
3849
|
-
#shouldUpdateAttribute=(attributeName, target, mutationType) => {
|
|
3850
|
-
const event = dispatch("turbo:before-morph-attribute", {
|
|
3851
|
-
cancelable: true,
|
|
3852
|
-
target: target,
|
|
3853
|
-
detail: {
|
|
3854
|
-
attributeName: attributeName,
|
|
3855
|
-
mutationType: mutationType
|
|
3991
|
+
beforeNodeMorphed: element => !canRefreshFrame(element)
|
|
3856
3992
|
}
|
|
3857
3993
|
});
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
#didMorphElement=(oldNode, newNode) => {
|
|
3861
|
-
if (newNode instanceof HTMLElement) {
|
|
3862
|
-
dispatch("turbo:morph-element", {
|
|
3863
|
-
target: oldNode,
|
|
3864
|
-
detail: {
|
|
3865
|
-
newElement: newNode
|
|
3866
|
-
}
|
|
3867
|
-
});
|
|
3994
|
+
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
|
3995
|
+
if (canRefreshFrame(frame)) frame.reload();
|
|
3868
3996
|
}
|
|
3869
|
-
|
|
3870
|
-
#shouldRemoveElement=node => this.#shouldMorphElement(node);
|
|
3871
|
-
#reloadRemoteFrames() {
|
|
3872
|
-
this.#remoteFrames().forEach((frame => {
|
|
3873
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
3874
|
-
this.#renderFrameWithMorph(frame);
|
|
3875
|
-
frame.reload();
|
|
3876
|
-
}
|
|
3877
|
-
}));
|
|
3878
|
-
}
|
|
3879
|
-
#renderFrameWithMorph(frame) {
|
|
3880
|
-
frame.addEventListener("turbo:before-frame-render", (event => {
|
|
3881
|
-
event.detail.render = this.#morphFrameUpdate;
|
|
3882
|
-
}), {
|
|
3883
|
-
once: true
|
|
3884
|
-
});
|
|
3885
|
-
}
|
|
3886
|
-
#morphFrameUpdate=(currentElement, newElement) => {
|
|
3887
|
-
dispatch("turbo:before-frame-morph", {
|
|
3888
|
-
target: currentElement,
|
|
3997
|
+
dispatch("turbo:morph", {
|
|
3889
3998
|
detail: {
|
|
3890
3999
|
currentElement: currentElement,
|
|
3891
4000
|
newElement: newElement
|
|
3892
4001
|
}
|
|
3893
4002
|
});
|
|
3894
|
-
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
|
3895
|
-
};
|
|
3896
|
-
#isFrameReloadedWithMorph(element) {
|
|
3897
|
-
return element.src && element.refresh === "morph";
|
|
3898
4003
|
}
|
|
3899
|
-
|
|
3900
|
-
return
|
|
4004
|
+
async preservingPermanentElements(callback) {
|
|
4005
|
+
return await callback();
|
|
4006
|
+
}
|
|
4007
|
+
get renderMethod() {
|
|
4008
|
+
return "morph";
|
|
4009
|
+
}
|
|
4010
|
+
get shouldAutofocus() {
|
|
4011
|
+
return false;
|
|
3901
4012
|
}
|
|
3902
4013
|
}
|
|
3903
4014
|
|
|
4015
|
+
function canRefreshFrame(frame) {
|
|
4016
|
+
return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
|
|
4017
|
+
}
|
|
4018
|
+
|
|
3904
4019
|
class SnapshotCache {
|
|
3905
4020
|
keys=[];
|
|
3906
4021
|
snapshots={};
|
|
@@ -3954,8 +4069,8 @@ class PageView extends View {
|
|
|
3954
4069
|
}
|
|
3955
4070
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
3956
4071
|
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
|
3957
|
-
const rendererClass = shouldMorphPage ?
|
|
3958
|
-
const renderer = new rendererClass(this.snapshot, snapshot,
|
|
4072
|
+
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
4073
|
+
const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
|
3959
4074
|
if (!renderer.shouldRender) {
|
|
3960
4075
|
this.forceReloaded = true;
|
|
3961
4076
|
} else {
|
|
@@ -3965,7 +4080,7 @@ class PageView extends View {
|
|
|
3965
4080
|
}
|
|
3966
4081
|
renderError(snapshot, visit) {
|
|
3967
4082
|
visit?.changeHistory();
|
|
3968
|
-
const renderer = new ErrorRenderer(this.snapshot, snapshot,
|
|
4083
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
|
3969
4084
|
return this.render(renderer);
|
|
3970
4085
|
}
|
|
3971
4086
|
clearSnapshotCache() {
|
|
@@ -4083,11 +4198,8 @@ class Session {
|
|
|
4083
4198
|
frameRedirector=new FrameRedirector(this, document.documentElement);
|
|
4084
4199
|
streamMessageRenderer=new StreamMessageRenderer;
|
|
4085
4200
|
cache=new Cache(this);
|
|
4086
|
-
drive=true;
|
|
4087
4201
|
enabled=true;
|
|
4088
|
-
progressBarDelay=500;
|
|
4089
4202
|
started=false;
|
|
4090
|
-
formMode="on";
|
|
4091
4203
|
#pageRefreshDebouncePeriod=150;
|
|
4092
4204
|
constructor(recentRequests) {
|
|
4093
4205
|
this.recentRequests = recentRequests;
|
|
@@ -4146,10 +4258,11 @@ class Session {
|
|
|
4146
4258
|
}
|
|
4147
4259
|
refresh(url, requestId) {
|
|
4148
4260
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
4149
|
-
|
|
4150
|
-
|
|
4261
|
+
const isCurrentUrl = url === document.baseURI;
|
|
4262
|
+
if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
|
|
4151
4263
|
this.visit(url, {
|
|
4152
|
-
action: "replace"
|
|
4264
|
+
action: "replace",
|
|
4265
|
+
shouldCacheSnapshot: false
|
|
4153
4266
|
});
|
|
4154
4267
|
}
|
|
4155
4268
|
}
|
|
@@ -4166,10 +4279,26 @@ class Session {
|
|
|
4166
4279
|
this.view.clearSnapshotCache();
|
|
4167
4280
|
}
|
|
4168
4281
|
setProgressBarDelay(delay) {
|
|
4282
|
+
console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`");
|
|
4169
4283
|
this.progressBarDelay = delay;
|
|
4170
4284
|
}
|
|
4171
|
-
|
|
4172
|
-
|
|
4285
|
+
set progressBarDelay(delay) {
|
|
4286
|
+
config.drive.progressBarDelay = delay;
|
|
4287
|
+
}
|
|
4288
|
+
get progressBarDelay() {
|
|
4289
|
+
return config.drive.progressBarDelay;
|
|
4290
|
+
}
|
|
4291
|
+
set drive(value) {
|
|
4292
|
+
config.drive.enabled = value;
|
|
4293
|
+
}
|
|
4294
|
+
get drive() {
|
|
4295
|
+
return config.drive.enabled;
|
|
4296
|
+
}
|
|
4297
|
+
set formMode(value) {
|
|
4298
|
+
config.forms.mode = value;
|
|
4299
|
+
}
|
|
4300
|
+
get formMode() {
|
|
4301
|
+
return config.forms.mode;
|
|
4173
4302
|
}
|
|
4174
4303
|
get location() {
|
|
4175
4304
|
return this.history.location;
|
|
@@ -4219,7 +4348,7 @@ class Session {
|
|
|
4219
4348
|
}
|
|
4220
4349
|
submittedFormLinkToLocation() {}
|
|
4221
4350
|
canPrefetchRequestToLocation(link, location) {
|
|
4222
|
-
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
|
4351
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location);
|
|
4223
4352
|
}
|
|
4224
4353
|
willFollowLinkToLocation(link, location, event) {
|
|
4225
4354
|
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
|
|
@@ -4391,11 +4520,11 @@ class Session {
|
|
|
4391
4520
|
});
|
|
4392
4521
|
}
|
|
4393
4522
|
submissionIsNavigatable(form, submitter) {
|
|
4394
|
-
if (
|
|
4523
|
+
if (config.forms.mode == "off") {
|
|
4395
4524
|
return false;
|
|
4396
4525
|
} else {
|
|
4397
4526
|
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
|
4398
|
-
if (
|
|
4527
|
+
if (config.forms.mode == "optin") {
|
|
4399
4528
|
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
|
|
4400
4529
|
} else {
|
|
4401
4530
|
return submitterIsNavigatable && this.elementIsNavigatable(form);
|
|
@@ -4405,7 +4534,7 @@ class Session {
|
|
|
4405
4534
|
elementIsNavigatable(element) {
|
|
4406
4535
|
const container = findClosestRecursively(element, "[data-turbo]");
|
|
4407
4536
|
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
|
4408
|
-
if (
|
|
4537
|
+
if (config.drive.enabled || withinFrame) {
|
|
4409
4538
|
if (container) {
|
|
4410
4539
|
return container.getAttribute("data-turbo") != "false";
|
|
4411
4540
|
} else {
|
|
@@ -4473,15 +4602,18 @@ function clearCache() {
|
|
|
4473
4602
|
}
|
|
4474
4603
|
|
|
4475
4604
|
function setProgressBarDelay(delay) {
|
|
4476
|
-
|
|
4605
|
+
console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
4606
|
+
config.drive.progressBarDelay = delay;
|
|
4477
4607
|
}
|
|
4478
4608
|
|
|
4479
4609
|
function setConfirmMethod(confirmMethod) {
|
|
4480
|
-
|
|
4610
|
+
console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
4611
|
+
config.forms.confirm = confirmMethod;
|
|
4481
4612
|
}
|
|
4482
4613
|
|
|
4483
4614
|
function setFormMode(mode) {
|
|
4484
|
-
|
|
4615
|
+
console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
4616
|
+
config.forms.mode = mode;
|
|
4485
4617
|
}
|
|
4486
4618
|
|
|
4487
4619
|
var Turbo = Object.freeze({
|
|
@@ -4493,6 +4625,7 @@ var Turbo = Object.freeze({
|
|
|
4493
4625
|
PageSnapshot: PageSnapshot,
|
|
4494
4626
|
FrameRenderer: FrameRenderer,
|
|
4495
4627
|
fetch: fetchWithTurboHeaders,
|
|
4628
|
+
config: config,
|
|
4496
4629
|
start: start,
|
|
4497
4630
|
registerAdapter: registerAdapter,
|
|
4498
4631
|
visit: visit,
|
|
@@ -4514,6 +4647,7 @@ class FrameController {
|
|
|
4514
4647
|
#connected=false;
|
|
4515
4648
|
#hasBeenLoaded=false;
|
|
4516
4649
|
#ignoredAttributes=new Set;
|
|
4650
|
+
#shouldMorphFrame=false;
|
|
4517
4651
|
action=null;
|
|
4518
4652
|
constructor(element) {
|
|
4519
4653
|
this.element = element;
|
|
@@ -4561,18 +4695,13 @@ class FrameController {
|
|
|
4561
4695
|
}
|
|
4562
4696
|
}
|
|
4563
4697
|
sourceURLReloaded() {
|
|
4564
|
-
const {src: src} = this.element;
|
|
4565
|
-
this.#
|
|
4566
|
-
|
|
4567
|
-
}));
|
|
4698
|
+
const {refresh: refresh, src: src} = this.element;
|
|
4699
|
+
this.#shouldMorphFrame = src && refresh === "morph";
|
|
4700
|
+
this.element.removeAttribute("complete");
|
|
4568
4701
|
this.element.src = null;
|
|
4569
4702
|
this.element.src = src;
|
|
4570
4703
|
return this.element.loaded;
|
|
4571
4704
|
}
|
|
4572
|
-
completeChanged() {
|
|
4573
|
-
if (this.#isIgnoringChangesTo("complete")) return;
|
|
4574
|
-
this.#loadSourceURL();
|
|
4575
|
-
}
|
|
4576
4705
|
loadingStyleChanged() {
|
|
4577
4706
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
4578
4707
|
this.appearanceObserver.start();
|
|
@@ -4605,6 +4734,7 @@ class FrameController {
|
|
|
4605
4734
|
}
|
|
4606
4735
|
}
|
|
4607
4736
|
} finally {
|
|
4737
|
+
this.#shouldMorphFrame = false;
|
|
4608
4738
|
this.fetchResponseLoaded = () => Promise.resolve();
|
|
4609
4739
|
}
|
|
4610
4740
|
}
|
|
@@ -4717,9 +4847,10 @@ class FrameController {
|
|
|
4717
4847
|
};
|
|
4718
4848
|
async #loadFrameResponse(fetchResponse, document) {
|
|
4719
4849
|
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
|
4850
|
+
const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;
|
|
4720
4851
|
if (newFrameElement) {
|
|
4721
4852
|
const snapshot = new Snapshot(newFrameElement);
|
|
4722
|
-
const renderer = new
|
|
4853
|
+
const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);
|
|
4723
4854
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
4724
4855
|
this.changeHistory();
|
|
4725
4856
|
await this.view.render(renderer);
|
|
@@ -4904,13 +5035,11 @@ class FrameController {
|
|
|
4904
5035
|
return this.element.hasAttribute("complete");
|
|
4905
5036
|
}
|
|
4906
5037
|
set complete(value) {
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
}
|
|
4913
|
-
}));
|
|
5038
|
+
if (value) {
|
|
5039
|
+
this.element.setAttribute("complete", "");
|
|
5040
|
+
} else {
|
|
5041
|
+
this.element.removeAttribute("complete");
|
|
5042
|
+
}
|
|
4914
5043
|
}
|
|
4915
5044
|
get isActive() {
|
|
4916
5045
|
return this.element.isActive && this.#connected;
|
|
@@ -4980,12 +5109,24 @@ const StreamActions = {
|
|
|
4980
5109
|
this.targetElements.forEach((e => e.remove()));
|
|
4981
5110
|
},
|
|
4982
5111
|
replace() {
|
|
4983
|
-
|
|
5112
|
+
const method = this.getAttribute("method");
|
|
5113
|
+
this.targetElements.forEach((targetElement => {
|
|
5114
|
+
if (method === "morph") {
|
|
5115
|
+
morphElements(targetElement, this.templateContent);
|
|
5116
|
+
} else {
|
|
5117
|
+
targetElement.replaceWith(this.templateContent);
|
|
5118
|
+
}
|
|
5119
|
+
}));
|
|
4984
5120
|
},
|
|
4985
5121
|
update() {
|
|
5122
|
+
const method = this.getAttribute("method");
|
|
4986
5123
|
this.targetElements.forEach((targetElement => {
|
|
4987
|
-
|
|
4988
|
-
|
|
5124
|
+
if (method === "morph") {
|
|
5125
|
+
morphChildren(targetElement, this.templateContent);
|
|
5126
|
+
} else {
|
|
5127
|
+
targetElement.innerHTML = "";
|
|
5128
|
+
targetElement.append(this.templateContent);
|
|
5129
|
+
}
|
|
4989
5130
|
}));
|
|
4990
5131
|
},
|
|
4991
5132
|
refresh() {
|
|
@@ -5024,9 +5165,9 @@ class StreamElement extends HTMLElement {
|
|
|
5024
5165
|
this.duplicateChildren.forEach((c => c.remove()));
|
|
5025
5166
|
}
|
|
5026
5167
|
get duplicateChildren() {
|
|
5027
|
-
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
|
5028
|
-
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
|
5029
|
-
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
|
5168
|
+
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.getAttribute("id")));
|
|
5169
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
|
|
5170
|
+
return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
|
|
5030
5171
|
}
|
|
5031
5172
|
get performAction() {
|
|
5032
5173
|
if (this.action) {
|
|
@@ -5182,6 +5323,7 @@ var Turbo$1 = Object.freeze({
|
|
|
5182
5323
|
StreamSourceElement: StreamSourceElement,
|
|
5183
5324
|
cache: cache,
|
|
5184
5325
|
clearCache: clearCache,
|
|
5326
|
+
config: config,
|
|
5185
5327
|
connectStreamSource: connectStreamSource,
|
|
5186
5328
|
disconnectStreamSource: disconnectStreamSource,
|
|
5187
5329
|
fetch: fetchWithTurboHeaders,
|
|
@@ -5243,6 +5385,7 @@ function walk(obj) {
|
|
|
5243
5385
|
}
|
|
5244
5386
|
|
|
5245
5387
|
class TurboCableStreamSourceElement extends HTMLElement {
|
|
5388
|
+
static observedAttributes=[ "channel", "signed-stream-name" ];
|
|
5246
5389
|
async connectedCallback() {
|
|
5247
5390
|
connectStreamSource(this);
|
|
5248
5391
|
this.subscription = await subscribeTo(this.channel, {
|
|
@@ -5254,6 +5397,13 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
|
5254
5397
|
disconnectedCallback() {
|
|
5255
5398
|
disconnectStreamSource(this);
|
|
5256
5399
|
if (this.subscription) this.subscription.unsubscribe();
|
|
5400
|
+
this.subscriptionDisconnected();
|
|
5401
|
+
}
|
|
5402
|
+
attributeChangedCallback() {
|
|
5403
|
+
if (this.subscription) {
|
|
5404
|
+
this.disconnectedCallback();
|
|
5405
|
+
this.connectedCallback();
|
|
5406
|
+
}
|
|
5257
5407
|
}
|
|
5258
5408
|
dispatchMessageEvent(data) {
|
|
5259
5409
|
const event = new MessageEvent("message", {
|
|
@@ -5340,8 +5490,8 @@ window.Turbo = Turbo$1;
|
|
|
5340
5490
|
addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
|
|
5341
5491
|
|
|
5342
5492
|
var adapters = {
|
|
5343
|
-
logger:
|
|
5344
|
-
WebSocket:
|
|
5493
|
+
logger: typeof console !== "undefined" ? console : undefined,
|
|
5494
|
+
WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
|
|
5345
5495
|
};
|
|
5346
5496
|
|
|
5347
5497
|
var logger = {
|
|
@@ -5383,12 +5533,11 @@ class ConnectionMonitor {
|
|
|
5383
5533
|
isRunning() {
|
|
5384
5534
|
return this.startedAt && !this.stoppedAt;
|
|
5385
5535
|
}
|
|
5386
|
-
|
|
5536
|
+
recordMessage() {
|
|
5387
5537
|
this.pingedAt = now();
|
|
5388
5538
|
}
|
|
5389
5539
|
recordConnect() {
|
|
5390
5540
|
this.reconnectAttempts = 0;
|
|
5391
|
-
this.recordPing();
|
|
5392
5541
|
delete this.disconnectedAt;
|
|
5393
5542
|
logger.log("ConnectionMonitor recorded connect");
|
|
5394
5543
|
}
|
|
@@ -5587,6 +5736,7 @@ Connection.prototype.events = {
|
|
|
5587
5736
|
return;
|
|
5588
5737
|
}
|
|
5589
5738
|
const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
|
|
5739
|
+
this.monitor.recordMessage();
|
|
5590
5740
|
switch (type) {
|
|
5591
5741
|
case message_types.welcome:
|
|
5592
5742
|
if (this.triedToReconnect()) {
|
|
@@ -5602,7 +5752,7 @@ Connection.prototype.events = {
|
|
|
5602
5752
|
});
|
|
5603
5753
|
|
|
5604
5754
|
case message_types.ping:
|
|
5605
|
-
return
|
|
5755
|
+
return null;
|
|
5606
5756
|
|
|
5607
5757
|
case message_types.confirmation:
|
|
5608
5758
|
this.subscriptions.confirmSubscription(identifier);
|