@hotwired/turbo 7.1.0 → 7.3.0
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.
- package/README.md +5 -1
- package/dist/turbo.es2017-esm.js +1284 -655
- package/dist/turbo.es2017-umd.js +1298 -662
- package/dist/types/core/bardo.d.ts +7 -2
- package/dist/types/core/cache.d.ts +10 -0
- package/dist/types/core/drive/error_renderer.d.ts +2 -1
- package/dist/types/core/drive/form_submission.d.ts +20 -9
- package/dist/types/core/drive/head_snapshot.d.ts +6 -6
- package/dist/types/core/drive/history.d.ts +4 -4
- package/dist/types/core/drive/navigator.d.ts +2 -2
- package/dist/types/core/drive/page_renderer.d.ts +12 -7
- package/dist/types/core/drive/page_view.d.ts +10 -8
- package/dist/types/core/drive/preloader.d.ts +14 -0
- package/dist/types/core/drive/progress_bar.d.ts +1 -0
- package/dist/types/core/drive/visit.d.ts +17 -6
- package/dist/types/core/errors.d.ts +2 -0
- package/dist/types/core/frames/frame_controller.d.ts +56 -26
- package/dist/types/core/frames/frame_redirector.d.ts +10 -8
- package/dist/types/core/frames/frame_renderer.d.ts +8 -1
- package/dist/types/core/frames/frame_view.d.ts +3 -2
- package/dist/types/core/frames/link_interceptor.d.ts +3 -3
- package/dist/types/core/index.d.ts +12 -3
- package/dist/types/core/native/adapter.d.ts +2 -1
- package/dist/types/core/native/browser_adapter.d.ts +17 -8
- package/dist/types/core/renderer.d.ts +12 -6
- package/dist/types/core/session.d.ts +72 -17
- package/dist/types/core/snapshot.d.ts +6 -3
- package/dist/types/core/streams/stream_actions.d.ts +4 -2
- package/dist/types/core/streams/stream_message.d.ts +2 -6
- package/dist/types/core/streams/stream_message_renderer.d.ts +7 -0
- package/dist/types/core/types.d.ts +3 -4
- package/dist/types/core/url.d.ts +1 -1
- package/dist/types/core/view.d.ts +13 -7
- package/dist/types/elements/frame_element.d.ts +12 -6
- package/dist/types/elements/index.d.ts +1 -0
- package/dist/types/elements/stream_element.d.ts +8 -1
- package/dist/types/elements/stream_source_element.d.ts +7 -0
- package/dist/types/http/fetch_request.d.ts +18 -4
- package/dist/types/http/index.d.ts +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/observers/appearance_observer.d.ts +6 -6
- package/dist/types/observers/cache_observer.d.ts +5 -1
- package/dist/types/observers/form_link_click_observer.d.ts +14 -0
- package/dist/types/observers/form_submit_observer.d.ts +2 -1
- package/dist/types/observers/link_click_observer.d.ts +5 -4
- package/dist/types/polyfills/custom-elements-native-shim.d.ts +1 -0
- package/dist/types/polyfills/submit-event.d.ts +1 -7
- package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
- package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
- package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
- package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
- package/dist/types/tests/functional/drive_tests.d.ts +1 -8
- package/dist/types/tests/functional/form_mode_tests.d.ts +1 -0
- package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
- package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
- package/dist/types/tests/functional/frame_tests.d.ts +7 -51
- package/dist/types/tests/functional/import_tests.d.ts +1 -4
- package/dist/types/tests/functional/loading_tests.d.ts +1 -13
- package/dist/types/tests/functional/navigation_tests.d.ts +1 -38
- package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
- package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
- package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
- package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
- package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
- package/dist/types/tests/functional/stream_tests.d.ts +1 -6
- package/dist/types/tests/functional/visit_tests.d.ts +1 -15
- package/dist/types/tests/helpers/dom_test_case.d.ts +1 -2
- package/dist/types/tests/helpers/page.d.ts +60 -0
- package/dist/types/tests/integration/ujs_tests.d.ts +1 -0
- package/dist/types/tests/unit/deprecated_adapter_support_tests.d.ts +1 -0
- package/dist/types/tests/unit/export_tests.d.ts +1 -0
- package/dist/types/tests/unit/stream_element_tests.d.ts +0 -10
- package/dist/types/util.d.ts +15 -3
- package/package.json +32 -13
- package/CHANGELOG.md +0 -3
- package/dist/types/core/frames/form_interceptor.d.ts +0 -12
- package/dist/types/tests/functional/index.d.ts +0 -17
- package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
- package/dist/types/tests/helpers/intern_test_case.d.ts +0 -20
- package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
- package/dist/types/tests/helpers/turbo_drive_test_case.d.ts +0 -21
- package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +0 -24
- package/dist/types/tests/unit/index.d.ts +0 -2
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.
|
|
3
|
-
Copyright ©
|
|
2
|
+
Turbo 7.3.0
|
|
3
|
+
Copyright © 2023 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
6
|
-
if (window.Reflect === undefined ||
|
|
6
|
+
if (window.Reflect === undefined ||
|
|
7
|
+
window.customElements === undefined ||
|
|
7
8
|
window.customElements.polyfillWrapFlushCallback) {
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
const BuiltInHTMLElement = HTMLElement;
|
|
11
12
|
const wrapperForTheName = {
|
|
12
|
-
|
|
13
|
+
HTMLElement: function HTMLElement() {
|
|
13
14
|
return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
|
|
14
|
-
}
|
|
15
|
+
},
|
|
15
16
|
};
|
|
16
|
-
window.HTMLElement =
|
|
17
|
-
wrapperForTheName['HTMLElement'];
|
|
17
|
+
window.HTMLElement = wrapperForTheName["HTMLElement"];
|
|
18
18
|
HTMLElement.prototype = BuiltInHTMLElement.prototype;
|
|
19
19
|
HTMLElement.prototype.constructor = HTMLElement;
|
|
20
20
|
Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
|
|
@@ -72,7 +72,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
72
72
|
}
|
|
73
73
|
})(HTMLFormElement.prototype);
|
|
74
74
|
|
|
75
|
-
const submittersByForm = new WeakMap;
|
|
75
|
+
const submittersByForm = new WeakMap();
|
|
76
76
|
function findSubmitterFromClickTarget(target) {
|
|
77
77
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
78
78
|
const candidate = element ? element.closest("input, button") : null;
|
|
@@ -87,23 +87,20 @@ function clickCaptured(event) {
|
|
|
87
87
|
(function () {
|
|
88
88
|
if ("submitter" in Event.prototype)
|
|
89
89
|
return;
|
|
90
|
-
let prototype;
|
|
90
|
+
let prototype = window.Event.prototype;
|
|
91
91
|
if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
|
|
92
92
|
prototype = window.SubmitEvent.prototype;
|
|
93
93
|
}
|
|
94
94
|
else if ("SubmitEvent" in window) {
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
-
else {
|
|
98
|
-
prototype = window.Event.prototype;
|
|
99
|
-
}
|
|
100
97
|
addEventListener("click", clickCaptured, true);
|
|
101
98
|
Object.defineProperty(prototype, "submitter", {
|
|
102
99
|
get() {
|
|
103
100
|
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
|
|
104
101
|
return submittersByForm.get(this.target);
|
|
105
102
|
}
|
|
106
|
-
}
|
|
103
|
+
},
|
|
107
104
|
});
|
|
108
105
|
})();
|
|
109
106
|
|
|
@@ -113,14 +110,14 @@ var FrameLoadingStyle;
|
|
|
113
110
|
FrameLoadingStyle["lazy"] = "lazy";
|
|
114
111
|
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
|
115
112
|
class FrameElement extends HTMLElement {
|
|
113
|
+
static get observedAttributes() {
|
|
114
|
+
return ["disabled", "complete", "loading", "src"];
|
|
115
|
+
}
|
|
116
116
|
constructor() {
|
|
117
117
|
super();
|
|
118
118
|
this.loaded = Promise.resolve();
|
|
119
119
|
this.delegate = new FrameElement.delegateConstructor(this);
|
|
120
120
|
}
|
|
121
|
-
static get observedAttributes() {
|
|
122
|
-
return ["disabled", "loading", "src"];
|
|
123
|
-
}
|
|
124
121
|
connectedCallback() {
|
|
125
122
|
this.delegate.connect();
|
|
126
123
|
}
|
|
@@ -128,14 +125,15 @@ class FrameElement extends HTMLElement {
|
|
|
128
125
|
this.delegate.disconnect();
|
|
129
126
|
}
|
|
130
127
|
reload() {
|
|
131
|
-
|
|
132
|
-
this.src = null;
|
|
133
|
-
this.src = src;
|
|
128
|
+
return this.delegate.sourceURLReloaded();
|
|
134
129
|
}
|
|
135
130
|
attributeChangedCallback(name) {
|
|
136
131
|
if (name == "loading") {
|
|
137
132
|
this.delegate.loadingStyleChanged();
|
|
138
133
|
}
|
|
134
|
+
else if (name == "complete") {
|
|
135
|
+
this.delegate.completeChanged();
|
|
136
|
+
}
|
|
139
137
|
else if (name == "src") {
|
|
140
138
|
this.delegate.sourceURLChanged();
|
|
141
139
|
}
|
|
@@ -200,8 +198,10 @@ class FrameElement extends HTMLElement {
|
|
|
200
198
|
}
|
|
201
199
|
function frameLoadingStyleFromString(style) {
|
|
202
200
|
switch (style.toLowerCase()) {
|
|
203
|
-
case "lazy":
|
|
204
|
-
|
|
201
|
+
case "lazy":
|
|
202
|
+
return FrameLoadingStyle.lazy;
|
|
203
|
+
default:
|
|
204
|
+
return FrameLoadingStyle.eager;
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -213,7 +213,7 @@ function getAnchor(url) {
|
|
|
213
213
|
if (url.hash) {
|
|
214
214
|
return url.hash.slice(1);
|
|
215
215
|
}
|
|
216
|
-
else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
|
216
|
+
else if ((anchorMatch = url.href.match(/#(.*)$/))) {
|
|
217
217
|
return anchorMatch[1];
|
|
218
218
|
}
|
|
219
219
|
}
|
|
@@ -225,7 +225,7 @@ function getExtension(url) {
|
|
|
225
225
|
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
226
226
|
}
|
|
227
227
|
function isHTML(url) {
|
|
228
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
|
228
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
|
229
229
|
}
|
|
230
230
|
function isPrefixedBy(baseURL, url) {
|
|
231
231
|
const prefix = getPrefix(url);
|
|
@@ -236,9 +236,7 @@ function locationIsVisitable(location, rootLocation) {
|
|
|
236
236
|
}
|
|
237
237
|
function getRequestURL(url) {
|
|
238
238
|
const anchor = getAnchor(url);
|
|
239
|
-
return anchor != null
|
|
240
|
-
? url.href.slice(0, -(anchor.length + 1))
|
|
241
|
-
: url.href;
|
|
239
|
+
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
|
242
240
|
}
|
|
243
241
|
function toCacheKey(url) {
|
|
244
242
|
return getRequestURL(url);
|
|
@@ -306,8 +304,39 @@ class FetchResponse {
|
|
|
306
304
|
}
|
|
307
305
|
}
|
|
308
306
|
|
|
307
|
+
function activateScriptElement(element) {
|
|
308
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
|
309
|
+
return element;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const createdScriptElement = document.createElement("script");
|
|
313
|
+
const cspNonce = getMetaContent("csp-nonce");
|
|
314
|
+
if (cspNonce) {
|
|
315
|
+
createdScriptElement.nonce = cspNonce;
|
|
316
|
+
}
|
|
317
|
+
createdScriptElement.textContent = element.textContent;
|
|
318
|
+
createdScriptElement.async = false;
|
|
319
|
+
copyElementAttributes(createdScriptElement, element);
|
|
320
|
+
return createdScriptElement;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
|
324
|
+
for (const { name, value } of sourceElement.attributes) {
|
|
325
|
+
destinationElement.setAttribute(name, value);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function createDocumentFragment(html) {
|
|
329
|
+
const template = document.createElement("template");
|
|
330
|
+
template.innerHTML = html;
|
|
331
|
+
return template.content;
|
|
332
|
+
}
|
|
309
333
|
function dispatch(eventName, { target, cancelable, detail } = {}) {
|
|
310
|
-
const event = new CustomEvent(eventName, {
|
|
334
|
+
const event = new CustomEvent(eventName, {
|
|
335
|
+
cancelable,
|
|
336
|
+
bubbles: true,
|
|
337
|
+
composed: true,
|
|
338
|
+
detail,
|
|
339
|
+
});
|
|
311
340
|
if (target && target.isConnected) {
|
|
312
341
|
target.dispatchEvent(event);
|
|
313
342
|
}
|
|
@@ -317,10 +346,10 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
|
|
|
317
346
|
return event;
|
|
318
347
|
}
|
|
319
348
|
function nextAnimationFrame() {
|
|
320
|
-
return new Promise(resolve => requestAnimationFrame(() => resolve()));
|
|
349
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
321
350
|
}
|
|
322
351
|
function nextEventLoopTick() {
|
|
323
|
-
return new Promise(resolve => setTimeout(() => resolve(), 0));
|
|
352
|
+
return new Promise((resolve) => setTimeout(() => resolve(), 0));
|
|
324
353
|
}
|
|
325
354
|
function nextMicrotask() {
|
|
326
355
|
return Promise.resolve();
|
|
@@ -332,7 +361,7 @@ function unindent(strings, ...values) {
|
|
|
332
361
|
const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
|
|
333
362
|
const match = lines[0].match(/^\s+/);
|
|
334
363
|
const indent = match ? match[0].length : 0;
|
|
335
|
-
return lines.map(line => line.slice(indent)).join("\n");
|
|
364
|
+
return lines.map((line) => line.slice(indent)).join("\n");
|
|
336
365
|
}
|
|
337
366
|
function interpolate(strings, values) {
|
|
338
367
|
return strings.reduce((result, string, i) => {
|
|
@@ -341,7 +370,8 @@ function interpolate(strings, values) {
|
|
|
341
370
|
}, "");
|
|
342
371
|
}
|
|
343
372
|
function uuid() {
|
|
344
|
-
return Array.
|
|
373
|
+
return Array.from({ length: 36 })
|
|
374
|
+
.map((_, i) => {
|
|
345
375
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
346
376
|
return "-";
|
|
347
377
|
}
|
|
@@ -354,15 +384,19 @@ function uuid() {
|
|
|
354
384
|
else {
|
|
355
385
|
return Math.floor(Math.random() * 15).toString(16);
|
|
356
386
|
}
|
|
357
|
-
})
|
|
387
|
+
})
|
|
388
|
+
.join("");
|
|
358
389
|
}
|
|
359
390
|
function getAttribute(attributeName, ...elements) {
|
|
360
|
-
for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
|
|
391
|
+
for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
|
|
361
392
|
if (typeof value == "string")
|
|
362
393
|
return value;
|
|
363
394
|
}
|
|
364
395
|
return null;
|
|
365
396
|
}
|
|
397
|
+
function hasAttribute(attributeName, ...elements) {
|
|
398
|
+
return elements.some((element) => element && element.hasAttribute(attributeName));
|
|
399
|
+
}
|
|
366
400
|
function markAsBusy(...elements) {
|
|
367
401
|
for (const element of elements) {
|
|
368
402
|
if (element.localName == "turbo-frame") {
|
|
@@ -379,6 +413,58 @@ function clearBusyState(...elements) {
|
|
|
379
413
|
element.removeAttribute("aria-busy");
|
|
380
414
|
}
|
|
381
415
|
}
|
|
416
|
+
function waitForLoad(element, timeoutInMilliseconds = 2000) {
|
|
417
|
+
return new Promise((resolve) => {
|
|
418
|
+
const onComplete = () => {
|
|
419
|
+
element.removeEventListener("error", onComplete);
|
|
420
|
+
element.removeEventListener("load", onComplete);
|
|
421
|
+
resolve();
|
|
422
|
+
};
|
|
423
|
+
element.addEventListener("load", onComplete, { once: true });
|
|
424
|
+
element.addEventListener("error", onComplete, { once: true });
|
|
425
|
+
setTimeout(resolve, timeoutInMilliseconds);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function getHistoryMethodForAction(action) {
|
|
429
|
+
switch (action) {
|
|
430
|
+
case "replace":
|
|
431
|
+
return history.replaceState;
|
|
432
|
+
case "advance":
|
|
433
|
+
case "restore":
|
|
434
|
+
return history.pushState;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function isAction(action) {
|
|
438
|
+
return action == "advance" || action == "replace" || action == "restore";
|
|
439
|
+
}
|
|
440
|
+
function getVisitAction(...elements) {
|
|
441
|
+
const action = getAttribute("data-turbo-action", ...elements);
|
|
442
|
+
return isAction(action) ? action : null;
|
|
443
|
+
}
|
|
444
|
+
function getMetaElement(name) {
|
|
445
|
+
return document.querySelector(`meta[name="${name}"]`);
|
|
446
|
+
}
|
|
447
|
+
function getMetaContent(name) {
|
|
448
|
+
const element = getMetaElement(name);
|
|
449
|
+
return element && element.content;
|
|
450
|
+
}
|
|
451
|
+
function setMetaContent(name, content) {
|
|
452
|
+
let element = getMetaElement(name);
|
|
453
|
+
if (!element) {
|
|
454
|
+
element = document.createElement("meta");
|
|
455
|
+
element.setAttribute("name", name);
|
|
456
|
+
document.head.appendChild(element);
|
|
457
|
+
}
|
|
458
|
+
element.setAttribute("content", content);
|
|
459
|
+
return element;
|
|
460
|
+
}
|
|
461
|
+
function findClosestRecursively(element, selector) {
|
|
462
|
+
var _a;
|
|
463
|
+
if (element instanceof Element) {
|
|
464
|
+
return (element.closest(selector) ||
|
|
465
|
+
findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
382
468
|
|
|
383
469
|
var FetchMethod;
|
|
384
470
|
(function (FetchMethod) {
|
|
@@ -390,17 +476,22 @@ var FetchMethod;
|
|
|
390
476
|
})(FetchMethod || (FetchMethod = {}));
|
|
391
477
|
function fetchMethodFromString(method) {
|
|
392
478
|
switch (method.toLowerCase()) {
|
|
393
|
-
case "get":
|
|
394
|
-
|
|
395
|
-
case "
|
|
396
|
-
|
|
397
|
-
case "
|
|
479
|
+
case "get":
|
|
480
|
+
return FetchMethod.get;
|
|
481
|
+
case "post":
|
|
482
|
+
return FetchMethod.post;
|
|
483
|
+
case "put":
|
|
484
|
+
return FetchMethod.put;
|
|
485
|
+
case "patch":
|
|
486
|
+
return FetchMethod.patch;
|
|
487
|
+
case "delete":
|
|
488
|
+
return FetchMethod.delete;
|
|
398
489
|
}
|
|
399
490
|
}
|
|
400
491
|
class FetchRequest {
|
|
401
|
-
constructor(delegate, method, location, body = new URLSearchParams, target = null) {
|
|
402
|
-
this.abortController = new AbortController;
|
|
403
|
-
this.resolveRequestPromise = (
|
|
492
|
+
constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
|
|
493
|
+
this.abortController = new AbortController();
|
|
494
|
+
this.resolveRequestPromise = (_value) => { };
|
|
404
495
|
this.delegate = delegate;
|
|
405
496
|
this.method = method;
|
|
406
497
|
this.headers = this.defaultHeaders;
|
|
@@ -421,9 +512,8 @@ class FetchRequest {
|
|
|
421
512
|
this.abortController.abort();
|
|
422
513
|
}
|
|
423
514
|
async perform() {
|
|
424
|
-
var _a, _b;
|
|
425
515
|
const { fetchOptions } = this;
|
|
426
|
-
|
|
516
|
+
this.delegate.prepareRequest(this);
|
|
427
517
|
await this.allowRequestToBeIntercepted(fetchOptions);
|
|
428
518
|
try {
|
|
429
519
|
this.delegate.requestStarted(this);
|
|
@@ -431,8 +521,10 @@ class FetchRequest {
|
|
|
431
521
|
return await this.receive(response);
|
|
432
522
|
}
|
|
433
523
|
catch (error) {
|
|
434
|
-
if (error.name !==
|
|
435
|
-
this.
|
|
524
|
+
if (error.name !== "AbortError") {
|
|
525
|
+
if (this.willDelegateErrorHandling(error)) {
|
|
526
|
+
this.delegate.requestErrored(this, error);
|
|
527
|
+
}
|
|
436
528
|
throw error;
|
|
437
529
|
}
|
|
438
530
|
}
|
|
@@ -442,7 +534,11 @@ class FetchRequest {
|
|
|
442
534
|
}
|
|
443
535
|
async receive(response) {
|
|
444
536
|
const fetchResponse = new FetchResponse(response);
|
|
445
|
-
const event = dispatch("turbo:before-fetch-response", {
|
|
537
|
+
const event = dispatch("turbo:before-fetch-response", {
|
|
538
|
+
cancelable: true,
|
|
539
|
+
detail: { fetchResponse },
|
|
540
|
+
target: this.target,
|
|
541
|
+
});
|
|
446
542
|
if (event.defaultPrevented) {
|
|
447
543
|
this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
|
|
448
544
|
}
|
|
@@ -461,42 +557,53 @@ class FetchRequest {
|
|
|
461
557
|
credentials: "same-origin",
|
|
462
558
|
headers: this.headers,
|
|
463
559
|
redirect: "follow",
|
|
464
|
-
body: this.
|
|
560
|
+
body: this.isSafe ? null : this.body,
|
|
465
561
|
signal: this.abortSignal,
|
|
466
|
-
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
|
|
562
|
+
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
|
|
467
563
|
};
|
|
468
564
|
}
|
|
469
565
|
get defaultHeaders() {
|
|
470
566
|
return {
|
|
471
|
-
|
|
567
|
+
Accept: "text/html, application/xhtml+xml",
|
|
472
568
|
};
|
|
473
569
|
}
|
|
474
|
-
get
|
|
475
|
-
return this.method
|
|
570
|
+
get isSafe() {
|
|
571
|
+
return this.method === FetchMethod.get;
|
|
476
572
|
}
|
|
477
573
|
get abortSignal() {
|
|
478
574
|
return this.abortController.signal;
|
|
479
575
|
}
|
|
576
|
+
acceptResponseType(mimeType) {
|
|
577
|
+
this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
|
|
578
|
+
}
|
|
480
579
|
async allowRequestToBeIntercepted(fetchOptions) {
|
|
481
|
-
const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
|
|
580
|
+
const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
|
|
482
581
|
const event = dispatch("turbo:before-fetch-request", {
|
|
483
582
|
cancelable: true,
|
|
484
583
|
detail: {
|
|
485
584
|
fetchOptions,
|
|
486
585
|
url: this.url,
|
|
487
|
-
resume: this.resolveRequestPromise
|
|
586
|
+
resume: this.resolveRequestPromise,
|
|
488
587
|
},
|
|
489
|
-
target: this.target
|
|
588
|
+
target: this.target,
|
|
490
589
|
});
|
|
491
590
|
if (event.defaultPrevented)
|
|
492
591
|
await requestInterception;
|
|
493
592
|
}
|
|
593
|
+
willDelegateErrorHandling(error) {
|
|
594
|
+
const event = dispatch("turbo:fetch-request-error", {
|
|
595
|
+
target: this.target,
|
|
596
|
+
cancelable: true,
|
|
597
|
+
detail: { request: this, error: error },
|
|
598
|
+
});
|
|
599
|
+
return !event.defaultPrevented;
|
|
600
|
+
}
|
|
494
601
|
}
|
|
495
602
|
|
|
496
603
|
class AppearanceObserver {
|
|
497
604
|
constructor(delegate, element) {
|
|
498
605
|
this.started = false;
|
|
499
|
-
this.intersect = entries => {
|
|
606
|
+
this.intersect = (entries) => {
|
|
500
607
|
const lastEntry = entries.slice(-1)[0];
|
|
501
608
|
if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
|
|
502
609
|
this.delegate.elementAppearedInViewport(this.element);
|
|
@@ -521,40 +628,29 @@ class AppearanceObserver {
|
|
|
521
628
|
}
|
|
522
629
|
|
|
523
630
|
class StreamMessage {
|
|
524
|
-
constructor(html) {
|
|
525
|
-
this.templateElement = document.createElement("template");
|
|
526
|
-
this.templateElement.innerHTML = html;
|
|
527
|
-
}
|
|
528
631
|
static wrap(message) {
|
|
529
632
|
if (typeof message == "string") {
|
|
530
|
-
return new this(message);
|
|
633
|
+
return new this(createDocumentFragment(message));
|
|
531
634
|
}
|
|
532
635
|
else {
|
|
533
636
|
return message;
|
|
534
637
|
}
|
|
535
638
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
for (const element of this.foreignElements) {
|
|
539
|
-
fragment.appendChild(document.importNode(element, true));
|
|
540
|
-
}
|
|
541
|
-
return fragment;
|
|
542
|
-
}
|
|
543
|
-
get foreignElements() {
|
|
544
|
-
return this.templateChildren.reduce((streamElements, child) => {
|
|
545
|
-
if (child.tagName.toLowerCase() == "turbo-stream") {
|
|
546
|
-
return [...streamElements, child];
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
return streamElements;
|
|
550
|
-
}
|
|
551
|
-
}, []);
|
|
552
|
-
}
|
|
553
|
-
get templateChildren() {
|
|
554
|
-
return Array.from(this.templateElement.content.children);
|
|
639
|
+
constructor(fragment) {
|
|
640
|
+
this.fragment = importStreamElements(fragment);
|
|
555
641
|
}
|
|
556
642
|
}
|
|
557
643
|
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
|
644
|
+
function importStreamElements(fragment) {
|
|
645
|
+
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
|
646
|
+
const streamElement = document.importNode(element, true);
|
|
647
|
+
for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
|
|
648
|
+
inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
|
|
649
|
+
}
|
|
650
|
+
element.replaceWith(streamElement);
|
|
651
|
+
}
|
|
652
|
+
return fragment;
|
|
653
|
+
}
|
|
558
654
|
|
|
559
655
|
var FormSubmissionState;
|
|
560
656
|
(function (FormSubmissionState) {
|
|
@@ -573,12 +669,18 @@ var FormEnctype;
|
|
|
573
669
|
})(FormEnctype || (FormEnctype = {}));
|
|
574
670
|
function formEnctypeFromString(encoding) {
|
|
575
671
|
switch (encoding.toLowerCase()) {
|
|
576
|
-
case FormEnctype.multipart:
|
|
577
|
-
|
|
578
|
-
|
|
672
|
+
case FormEnctype.multipart:
|
|
673
|
+
return FormEnctype.multipart;
|
|
674
|
+
case FormEnctype.plain:
|
|
675
|
+
return FormEnctype.plain;
|
|
676
|
+
default:
|
|
677
|
+
return FormEnctype.urlEncoded;
|
|
579
678
|
}
|
|
580
679
|
}
|
|
581
680
|
class FormSubmission {
|
|
681
|
+
static confirmMethod(message, _element, _submitter) {
|
|
682
|
+
return Promise.resolve(confirm(message));
|
|
683
|
+
}
|
|
582
684
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
|
583
685
|
this.state = FormSubmissionState.initialized;
|
|
584
686
|
this.delegate = delegate;
|
|
@@ -592,9 +694,6 @@ class FormSubmission {
|
|
|
592
694
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
593
695
|
this.mustRedirect = mustRedirect;
|
|
594
696
|
}
|
|
595
|
-
static confirmMethod(message, element) {
|
|
596
|
-
return confirm(message);
|
|
597
|
-
}
|
|
598
697
|
get method() {
|
|
599
698
|
var _a;
|
|
600
699
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
|
@@ -602,8 +701,13 @@ class FormSubmission {
|
|
|
602
701
|
}
|
|
603
702
|
get action() {
|
|
604
703
|
var _a;
|
|
605
|
-
const formElementAction = typeof this.formElement.action ===
|
|
606
|
-
|
|
704
|
+
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
|
|
705
|
+
if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
|
|
706
|
+
return this.submitter.getAttribute("formaction") || "";
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
return this.formElement.getAttribute("action") || formElementAction || "";
|
|
710
|
+
}
|
|
607
711
|
}
|
|
608
712
|
get body() {
|
|
609
713
|
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
|
@@ -617,24 +721,19 @@ class FormSubmission {
|
|
|
617
721
|
var _a;
|
|
618
722
|
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
|
619
723
|
}
|
|
620
|
-
get
|
|
621
|
-
return this.fetchRequest.
|
|
724
|
+
get isSafe() {
|
|
725
|
+
return this.fetchRequest.isSafe;
|
|
622
726
|
}
|
|
623
727
|
get stringFormData() {
|
|
624
728
|
return [...this.formData].reduce((entries, [name, value]) => {
|
|
625
729
|
return entries.concat(typeof value == "string" ? [[name, value]] : []);
|
|
626
730
|
}, []);
|
|
627
731
|
}
|
|
628
|
-
get confirmationMessage() {
|
|
629
|
-
return this.formElement.getAttribute("data-turbo-confirm");
|
|
630
|
-
}
|
|
631
|
-
get needsConfirmation() {
|
|
632
|
-
return this.confirmationMessage !== null;
|
|
633
|
-
}
|
|
634
732
|
async start() {
|
|
635
733
|
const { initialized, requesting } = FormSubmissionState;
|
|
636
|
-
|
|
637
|
-
|
|
734
|
+
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
|
735
|
+
if (typeof confirmationMessage === "string") {
|
|
736
|
+
const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
|
638
737
|
if (!answer) {
|
|
639
738
|
return;
|
|
640
739
|
}
|
|
@@ -652,20 +751,26 @@ class FormSubmission {
|
|
|
652
751
|
return true;
|
|
653
752
|
}
|
|
654
753
|
}
|
|
655
|
-
|
|
656
|
-
if (!request.
|
|
754
|
+
prepareRequest(request) {
|
|
755
|
+
if (!request.isSafe) {
|
|
657
756
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
|
658
757
|
if (token) {
|
|
659
|
-
headers["X-CSRF-Token"] = token;
|
|
758
|
+
request.headers["X-CSRF-Token"] = token;
|
|
660
759
|
}
|
|
661
|
-
|
|
760
|
+
}
|
|
761
|
+
if (this.requestAcceptsTurboStreamResponse(request)) {
|
|
762
|
+
request.acceptResponseType(StreamMessage.contentType);
|
|
662
763
|
}
|
|
663
764
|
}
|
|
664
|
-
requestStarted(
|
|
765
|
+
requestStarted(_request) {
|
|
665
766
|
var _a;
|
|
666
767
|
this.state = FormSubmissionState.waiting;
|
|
667
768
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
668
|
-
|
|
769
|
+
this.setSubmitsWith();
|
|
770
|
+
dispatch("turbo:submit-start", {
|
|
771
|
+
target: this.formElement,
|
|
772
|
+
detail: { formSubmission: this },
|
|
773
|
+
});
|
|
669
774
|
this.delegate.formSubmissionStarted(this);
|
|
670
775
|
}
|
|
671
776
|
requestPreventedHandlingResponse(request, response) {
|
|
@@ -693,23 +798,58 @@ class FormSubmission {
|
|
|
693
798
|
this.result = { success: false, error };
|
|
694
799
|
this.delegate.formSubmissionErrored(this, error);
|
|
695
800
|
}
|
|
696
|
-
requestFinished(
|
|
801
|
+
requestFinished(_request) {
|
|
697
802
|
var _a;
|
|
698
803
|
this.state = FormSubmissionState.stopped;
|
|
699
804
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
700
|
-
|
|
805
|
+
this.resetSubmitterText();
|
|
806
|
+
dispatch("turbo:submit-end", {
|
|
807
|
+
target: this.formElement,
|
|
808
|
+
detail: Object.assign({ formSubmission: this }, this.result),
|
|
809
|
+
});
|
|
701
810
|
this.delegate.formSubmissionFinished(this);
|
|
702
811
|
}
|
|
812
|
+
setSubmitsWith() {
|
|
813
|
+
if (!this.submitter || !this.submitsWith)
|
|
814
|
+
return;
|
|
815
|
+
if (this.submitter.matches("button")) {
|
|
816
|
+
this.originalSubmitText = this.submitter.innerHTML;
|
|
817
|
+
this.submitter.innerHTML = this.submitsWith;
|
|
818
|
+
}
|
|
819
|
+
else if (this.submitter.matches("input")) {
|
|
820
|
+
const input = this.submitter;
|
|
821
|
+
this.originalSubmitText = input.value;
|
|
822
|
+
input.value = this.submitsWith;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
resetSubmitterText() {
|
|
826
|
+
if (!this.submitter || !this.originalSubmitText)
|
|
827
|
+
return;
|
|
828
|
+
if (this.submitter.matches("button")) {
|
|
829
|
+
this.submitter.innerHTML = this.originalSubmitText;
|
|
830
|
+
}
|
|
831
|
+
else if (this.submitter.matches("input")) {
|
|
832
|
+
const input = this.submitter;
|
|
833
|
+
input.value = this.originalSubmitText;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
703
836
|
requestMustRedirect(request) {
|
|
704
|
-
return !request.
|
|
837
|
+
return !request.isSafe && this.mustRedirect;
|
|
838
|
+
}
|
|
839
|
+
requestAcceptsTurboStreamResponse(request) {
|
|
840
|
+
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
|
841
|
+
}
|
|
842
|
+
get submitsWith() {
|
|
843
|
+
var _a;
|
|
844
|
+
return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
|
|
705
845
|
}
|
|
706
846
|
}
|
|
707
847
|
function buildFormData(formElement, submitter) {
|
|
708
848
|
const formData = new FormData(formElement);
|
|
709
849
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
|
710
850
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
|
711
|
-
if (name
|
|
712
|
-
formData.append(name, value);
|
|
851
|
+
if (name) {
|
|
852
|
+
formData.append(name, value || "");
|
|
713
853
|
}
|
|
714
854
|
return formData;
|
|
715
855
|
}
|
|
@@ -723,15 +863,11 @@ function getCookieValue(cookieName) {
|
|
|
723
863
|
}
|
|
724
864
|
}
|
|
725
865
|
}
|
|
726
|
-
function getMetaContent(name) {
|
|
727
|
-
const element = document.querySelector(`meta[name="${name}"]`);
|
|
728
|
-
return element && element.content;
|
|
729
|
-
}
|
|
730
866
|
function responseSucceededWithoutRedirect(response) {
|
|
731
867
|
return response.statusCode == 200 && !response.redirected;
|
|
732
868
|
}
|
|
733
869
|
function mergeFormDataEntries(url, entries) {
|
|
734
|
-
const searchParams = new URLSearchParams;
|
|
870
|
+
const searchParams = new URLSearchParams();
|
|
735
871
|
for (const [name, value] of entries) {
|
|
736
872
|
if (value instanceof File)
|
|
737
873
|
continue;
|
|
@@ -745,6 +881,9 @@ class Snapshot {
|
|
|
745
881
|
constructor(element) {
|
|
746
882
|
this.element = element;
|
|
747
883
|
}
|
|
884
|
+
get activeElement() {
|
|
885
|
+
return this.element.ownerDocument.activeElement;
|
|
886
|
+
}
|
|
748
887
|
get children() {
|
|
749
888
|
return [...this.element.children];
|
|
750
889
|
}
|
|
@@ -758,13 +897,20 @@ class Snapshot {
|
|
|
758
897
|
return this.element.isConnected;
|
|
759
898
|
}
|
|
760
899
|
get firstAutofocusableElement() {
|
|
761
|
-
|
|
900
|
+
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
|
901
|
+
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
|
902
|
+
if (element.closest(inertDisabledOrHidden) == null)
|
|
903
|
+
return element;
|
|
904
|
+
else
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
return null;
|
|
762
908
|
}
|
|
763
909
|
get permanentElements() {
|
|
764
|
-
return
|
|
910
|
+
return queryPermanentElementsAll(this.element);
|
|
765
911
|
}
|
|
766
912
|
getPermanentElementById(id) {
|
|
767
|
-
return this.element
|
|
913
|
+
return getPermanentElementById(this.element, id);
|
|
768
914
|
}
|
|
769
915
|
getPermanentElementMapForSnapshot(snapshot) {
|
|
770
916
|
const permanentElementMap = {};
|
|
@@ -778,36 +924,72 @@ class Snapshot {
|
|
|
778
924
|
return permanentElementMap;
|
|
779
925
|
}
|
|
780
926
|
}
|
|
927
|
+
function getPermanentElementById(node, id) {
|
|
928
|
+
return node.querySelector(`#${id}[data-turbo-permanent]`);
|
|
929
|
+
}
|
|
930
|
+
function queryPermanentElementsAll(node) {
|
|
931
|
+
return node.querySelectorAll("[id][data-turbo-permanent]");
|
|
932
|
+
}
|
|
781
933
|
|
|
782
|
-
class
|
|
783
|
-
constructor(delegate,
|
|
934
|
+
class FormSubmitObserver {
|
|
935
|
+
constructor(delegate, eventTarget) {
|
|
936
|
+
this.started = false;
|
|
937
|
+
this.submitCaptured = () => {
|
|
938
|
+
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
|
939
|
+
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
|
940
|
+
};
|
|
784
941
|
this.submitBubbled = ((event) => {
|
|
785
|
-
|
|
786
|
-
|
|
942
|
+
if (!event.defaultPrevented) {
|
|
943
|
+
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
787
944
|
const submitter = event.submitter || undefined;
|
|
788
|
-
|
|
789
|
-
|
|
945
|
+
if (form &&
|
|
946
|
+
submissionDoesNotDismissDialog(form, submitter) &&
|
|
947
|
+
submissionDoesNotTargetIFrame(form, submitter) &&
|
|
948
|
+
this.delegate.willSubmitForm(form, submitter)) {
|
|
790
949
|
event.preventDefault();
|
|
791
950
|
event.stopImmediatePropagation();
|
|
792
|
-
this.delegate.
|
|
951
|
+
this.delegate.formSubmitted(form, submitter);
|
|
793
952
|
}
|
|
794
953
|
}
|
|
795
954
|
});
|
|
796
955
|
this.delegate = delegate;
|
|
797
|
-
this.
|
|
956
|
+
this.eventTarget = eventTarget;
|
|
798
957
|
}
|
|
799
958
|
start() {
|
|
800
|
-
|
|
959
|
+
if (!this.started) {
|
|
960
|
+
this.eventTarget.addEventListener("submit", this.submitCaptured, true);
|
|
961
|
+
this.started = true;
|
|
962
|
+
}
|
|
801
963
|
}
|
|
802
964
|
stop() {
|
|
803
|
-
|
|
965
|
+
if (this.started) {
|
|
966
|
+
this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
|
|
967
|
+
this.started = false;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function submissionDoesNotDismissDialog(form, submitter) {
|
|
972
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
|
973
|
+
return method != "dialog";
|
|
974
|
+
}
|
|
975
|
+
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
976
|
+
if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
|
|
977
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
|
978
|
+
for (const element of document.getElementsByName(target)) {
|
|
979
|
+
if (element instanceof HTMLIFrameElement)
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
return true;
|
|
804
986
|
}
|
|
805
987
|
}
|
|
806
988
|
|
|
807
989
|
class View {
|
|
808
990
|
constructor(delegate, element) {
|
|
809
|
-
this.resolveRenderPromise = (
|
|
810
|
-
this.resolveInterceptionPromise = (
|
|
991
|
+
this.resolveRenderPromise = (_value) => { };
|
|
992
|
+
this.resolveInterceptionPromise = (_value) => { };
|
|
811
993
|
this.delegate = delegate;
|
|
812
994
|
this.element = element;
|
|
813
995
|
}
|
|
@@ -852,15 +1034,17 @@ class View {
|
|
|
852
1034
|
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
|
|
853
1035
|
if (shouldRender) {
|
|
854
1036
|
try {
|
|
855
|
-
this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
|
|
1037
|
+
this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
|
|
856
1038
|
this.renderer = renderer;
|
|
857
|
-
this.prepareToRenderSnapshot(renderer);
|
|
858
|
-
const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
|
|
859
|
-
const
|
|
1039
|
+
await this.prepareToRenderSnapshot(renderer);
|
|
1040
|
+
const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
|
|
1041
|
+
const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
|
|
1042
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
|
860
1043
|
if (!immediateRender)
|
|
861
1044
|
await renderInterception;
|
|
862
1045
|
await this.renderSnapshot(renderer);
|
|
863
1046
|
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
|
1047
|
+
this.delegate.preloadOnLoadLinksForView(this.element);
|
|
864
1048
|
this.finishRenderingSnapshot(renderer);
|
|
865
1049
|
}
|
|
866
1050
|
finally {
|
|
@@ -870,15 +1054,15 @@ class View {
|
|
|
870
1054
|
}
|
|
871
1055
|
}
|
|
872
1056
|
else {
|
|
873
|
-
this.invalidate();
|
|
1057
|
+
this.invalidate(renderer.reloadReason);
|
|
874
1058
|
}
|
|
875
1059
|
}
|
|
876
|
-
invalidate() {
|
|
877
|
-
this.delegate.viewInvalidated();
|
|
1060
|
+
invalidate(reason) {
|
|
1061
|
+
this.delegate.viewInvalidated(reason);
|
|
878
1062
|
}
|
|
879
|
-
prepareToRenderSnapshot(renderer) {
|
|
1063
|
+
async prepareToRenderSnapshot(renderer) {
|
|
880
1064
|
this.markAsPreview(renderer.isPreview);
|
|
881
|
-
renderer.prepareToRender();
|
|
1065
|
+
await renderer.prepareToRender();
|
|
882
1066
|
}
|
|
883
1067
|
markAsPreview(isPreview) {
|
|
884
1068
|
if (isPreview) {
|
|
@@ -897,8 +1081,8 @@ class View {
|
|
|
897
1081
|
}
|
|
898
1082
|
|
|
899
1083
|
class FrameView extends View {
|
|
900
|
-
|
|
901
|
-
this.element.innerHTML = ""
|
|
1084
|
+
missing() {
|
|
1085
|
+
this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
|
|
902
1086
|
}
|
|
903
1087
|
get snapshot() {
|
|
904
1088
|
return new Snapshot(this.element);
|
|
@@ -917,17 +1101,17 @@ class LinkInterceptor {
|
|
|
917
1101
|
};
|
|
918
1102
|
this.linkClicked = ((event) => {
|
|
919
1103
|
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
|
920
|
-
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
|
|
1104
|
+
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
|
921
1105
|
this.clickEvent.preventDefault();
|
|
922
1106
|
event.preventDefault();
|
|
923
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
|
1107
|
+
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
|
924
1108
|
}
|
|
925
1109
|
}
|
|
926
1110
|
delete this.clickEvent;
|
|
927
1111
|
});
|
|
928
|
-
this.willVisit = () => {
|
|
1112
|
+
this.willVisit = ((_event) => {
|
|
929
1113
|
delete this.clickEvent;
|
|
930
|
-
};
|
|
1114
|
+
});
|
|
931
1115
|
this.delegate = delegate;
|
|
932
1116
|
this.element = element;
|
|
933
1117
|
}
|
|
@@ -942,28 +1126,137 @@ class LinkInterceptor {
|
|
|
942
1126
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
|
943
1127
|
}
|
|
944
1128
|
respondsToEventTarget(target) {
|
|
945
|
-
const element = target instanceof Element
|
|
946
|
-
? target
|
|
947
|
-
: target instanceof Node
|
|
948
|
-
? target.parentElement
|
|
949
|
-
: null;
|
|
1129
|
+
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
950
1130
|
return element && element.closest("turbo-frame, html") == this.element;
|
|
951
1131
|
}
|
|
952
1132
|
}
|
|
953
1133
|
|
|
954
|
-
class
|
|
955
|
-
constructor(
|
|
956
|
-
this.
|
|
1134
|
+
class LinkClickObserver {
|
|
1135
|
+
constructor(delegate, eventTarget) {
|
|
1136
|
+
this.started = false;
|
|
1137
|
+
this.clickCaptured = () => {
|
|
1138
|
+
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
|
1139
|
+
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
|
1140
|
+
};
|
|
1141
|
+
this.clickBubbled = (event) => {
|
|
1142
|
+
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
|
1143
|
+
const target = (event.composedPath && event.composedPath()[0]) || event.target;
|
|
1144
|
+
const link = this.findLinkFromClickTarget(target);
|
|
1145
|
+
if (link && doesNotTargetIFrame(link)) {
|
|
1146
|
+
const location = this.getLocationForLink(link);
|
|
1147
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
1148
|
+
event.preventDefault();
|
|
1149
|
+
this.delegate.followedLinkToLocation(link, location);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
this.delegate = delegate;
|
|
1155
|
+
this.eventTarget = eventTarget;
|
|
1156
|
+
}
|
|
1157
|
+
start() {
|
|
1158
|
+
if (!this.started) {
|
|
1159
|
+
this.eventTarget.addEventListener("click", this.clickCaptured, true);
|
|
1160
|
+
this.started = true;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
stop() {
|
|
1164
|
+
if (this.started) {
|
|
1165
|
+
this.eventTarget.removeEventListener("click", this.clickCaptured, true);
|
|
1166
|
+
this.started = false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
clickEventIsSignificant(event) {
|
|
1170
|
+
return !((event.target && event.target.isContentEditable) ||
|
|
1171
|
+
event.defaultPrevented ||
|
|
1172
|
+
event.which > 1 ||
|
|
1173
|
+
event.altKey ||
|
|
1174
|
+
event.ctrlKey ||
|
|
1175
|
+
event.metaKey ||
|
|
1176
|
+
event.shiftKey);
|
|
1177
|
+
}
|
|
1178
|
+
findLinkFromClickTarget(target) {
|
|
1179
|
+
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
|
1180
|
+
}
|
|
1181
|
+
getLocationForLink(link) {
|
|
1182
|
+
return expandURL(link.getAttribute("href") || "");
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
function doesNotTargetIFrame(anchor) {
|
|
1186
|
+
if (anchor.hasAttribute("target")) {
|
|
1187
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
|
1188
|
+
if (element instanceof HTMLIFrameElement)
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
return true;
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
return true;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
class FormLinkClickObserver {
|
|
1199
|
+
constructor(delegate, element) {
|
|
1200
|
+
this.delegate = delegate;
|
|
1201
|
+
this.linkInterceptor = new LinkClickObserver(this, element);
|
|
957
1202
|
}
|
|
958
|
-
|
|
959
|
-
|
|
1203
|
+
start() {
|
|
1204
|
+
this.linkInterceptor.start();
|
|
1205
|
+
}
|
|
1206
|
+
stop() {
|
|
1207
|
+
this.linkInterceptor.stop();
|
|
1208
|
+
}
|
|
1209
|
+
willFollowLinkToLocation(link, location, originalEvent) {
|
|
1210
|
+
return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
|
|
1211
|
+
link.hasAttribute("data-turbo-method"));
|
|
1212
|
+
}
|
|
1213
|
+
followedLinkToLocation(link, location) {
|
|
1214
|
+
const form = document.createElement("form");
|
|
1215
|
+
const type = "hidden";
|
|
1216
|
+
for (const [name, value] of location.searchParams) {
|
|
1217
|
+
form.append(Object.assign(document.createElement("input"), { type, name, value }));
|
|
1218
|
+
}
|
|
1219
|
+
const action = Object.assign(location, { search: "" });
|
|
1220
|
+
form.setAttribute("data-turbo", "true");
|
|
1221
|
+
form.setAttribute("action", action.href);
|
|
1222
|
+
form.setAttribute("hidden", "");
|
|
1223
|
+
const method = link.getAttribute("data-turbo-method");
|
|
1224
|
+
if (method)
|
|
1225
|
+
form.setAttribute("method", method);
|
|
1226
|
+
const turboFrame = link.getAttribute("data-turbo-frame");
|
|
1227
|
+
if (turboFrame)
|
|
1228
|
+
form.setAttribute("data-turbo-frame", turboFrame);
|
|
1229
|
+
const turboAction = getVisitAction(link);
|
|
1230
|
+
if (turboAction)
|
|
1231
|
+
form.setAttribute("data-turbo-action", turboAction);
|
|
1232
|
+
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
|
1233
|
+
if (turboConfirm)
|
|
1234
|
+
form.setAttribute("data-turbo-confirm", turboConfirm);
|
|
1235
|
+
const turboStream = link.hasAttribute("data-turbo-stream");
|
|
1236
|
+
if (turboStream)
|
|
1237
|
+
form.setAttribute("data-turbo-stream", "");
|
|
1238
|
+
this.delegate.submittedFormLinkToLocation(link, location, form);
|
|
1239
|
+
document.body.appendChild(form);
|
|
1240
|
+
form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
|
|
1241
|
+
requestAnimationFrame(() => form.requestSubmit());
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
class Bardo {
|
|
1246
|
+
static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
|
1247
|
+
const bardo = new this(delegate, permanentElementMap);
|
|
960
1248
|
bardo.enter();
|
|
961
|
-
callback();
|
|
1249
|
+
await callback();
|
|
962
1250
|
bardo.leave();
|
|
963
1251
|
}
|
|
1252
|
+
constructor(delegate, permanentElementMap) {
|
|
1253
|
+
this.delegate = delegate;
|
|
1254
|
+
this.permanentElementMap = permanentElementMap;
|
|
1255
|
+
}
|
|
964
1256
|
enter() {
|
|
965
1257
|
for (const id in this.permanentElementMap) {
|
|
966
|
-
const [, newPermanentElement] = this.permanentElementMap[id];
|
|
1258
|
+
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
|
1259
|
+
this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
|
|
967
1260
|
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
|
968
1261
|
}
|
|
969
1262
|
}
|
|
@@ -972,6 +1265,7 @@ class Bardo {
|
|
|
972
1265
|
const [currentPermanentElement] = this.permanentElementMap[id];
|
|
973
1266
|
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
|
974
1267
|
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
|
1268
|
+
this.delegate.leavingBardo(currentPermanentElement);
|
|
975
1269
|
}
|
|
976
1270
|
}
|
|
977
1271
|
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
|
@@ -987,7 +1281,7 @@ class Bardo {
|
|
|
987
1281
|
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
|
|
988
1282
|
}
|
|
989
1283
|
getPlaceholderById(id) {
|
|
990
|
-
return this.placeholders.find(element => element.content == id);
|
|
1284
|
+
return this.placeholders.find((element) => element.content == id);
|
|
991
1285
|
}
|
|
992
1286
|
get placeholders() {
|
|
993
1287
|
return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
|
|
@@ -1001,16 +1295,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
1001
1295
|
}
|
|
1002
1296
|
|
|
1003
1297
|
class Renderer {
|
|
1004
|
-
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
1298
|
+
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1299
|
+
this.activeElement = null;
|
|
1005
1300
|
this.currentSnapshot = currentSnapshot;
|
|
1006
1301
|
this.newSnapshot = newSnapshot;
|
|
1007
1302
|
this.isPreview = isPreview;
|
|
1008
1303
|
this.willRender = willRender;
|
|
1009
|
-
this.
|
|
1304
|
+
this.renderElement = renderElement;
|
|
1305
|
+
this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
|
|
1010
1306
|
}
|
|
1011
1307
|
get shouldRender() {
|
|
1012
1308
|
return true;
|
|
1013
1309
|
}
|
|
1310
|
+
get reloadReason() {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1014
1313
|
prepareToRender() {
|
|
1015
1314
|
return;
|
|
1016
1315
|
}
|
|
@@ -1020,23 +1319,8 @@ class Renderer {
|
|
|
1020
1319
|
delete this.resolvingFunctions;
|
|
1021
1320
|
}
|
|
1022
1321
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
return element;
|
|
1026
|
-
}
|
|
1027
|
-
else {
|
|
1028
|
-
const createdScriptElement = document.createElement("script");
|
|
1029
|
-
if (this.cspNonce) {
|
|
1030
|
-
createdScriptElement.nonce = this.cspNonce;
|
|
1031
|
-
}
|
|
1032
|
-
createdScriptElement.textContent = element.textContent;
|
|
1033
|
-
createdScriptElement.async = false;
|
|
1034
|
-
copyElementAttributes(createdScriptElement, element);
|
|
1035
|
-
return createdScriptElement;
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
preservingPermanentElements(callback) {
|
|
1039
|
-
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
|
1322
|
+
async preservingPermanentElements(callback) {
|
|
1323
|
+
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
|
1040
1324
|
}
|
|
1041
1325
|
focusFirstAutofocusableElement() {
|
|
1042
1326
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
@@ -1044,6 +1328,19 @@ class Renderer {
|
|
|
1044
1328
|
element.focus();
|
|
1045
1329
|
}
|
|
1046
1330
|
}
|
|
1331
|
+
enteringBardo(currentPermanentElement) {
|
|
1332
|
+
if (this.activeElement)
|
|
1333
|
+
return;
|
|
1334
|
+
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
|
1335
|
+
this.activeElement = this.currentSnapshot.activeElement;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
leavingBardo(currentPermanentElement) {
|
|
1339
|
+
if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
|
|
1340
|
+
this.activeElement.focus();
|
|
1341
|
+
this.activeElement = null;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1047
1344
|
get connectedSnapshot() {
|
|
1048
1345
|
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
|
1049
1346
|
}
|
|
@@ -1056,21 +1353,28 @@ class Renderer {
|
|
|
1056
1353
|
get permanentElementMap() {
|
|
1057
1354
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
|
1058
1355
|
}
|
|
1059
|
-
get cspNonce() {
|
|
1060
|
-
var _a;
|
|
1061
|
-
return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
function copyElementAttributes(destinationElement, sourceElement) {
|
|
1065
|
-
for (const { name, value } of [...sourceElement.attributes]) {
|
|
1066
|
-
destinationElement.setAttribute(name, value);
|
|
1067
|
-
}
|
|
1068
1356
|
}
|
|
1069
1357
|
function elementIsFocusable(element) {
|
|
1070
1358
|
return element && typeof element.focus == "function";
|
|
1071
1359
|
}
|
|
1072
1360
|
|
|
1073
1361
|
class FrameRenderer extends Renderer {
|
|
1362
|
+
static renderElement(currentElement, newElement) {
|
|
1363
|
+
var _a;
|
|
1364
|
+
const destinationRange = document.createRange();
|
|
1365
|
+
destinationRange.selectNodeContents(currentElement);
|
|
1366
|
+
destinationRange.deleteContents();
|
|
1367
|
+
const frameElement = newElement;
|
|
1368
|
+
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
1369
|
+
if (sourceRange) {
|
|
1370
|
+
sourceRange.selectNodeContents(frameElement);
|
|
1371
|
+
currentElement.appendChild(sourceRange.extractContents());
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1375
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1376
|
+
this.delegate = delegate;
|
|
1377
|
+
}
|
|
1074
1378
|
get shouldRender() {
|
|
1075
1379
|
return true;
|
|
1076
1380
|
}
|
|
@@ -1086,23 +1390,16 @@ class FrameRenderer extends Renderer {
|
|
|
1086
1390
|
this.activateScriptElements();
|
|
1087
1391
|
}
|
|
1088
1392
|
loadFrameElement() {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
destinationRange.selectNodeContents(this.currentElement);
|
|
1092
|
-
destinationRange.deleteContents();
|
|
1093
|
-
const frameElement = this.newElement;
|
|
1094
|
-
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
1095
|
-
if (sourceRange) {
|
|
1096
|
-
sourceRange.selectNodeContents(frameElement);
|
|
1097
|
-
this.currentElement.appendChild(sourceRange.extractContents());
|
|
1098
|
-
}
|
|
1393
|
+
this.delegate.willRenderFrame(this.currentElement, this.newElement);
|
|
1394
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
1099
1395
|
}
|
|
1100
1396
|
scrollFrameIntoView() {
|
|
1101
1397
|
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
|
1102
1398
|
const element = this.currentElement.firstElementChild;
|
|
1103
1399
|
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
|
1400
|
+
const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
|
|
1104
1401
|
if (element) {
|
|
1105
|
-
element.scrollIntoView({ block });
|
|
1402
|
+
element.scrollIntoView({ block, behavior });
|
|
1106
1403
|
return true;
|
|
1107
1404
|
}
|
|
1108
1405
|
}
|
|
@@ -1110,7 +1407,7 @@ class FrameRenderer extends Renderer {
|
|
|
1110
1407
|
}
|
|
1111
1408
|
activateScriptElements() {
|
|
1112
1409
|
for (const inertScriptElement of this.newScriptElements) {
|
|
1113
|
-
const activatedScriptElement =
|
|
1410
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
1114
1411
|
inertScriptElement.replaceWith(activatedScriptElement);
|
|
1115
1412
|
}
|
|
1116
1413
|
}
|
|
@@ -1126,20 +1423,16 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
|
1126
1423
|
return defaultValue;
|
|
1127
1424
|
}
|
|
1128
1425
|
}
|
|
1426
|
+
function readScrollBehavior(value, defaultValue) {
|
|
1427
|
+
if (value == "auto" || value == "smooth") {
|
|
1428
|
+
return value;
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
return defaultValue;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1129
1434
|
|
|
1130
1435
|
class ProgressBar {
|
|
1131
|
-
constructor() {
|
|
1132
|
-
this.hiding = false;
|
|
1133
|
-
this.value = 0;
|
|
1134
|
-
this.visible = false;
|
|
1135
|
-
this.trickle = () => {
|
|
1136
|
-
this.setValue(this.value + Math.random() / 100);
|
|
1137
|
-
};
|
|
1138
|
-
this.stylesheetElement = this.createStylesheetElement();
|
|
1139
|
-
this.progressElement = this.createProgressElement();
|
|
1140
|
-
this.installStylesheetElement();
|
|
1141
|
-
this.setValue(0);
|
|
1142
|
-
}
|
|
1143
1436
|
static get defaultCSS() {
|
|
1144
1437
|
return unindent `
|
|
1145
1438
|
.turbo-progress-bar {
|
|
@@ -1149,7 +1442,7 @@ class ProgressBar {
|
|
|
1149
1442
|
left: 0;
|
|
1150
1443
|
height: 3px;
|
|
1151
1444
|
background: #0076ff;
|
|
1152
|
-
z-index:
|
|
1445
|
+
z-index: 2147483647;
|
|
1153
1446
|
transition:
|
|
1154
1447
|
width ${ProgressBar.animationDuration}ms ease-out,
|
|
1155
1448
|
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
|
@@ -1157,6 +1450,18 @@ class ProgressBar {
|
|
|
1157
1450
|
}
|
|
1158
1451
|
`;
|
|
1159
1452
|
}
|
|
1453
|
+
constructor() {
|
|
1454
|
+
this.hiding = false;
|
|
1455
|
+
this.value = 0;
|
|
1456
|
+
this.visible = false;
|
|
1457
|
+
this.trickle = () => {
|
|
1458
|
+
this.setValue(this.value + Math.random() / 100);
|
|
1459
|
+
};
|
|
1460
|
+
this.stylesheetElement = this.createStylesheetElement();
|
|
1461
|
+
this.progressElement = this.createProgressElement();
|
|
1462
|
+
this.installStylesheetElement();
|
|
1463
|
+
this.setValue(0);
|
|
1464
|
+
}
|
|
1160
1465
|
show() {
|
|
1161
1466
|
if (!this.visible) {
|
|
1162
1467
|
this.visible = true;
|
|
@@ -1208,13 +1513,16 @@ class ProgressBar {
|
|
|
1208
1513
|
}
|
|
1209
1514
|
refresh() {
|
|
1210
1515
|
requestAnimationFrame(() => {
|
|
1211
|
-
this.progressElement.style.width = `${10 +
|
|
1516
|
+
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
|
1212
1517
|
});
|
|
1213
1518
|
}
|
|
1214
1519
|
createStylesheetElement() {
|
|
1215
1520
|
const element = document.createElement("style");
|
|
1216
1521
|
element.type = "text/css";
|
|
1217
1522
|
element.textContent = ProgressBar.defaultCSS;
|
|
1523
|
+
if (this.cspNonce) {
|
|
1524
|
+
element.nonce = this.cspNonce;
|
|
1525
|
+
}
|
|
1218
1526
|
return element;
|
|
1219
1527
|
}
|
|
1220
1528
|
createProgressElement() {
|
|
@@ -1222,6 +1530,9 @@ class ProgressBar {
|
|
|
1222
1530
|
element.className = "turbo-progress-bar";
|
|
1223
1531
|
return element;
|
|
1224
1532
|
}
|
|
1533
|
+
get cspNonce() {
|
|
1534
|
+
return getMetaContent("csp-nonce");
|
|
1535
|
+
}
|
|
1225
1536
|
}
|
|
1226
1537
|
ProgressBar.animationDuration = 300;
|
|
1227
1538
|
|
|
@@ -1238,14 +1549,14 @@ class HeadSnapshot extends Snapshot {
|
|
|
1238
1549
|
: {
|
|
1239
1550
|
type: elementType(element),
|
|
1240
1551
|
tracked: elementIsTracked(element),
|
|
1241
|
-
elements: []
|
|
1552
|
+
elements: [],
|
|
1242
1553
|
};
|
|
1243
1554
|
return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
|
|
1244
1555
|
}, {});
|
|
1245
1556
|
}
|
|
1246
1557
|
get trackedElementSignature() {
|
|
1247
1558
|
return Object.keys(this.detailsByOuterHTML)
|
|
1248
|
-
.filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
|
|
1559
|
+
.filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
|
|
1249
1560
|
.join("");
|
|
1250
1561
|
}
|
|
1251
1562
|
getScriptElementsNotInSnapshot(snapshot) {
|
|
@@ -1256,8 +1567,8 @@ class HeadSnapshot extends Snapshot {
|
|
|
1256
1567
|
}
|
|
1257
1568
|
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
|
1258
1569
|
return Object.keys(this.detailsByOuterHTML)
|
|
1259
|
-
.filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
|
|
1260
|
-
.map(outerHTML => this.detailsByOuterHTML[outerHTML])
|
|
1570
|
+
.filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
|
|
1571
|
+
.map((outerHTML) => this.detailsByOuterHTML[outerHTML])
|
|
1261
1572
|
.filter(({ type }) => type == matchedType)
|
|
1262
1573
|
.map(({ elements: [element] }) => element);
|
|
1263
1574
|
}
|
|
@@ -1277,13 +1588,11 @@ class HeadSnapshot extends Snapshot {
|
|
|
1277
1588
|
}
|
|
1278
1589
|
getMetaValue(name) {
|
|
1279
1590
|
const element = this.findMetaElementByName(name);
|
|
1280
|
-
return element
|
|
1281
|
-
? element.getAttribute("content")
|
|
1282
|
-
: null;
|
|
1591
|
+
return element ? element.getAttribute("content") : null;
|
|
1283
1592
|
}
|
|
1284
1593
|
findMetaElementByName(name) {
|
|
1285
1594
|
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
|
|
1286
|
-
const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
|
|
1595
|
+
const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
|
|
1287
1596
|
return elementIsMetaElementWithName(element, name) ? element : result;
|
|
1288
1597
|
}, undefined);
|
|
1289
1598
|
}
|
|
@@ -1300,19 +1609,19 @@ function elementIsTracked(element) {
|
|
|
1300
1609
|
return element.getAttribute("data-turbo-track") == "reload";
|
|
1301
1610
|
}
|
|
1302
1611
|
function elementIsScript(element) {
|
|
1303
|
-
const tagName = element.
|
|
1612
|
+
const tagName = element.localName;
|
|
1304
1613
|
return tagName == "script";
|
|
1305
1614
|
}
|
|
1306
1615
|
function elementIsNoscript(element) {
|
|
1307
|
-
const tagName = element.
|
|
1616
|
+
const tagName = element.localName;
|
|
1308
1617
|
return tagName == "noscript";
|
|
1309
1618
|
}
|
|
1310
1619
|
function elementIsStylesheet(element) {
|
|
1311
|
-
const tagName = element.
|
|
1620
|
+
const tagName = element.localName;
|
|
1312
1621
|
return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
|
|
1313
1622
|
}
|
|
1314
1623
|
function elementIsMetaElementWithName(element, name) {
|
|
1315
|
-
const tagName = element.
|
|
1624
|
+
const tagName = element.localName;
|
|
1316
1625
|
return tagName == "meta" && element.getAttribute("name") == name;
|
|
1317
1626
|
}
|
|
1318
1627
|
function elementWithoutNonce(element) {
|
|
@@ -1323,10 +1632,6 @@ function elementWithoutNonce(element) {
|
|
|
1323
1632
|
}
|
|
1324
1633
|
|
|
1325
1634
|
class PageSnapshot extends Snapshot {
|
|
1326
|
-
constructor(element, headSnapshot) {
|
|
1327
|
-
super(element);
|
|
1328
|
-
this.headSnapshot = headSnapshot;
|
|
1329
|
-
}
|
|
1330
1635
|
static fromHTMLString(html = "") {
|
|
1331
1636
|
return this.fromDocument(parseHTMLDocument(html));
|
|
1332
1637
|
}
|
|
@@ -1336,8 +1641,25 @@ class PageSnapshot extends Snapshot {
|
|
|
1336
1641
|
static fromDocument({ head, body }) {
|
|
1337
1642
|
return new this(body, new HeadSnapshot(head));
|
|
1338
1643
|
}
|
|
1644
|
+
constructor(element, headSnapshot) {
|
|
1645
|
+
super(element);
|
|
1646
|
+
this.headSnapshot = headSnapshot;
|
|
1647
|
+
}
|
|
1339
1648
|
clone() {
|
|
1340
|
-
|
|
1649
|
+
const clonedElement = this.element.cloneNode(true);
|
|
1650
|
+
const selectElements = this.element.querySelectorAll("select");
|
|
1651
|
+
const clonedSelectElements = clonedElement.querySelectorAll("select");
|
|
1652
|
+
for (const [index, source] of selectElements.entries()) {
|
|
1653
|
+
const clone = clonedSelectElements[index];
|
|
1654
|
+
for (const option of clone.selectedOptions)
|
|
1655
|
+
option.selected = false;
|
|
1656
|
+
for (const option of source.selectedOptions)
|
|
1657
|
+
clone.options[option.index].selected = true;
|
|
1658
|
+
}
|
|
1659
|
+
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
|
1660
|
+
clonedPasswordInput.value = "";
|
|
1661
|
+
}
|
|
1662
|
+
return new PageSnapshot(clonedElement, this.headSnapshot);
|
|
1341
1663
|
}
|
|
1342
1664
|
get headElement() {
|
|
1343
1665
|
return this.headSnapshot.element;
|
|
@@ -1384,6 +1706,9 @@ const defaultOptions = {
|
|
|
1384
1706
|
historyChanged: false,
|
|
1385
1707
|
visitCachedSnapshot: () => { },
|
|
1386
1708
|
willRender: true,
|
|
1709
|
+
updateHistory: true,
|
|
1710
|
+
shouldCacheSnapshot: true,
|
|
1711
|
+
acceptsStreamResponse: false,
|
|
1387
1712
|
};
|
|
1388
1713
|
var SystemStatusCode;
|
|
1389
1714
|
(function (SystemStatusCode) {
|
|
@@ -1398,21 +1723,27 @@ class Visit {
|
|
|
1398
1723
|
this.followedRedirect = false;
|
|
1399
1724
|
this.historyChanged = false;
|
|
1400
1725
|
this.scrolled = false;
|
|
1726
|
+
this.shouldCacheSnapshot = true;
|
|
1727
|
+
this.acceptsStreamResponse = false;
|
|
1401
1728
|
this.snapshotCached = false;
|
|
1402
1729
|
this.state = VisitState.initialized;
|
|
1403
1730
|
this.delegate = delegate;
|
|
1404
1731
|
this.location = location;
|
|
1405
1732
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1406
|
-
const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1733
|
+
const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1407
1734
|
this.action = action;
|
|
1408
1735
|
this.historyChanged = historyChanged;
|
|
1409
1736
|
this.referrer = referrer;
|
|
1737
|
+
this.snapshot = snapshot;
|
|
1410
1738
|
this.snapshotHTML = snapshotHTML;
|
|
1411
1739
|
this.response = response;
|
|
1412
1740
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1413
1741
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
1414
1742
|
this.willRender = willRender;
|
|
1743
|
+
this.updateHistory = updateHistory;
|
|
1415
1744
|
this.scrolled = !willRender;
|
|
1745
|
+
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
1746
|
+
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
1416
1747
|
}
|
|
1417
1748
|
get adapter() {
|
|
1418
1749
|
return this.delegate.adapter;
|
|
@@ -1450,9 +1781,11 @@ class Visit {
|
|
|
1450
1781
|
if (this.state == VisitState.started) {
|
|
1451
1782
|
this.recordTimingMetric(TimingMetric.visitEnd);
|
|
1452
1783
|
this.state = VisitState.completed;
|
|
1453
|
-
this.adapter.visitCompleted(this);
|
|
1454
|
-
this.delegate.visitCompleted(this);
|
|
1455
1784
|
this.followRedirect();
|
|
1785
|
+
if (!this.followedRedirect) {
|
|
1786
|
+
this.adapter.visitCompleted(this);
|
|
1787
|
+
this.delegate.visitCompleted(this);
|
|
1788
|
+
}
|
|
1456
1789
|
}
|
|
1457
1790
|
}
|
|
1458
1791
|
fail() {
|
|
@@ -1463,9 +1796,9 @@ class Visit {
|
|
|
1463
1796
|
}
|
|
1464
1797
|
changeHistory() {
|
|
1465
1798
|
var _a;
|
|
1466
|
-
if (!this.historyChanged) {
|
|
1799
|
+
if (!this.historyChanged && this.updateHistory) {
|
|
1467
1800
|
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
|
1468
|
-
const method =
|
|
1801
|
+
const method = getHistoryMethodForAction(actionForHistory);
|
|
1469
1802
|
this.history.update(method, this.location, this.restorationIdentifier);
|
|
1470
1803
|
this.historyChanged = true;
|
|
1471
1804
|
}
|
|
@@ -1510,16 +1843,18 @@ class Visit {
|
|
|
1510
1843
|
if (this.response) {
|
|
1511
1844
|
const { statusCode, responseHTML } = this.response;
|
|
1512
1845
|
this.render(async () => {
|
|
1513
|
-
this.
|
|
1846
|
+
if (this.shouldCacheSnapshot)
|
|
1847
|
+
this.cacheSnapshot();
|
|
1514
1848
|
if (this.view.renderPromise)
|
|
1515
1849
|
await this.view.renderPromise;
|
|
1516
1850
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1517
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
|
1851
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
|
|
1852
|
+
this.performScroll();
|
|
1518
1853
|
this.adapter.visitRendered(this);
|
|
1519
1854
|
this.complete();
|
|
1520
1855
|
}
|
|
1521
1856
|
else {
|
|
1522
|
-
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
|
1857
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
|
1523
1858
|
this.adapter.visitRendered(this);
|
|
1524
1859
|
this.fail();
|
|
1525
1860
|
}
|
|
@@ -1554,7 +1889,8 @@ class Visit {
|
|
|
1554
1889
|
else {
|
|
1555
1890
|
if (this.view.renderPromise)
|
|
1556
1891
|
await this.view.renderPromise;
|
|
1557
|
-
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
|
1892
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
|
1893
|
+
this.performScroll();
|
|
1558
1894
|
this.adapter.visitRendered(this);
|
|
1559
1895
|
if (!isPreview) {
|
|
1560
1896
|
this.complete();
|
|
@@ -1567,8 +1903,10 @@ class Visit {
|
|
|
1567
1903
|
var _a;
|
|
1568
1904
|
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
|
1569
1905
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1570
|
-
action:
|
|
1571
|
-
response: this.response
|
|
1906
|
+
action: "replace",
|
|
1907
|
+
response: this.response,
|
|
1908
|
+
shouldCacheSnapshot: false,
|
|
1909
|
+
willRender: false,
|
|
1572
1910
|
});
|
|
1573
1911
|
this.followedRedirect = true;
|
|
1574
1912
|
}
|
|
@@ -1577,20 +1915,29 @@ class Visit {
|
|
|
1577
1915
|
if (this.isSamePage) {
|
|
1578
1916
|
this.render(async () => {
|
|
1579
1917
|
this.cacheSnapshot();
|
|
1918
|
+
this.performScroll();
|
|
1919
|
+
this.changeHistory();
|
|
1580
1920
|
this.adapter.visitRendered(this);
|
|
1581
1921
|
});
|
|
1582
1922
|
}
|
|
1583
1923
|
}
|
|
1924
|
+
prepareRequest(request) {
|
|
1925
|
+
if (this.acceptsStreamResponse) {
|
|
1926
|
+
request.acceptResponseType(StreamMessage.contentType);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1584
1929
|
requestStarted() {
|
|
1585
1930
|
this.startRequest();
|
|
1586
1931
|
}
|
|
1587
|
-
requestPreventedHandlingResponse(
|
|
1588
|
-
}
|
|
1932
|
+
requestPreventedHandlingResponse(_request, _response) { }
|
|
1589
1933
|
async requestSucceededWithResponse(request, response) {
|
|
1590
1934
|
const responseHTML = await response.responseHTML;
|
|
1591
1935
|
const { redirected, statusCode } = response;
|
|
1592
1936
|
if (responseHTML == undefined) {
|
|
1593
|
-
this.recordResponse({
|
|
1937
|
+
this.recordResponse({
|
|
1938
|
+
statusCode: SystemStatusCode.contentTypeMismatch,
|
|
1939
|
+
redirected,
|
|
1940
|
+
});
|
|
1594
1941
|
}
|
|
1595
1942
|
else {
|
|
1596
1943
|
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
|
@@ -1601,20 +1948,26 @@ class Visit {
|
|
|
1601
1948
|
const responseHTML = await response.responseHTML;
|
|
1602
1949
|
const { redirected, statusCode } = response;
|
|
1603
1950
|
if (responseHTML == undefined) {
|
|
1604
|
-
this.recordResponse({
|
|
1951
|
+
this.recordResponse({
|
|
1952
|
+
statusCode: SystemStatusCode.contentTypeMismatch,
|
|
1953
|
+
redirected,
|
|
1954
|
+
});
|
|
1605
1955
|
}
|
|
1606
1956
|
else {
|
|
1607
1957
|
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1608
1958
|
}
|
|
1609
1959
|
}
|
|
1610
|
-
requestErrored(
|
|
1611
|
-
this.recordResponse({
|
|
1960
|
+
requestErrored(_request, _error) {
|
|
1961
|
+
this.recordResponse({
|
|
1962
|
+
statusCode: SystemStatusCode.networkFailure,
|
|
1963
|
+
redirected: false,
|
|
1964
|
+
});
|
|
1612
1965
|
}
|
|
1613
1966
|
requestFinished() {
|
|
1614
1967
|
this.finishRequest();
|
|
1615
1968
|
}
|
|
1616
1969
|
performScroll() {
|
|
1617
|
-
if (!this.scrolled) {
|
|
1970
|
+
if (!this.scrolled && !this.view.forceReloaded) {
|
|
1618
1971
|
if (this.action == "restore") {
|
|
1619
1972
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
|
1620
1973
|
}
|
|
@@ -1649,9 +2002,11 @@ class Visit {
|
|
|
1649
2002
|
}
|
|
1650
2003
|
getHistoryMethodForAction(action) {
|
|
1651
2004
|
switch (action) {
|
|
1652
|
-
case "replace":
|
|
2005
|
+
case "replace":
|
|
2006
|
+
return history.replaceState;
|
|
1653
2007
|
case "advance":
|
|
1654
|
-
case "restore":
|
|
2008
|
+
case "restore":
|
|
2009
|
+
return history.pushState;
|
|
1655
2010
|
}
|
|
1656
2011
|
}
|
|
1657
2012
|
hasPreloadedResponse() {
|
|
@@ -1670,18 +2025,17 @@ class Visit {
|
|
|
1670
2025
|
}
|
|
1671
2026
|
cacheSnapshot() {
|
|
1672
2027
|
if (!this.snapshotCached) {
|
|
1673
|
-
this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
|
|
2028
|
+
this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
|
|
1674
2029
|
this.snapshotCached = true;
|
|
1675
2030
|
}
|
|
1676
2031
|
}
|
|
1677
2032
|
async render(callback) {
|
|
1678
2033
|
this.cancelRender();
|
|
1679
|
-
await new Promise(resolve => {
|
|
2034
|
+
await new Promise((resolve) => {
|
|
1680
2035
|
this.frame = requestAnimationFrame(() => resolve());
|
|
1681
2036
|
});
|
|
1682
2037
|
await callback();
|
|
1683
2038
|
delete this.frame;
|
|
1684
|
-
this.performScroll();
|
|
1685
2039
|
}
|
|
1686
2040
|
cancelRender() {
|
|
1687
2041
|
if (this.frame) {
|
|
@@ -1696,19 +2050,19 @@ function isSuccessful(statusCode) {
|
|
|
1696
2050
|
|
|
1697
2051
|
class BrowserAdapter {
|
|
1698
2052
|
constructor(session) {
|
|
1699
|
-
this.progressBar = new ProgressBar;
|
|
2053
|
+
this.progressBar = new ProgressBar();
|
|
1700
2054
|
this.showProgressBar = () => {
|
|
1701
2055
|
this.progressBar.show();
|
|
1702
2056
|
};
|
|
1703
2057
|
this.session = session;
|
|
1704
2058
|
}
|
|
1705
2059
|
visitProposedToLocation(location, options) {
|
|
1706
|
-
this.navigator.startVisit(location, uuid(), options);
|
|
2060
|
+
this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
|
|
1707
2061
|
}
|
|
1708
2062
|
visitStarted(visit) {
|
|
2063
|
+
this.location = visit.location;
|
|
1709
2064
|
visit.loadCachedSnapshot();
|
|
1710
2065
|
visit.issueRequest();
|
|
1711
|
-
visit.changeHistory();
|
|
1712
2066
|
visit.goToSamePageAnchor();
|
|
1713
2067
|
}
|
|
1714
2068
|
visitRequestStarted(visit) {
|
|
@@ -1728,29 +2082,31 @@ class BrowserAdapter {
|
|
|
1728
2082
|
case SystemStatusCode.networkFailure:
|
|
1729
2083
|
case SystemStatusCode.timeoutFailure:
|
|
1730
2084
|
case SystemStatusCode.contentTypeMismatch:
|
|
1731
|
-
return this.reload(
|
|
2085
|
+
return this.reload({
|
|
2086
|
+
reason: "request_failed",
|
|
2087
|
+
context: {
|
|
2088
|
+
statusCode,
|
|
2089
|
+
},
|
|
2090
|
+
});
|
|
1732
2091
|
default:
|
|
1733
2092
|
return visit.loadResponse();
|
|
1734
2093
|
}
|
|
1735
2094
|
}
|
|
1736
|
-
visitRequestFinished(
|
|
2095
|
+
visitRequestFinished(_visit) {
|
|
1737
2096
|
this.progressBar.setValue(1);
|
|
1738
2097
|
this.hideVisitProgressBar();
|
|
1739
2098
|
}
|
|
1740
|
-
visitCompleted(
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
this.reload();
|
|
2099
|
+
visitCompleted(_visit) { }
|
|
2100
|
+
pageInvalidated(reason) {
|
|
2101
|
+
this.reload(reason);
|
|
1744
2102
|
}
|
|
1745
|
-
visitFailed(
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
}
|
|
1749
|
-
formSubmissionStarted(formSubmission) {
|
|
2103
|
+
visitFailed(_visit) { }
|
|
2104
|
+
visitRendered(_visit) { }
|
|
2105
|
+
formSubmissionStarted(_formSubmission) {
|
|
1750
2106
|
this.progressBar.setValue(0);
|
|
1751
2107
|
this.showFormProgressBarAfterDelay();
|
|
1752
2108
|
}
|
|
1753
|
-
formSubmissionFinished(
|
|
2109
|
+
formSubmissionFinished(_formSubmission) {
|
|
1754
2110
|
this.progressBar.setValue(1);
|
|
1755
2111
|
this.hideFormProgressBar();
|
|
1756
2112
|
}
|
|
@@ -1776,8 +2132,10 @@ class BrowserAdapter {
|
|
|
1776
2132
|
delete this.formProgressBarTimeout;
|
|
1777
2133
|
}
|
|
1778
2134
|
}
|
|
1779
|
-
reload() {
|
|
1780
|
-
|
|
2135
|
+
reload(reason) {
|
|
2136
|
+
var _a;
|
|
2137
|
+
dispatch("turbo:reload", { detail: reason });
|
|
2138
|
+
window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
|
|
1781
2139
|
}
|
|
1782
2140
|
get navigator() {
|
|
1783
2141
|
return this.session.navigator;
|
|
@@ -1786,95 +2144,72 @@ class BrowserAdapter {
|
|
|
1786
2144
|
|
|
1787
2145
|
class CacheObserver {
|
|
1788
2146
|
constructor() {
|
|
2147
|
+
this.selector = "[data-turbo-temporary]";
|
|
2148
|
+
this.deprecatedSelector = "[data-turbo-cache=false]";
|
|
1789
2149
|
this.started = false;
|
|
2150
|
+
this.removeTemporaryElements = ((_event) => {
|
|
2151
|
+
for (const element of this.temporaryElements) {
|
|
2152
|
+
element.remove();
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
1790
2155
|
}
|
|
1791
2156
|
start() {
|
|
1792
2157
|
if (!this.started) {
|
|
1793
2158
|
this.started = true;
|
|
1794
|
-
addEventListener("turbo:before-cache", this.
|
|
2159
|
+
addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
1795
2160
|
}
|
|
1796
2161
|
}
|
|
1797
2162
|
stop() {
|
|
1798
2163
|
if (this.started) {
|
|
1799
2164
|
this.started = false;
|
|
1800
|
-
removeEventListener("turbo:before-cache", this.
|
|
2165
|
+
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
1801
2166
|
}
|
|
1802
2167
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
for (const element of staleElements) {
|
|
1806
|
-
element.remove();
|
|
1807
|
-
}
|
|
2168
|
+
get temporaryElements() {
|
|
2169
|
+
return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];
|
|
1808
2170
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
this.started = false;
|
|
1814
|
-
this.submitCaptured = () => {
|
|
1815
|
-
removeEventListener("submit", this.submitBubbled, false);
|
|
1816
|
-
addEventListener("submit", this.submitBubbled, false);
|
|
1817
|
-
};
|
|
1818
|
-
this.submitBubbled = ((event) => {
|
|
1819
|
-
if (!event.defaultPrevented) {
|
|
1820
|
-
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
1821
|
-
const submitter = event.submitter || undefined;
|
|
1822
|
-
if (form) {
|
|
1823
|
-
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
|
1824
|
-
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
|
1825
|
-
event.preventDefault();
|
|
1826
|
-
this.delegate.formSubmitted(form, submitter);
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
this.delegate = delegate;
|
|
1832
|
-
}
|
|
1833
|
-
start() {
|
|
1834
|
-
if (!this.started) {
|
|
1835
|
-
addEventListener("submit", this.submitCaptured, true);
|
|
1836
|
-
this.started = true;
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
stop() {
|
|
1840
|
-
if (this.started) {
|
|
1841
|
-
removeEventListener("submit", this.submitCaptured, true);
|
|
1842
|
-
this.started = false;
|
|
2171
|
+
get temporaryElementsWithDeprecation() {
|
|
2172
|
+
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
2173
|
+
if (elements.length) {
|
|
2174
|
+
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
|
1843
2175
|
}
|
|
2176
|
+
return [...elements];
|
|
1844
2177
|
}
|
|
1845
2178
|
}
|
|
1846
2179
|
|
|
1847
2180
|
class FrameRedirector {
|
|
1848
|
-
constructor(element) {
|
|
2181
|
+
constructor(session, element) {
|
|
2182
|
+
this.session = session;
|
|
1849
2183
|
this.element = element;
|
|
1850
2184
|
this.linkInterceptor = new LinkInterceptor(this, element);
|
|
1851
|
-
this.
|
|
2185
|
+
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
|
1852
2186
|
}
|
|
1853
2187
|
start() {
|
|
1854
2188
|
this.linkInterceptor.start();
|
|
1855
|
-
this.
|
|
2189
|
+
this.formSubmitObserver.start();
|
|
1856
2190
|
}
|
|
1857
2191
|
stop() {
|
|
1858
2192
|
this.linkInterceptor.stop();
|
|
1859
|
-
this.
|
|
2193
|
+
this.formSubmitObserver.stop();
|
|
1860
2194
|
}
|
|
1861
|
-
shouldInterceptLinkClick(element,
|
|
2195
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
|
1862
2196
|
return this.shouldRedirect(element);
|
|
1863
2197
|
}
|
|
1864
|
-
linkClickIntercepted(element, url) {
|
|
2198
|
+
linkClickIntercepted(element, url, event) {
|
|
1865
2199
|
const frame = this.findFrameElement(element);
|
|
1866
2200
|
if (frame) {
|
|
1867
|
-
frame.delegate.linkClickIntercepted(element, url);
|
|
2201
|
+
frame.delegate.linkClickIntercepted(element, url, event);
|
|
1868
2202
|
}
|
|
1869
2203
|
}
|
|
1870
|
-
|
|
1871
|
-
return
|
|
2204
|
+
willSubmitForm(element, submitter) {
|
|
2205
|
+
return (element.closest("turbo-frame") == null &&
|
|
2206
|
+
this.shouldSubmit(element, submitter) &&
|
|
2207
|
+
this.shouldRedirect(element, submitter));
|
|
1872
2208
|
}
|
|
1873
|
-
|
|
2209
|
+
formSubmitted(element, submitter) {
|
|
1874
2210
|
const frame = this.findFrameElement(element, submitter);
|
|
1875
2211
|
if (frame) {
|
|
1876
|
-
frame.
|
|
1877
|
-
frame.delegate.formSubmissionIntercepted(element, submitter);
|
|
2212
|
+
frame.delegate.formSubmitted(element, submitter);
|
|
1878
2213
|
}
|
|
1879
2214
|
}
|
|
1880
2215
|
shouldSubmit(form, submitter) {
|
|
@@ -1885,8 +2220,16 @@ class FrameRedirector {
|
|
|
1885
2220
|
return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
|
1886
2221
|
}
|
|
1887
2222
|
shouldRedirect(element, submitter) {
|
|
1888
|
-
const
|
|
1889
|
-
|
|
2223
|
+
const isNavigatable = element instanceof HTMLFormElement
|
|
2224
|
+
? this.session.submissionIsNavigatable(element, submitter)
|
|
2225
|
+
: this.session.elementIsNavigatable(element);
|
|
2226
|
+
if (isNavigatable) {
|
|
2227
|
+
const frame = this.findFrameElement(element, submitter);
|
|
2228
|
+
return frame ? frame != element.closest("turbo-frame") : false;
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
1890
2233
|
}
|
|
1891
2234
|
findFrameElement(element, submitter) {
|
|
1892
2235
|
const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
|
|
@@ -1916,7 +2259,7 @@ class History {
|
|
|
1916
2259
|
}
|
|
1917
2260
|
}
|
|
1918
2261
|
};
|
|
1919
|
-
this.onPageLoad = async (
|
|
2262
|
+
this.onPageLoad = async (_event) => {
|
|
1920
2263
|
await nextMicrotask();
|
|
1921
2264
|
this.pageLoaded = true;
|
|
1922
2265
|
};
|
|
@@ -1978,63 +2321,6 @@ class History {
|
|
|
1978
2321
|
}
|
|
1979
2322
|
}
|
|
1980
2323
|
|
|
1981
|
-
class LinkClickObserver {
|
|
1982
|
-
constructor(delegate) {
|
|
1983
|
-
this.started = false;
|
|
1984
|
-
this.clickCaptured = () => {
|
|
1985
|
-
removeEventListener("click", this.clickBubbled, false);
|
|
1986
|
-
addEventListener("click", this.clickBubbled, false);
|
|
1987
|
-
};
|
|
1988
|
-
this.clickBubbled = (event) => {
|
|
1989
|
-
if (this.clickEventIsSignificant(event)) {
|
|
1990
|
-
const target = (event.composedPath && event.composedPath()[0]) || event.target;
|
|
1991
|
-
const link = this.findLinkFromClickTarget(target);
|
|
1992
|
-
if (link) {
|
|
1993
|
-
const location = this.getLocationForLink(link);
|
|
1994
|
-
if (this.delegate.willFollowLinkToLocation(link, location)) {
|
|
1995
|
-
event.preventDefault();
|
|
1996
|
-
this.delegate.followedLinkToLocation(link, location);
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
};
|
|
2001
|
-
this.delegate = delegate;
|
|
2002
|
-
}
|
|
2003
|
-
start() {
|
|
2004
|
-
if (!this.started) {
|
|
2005
|
-
addEventListener("click", this.clickCaptured, true);
|
|
2006
|
-
this.started = true;
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
stop() {
|
|
2010
|
-
if (this.started) {
|
|
2011
|
-
removeEventListener("click", this.clickCaptured, true);
|
|
2012
|
-
this.started = false;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
clickEventIsSignificant(event) {
|
|
2016
|
-
return !((event.target && event.target.isContentEditable)
|
|
2017
|
-
|| event.defaultPrevented
|
|
2018
|
-
|| event.which > 1
|
|
2019
|
-
|| event.altKey
|
|
2020
|
-
|| event.ctrlKey
|
|
2021
|
-
|| event.metaKey
|
|
2022
|
-
|| event.shiftKey);
|
|
2023
|
-
}
|
|
2024
|
-
findLinkFromClickTarget(target) {
|
|
2025
|
-
if (target instanceof Element) {
|
|
2026
|
-
return target.closest("a[href]:not([target^=_]):not([download])");
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
getLocationForLink(link) {
|
|
2030
|
-
return expandURL(link.getAttribute("href") || "");
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
function isAction(action) {
|
|
2035
|
-
return action == "advance" || action == "replace" || action == "restore";
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
2324
|
class Navigator {
|
|
2039
2325
|
constructor(delegate) {
|
|
2040
2326
|
this.delegate = delegate;
|
|
@@ -2079,7 +2365,7 @@ class Navigator {
|
|
|
2079
2365
|
return this.delegate.history;
|
|
2080
2366
|
}
|
|
2081
2367
|
formSubmissionStarted(formSubmission) {
|
|
2082
|
-
if (typeof this.adapter.formSubmissionStarted ===
|
|
2368
|
+
if (typeof this.adapter.formSubmissionStarted === "function") {
|
|
2083
2369
|
this.adapter.formSubmissionStarted(formSubmission);
|
|
2084
2370
|
}
|
|
2085
2371
|
}
|
|
@@ -2087,12 +2373,17 @@ class Navigator {
|
|
|
2087
2373
|
if (formSubmission == this.formSubmission) {
|
|
2088
2374
|
const responseHTML = await fetchResponse.responseHTML;
|
|
2089
2375
|
if (responseHTML) {
|
|
2090
|
-
|
|
2376
|
+
const shouldCacheSnapshot = formSubmission.isSafe;
|
|
2377
|
+
if (!shouldCacheSnapshot) {
|
|
2091
2378
|
this.view.clearSnapshotCache();
|
|
2092
2379
|
}
|
|
2093
2380
|
const { statusCode, redirected } = fetchResponse;
|
|
2094
2381
|
const action = this.getActionForFormSubmission(formSubmission);
|
|
2095
|
-
const visitOptions = {
|
|
2382
|
+
const visitOptions = {
|
|
2383
|
+
action,
|
|
2384
|
+
shouldCacheSnapshot,
|
|
2385
|
+
response: { statusCode, responseHTML, redirected },
|
|
2386
|
+
};
|
|
2096
2387
|
this.proposeVisit(fetchResponse.location, visitOptions);
|
|
2097
2388
|
}
|
|
2098
2389
|
}
|
|
@@ -2102,10 +2393,10 @@ class Navigator {
|
|
|
2102
2393
|
if (responseHTML) {
|
|
2103
2394
|
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
2104
2395
|
if (fetchResponse.serverError) {
|
|
2105
|
-
await this.view.renderError(snapshot);
|
|
2396
|
+
await this.view.renderError(snapshot, this.currentVisit);
|
|
2106
2397
|
}
|
|
2107
2398
|
else {
|
|
2108
|
-
await this.view.renderPage(snapshot);
|
|
2399
|
+
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
2109
2400
|
}
|
|
2110
2401
|
this.view.scrollToTop();
|
|
2111
2402
|
this.view.clearSnapshotCache();
|
|
@@ -2115,7 +2406,7 @@ class Navigator {
|
|
|
2115
2406
|
console.error(error);
|
|
2116
2407
|
}
|
|
2117
2408
|
formSubmissionFinished(formSubmission) {
|
|
2118
|
-
if (typeof this.adapter.formSubmissionFinished ===
|
|
2409
|
+
if (typeof this.adapter.formSubmissionFinished === "function") {
|
|
2119
2410
|
this.adapter.formSubmissionFinished(formSubmission);
|
|
2120
2411
|
}
|
|
2121
2412
|
}
|
|
@@ -2128,10 +2419,10 @@ class Navigator {
|
|
|
2128
2419
|
locationWithActionIsSamePage(location, action) {
|
|
2129
2420
|
const anchor = getAnchor(location);
|
|
2130
2421
|
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
2131
|
-
const isRestorationToTop = action ===
|
|
2132
|
-
return action !== "replace" &&
|
|
2422
|
+
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
2423
|
+
return (action !== "replace" &&
|
|
2133
2424
|
getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
|
|
2134
|
-
(isRestorationToTop || (anchor != null && anchor !== currentAnchor));
|
|
2425
|
+
(isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
|
|
2135
2426
|
}
|
|
2136
2427
|
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
2137
2428
|
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
@@ -2142,10 +2433,8 @@ class Navigator {
|
|
|
2142
2433
|
get restorationIdentifier() {
|
|
2143
2434
|
return this.history.restorationIdentifier;
|
|
2144
2435
|
}
|
|
2145
|
-
getActionForFormSubmission(
|
|
2146
|
-
|
|
2147
|
-
const action = getAttribute("data-turbo-action", submitter, formElement);
|
|
2148
|
-
return isAction(action) ? action : "advance";
|
|
2436
|
+
getActionForFormSubmission({ submitter, formElement }) {
|
|
2437
|
+
return getVisitAction(submitter, formElement) || "advance";
|
|
2149
2438
|
}
|
|
2150
2439
|
}
|
|
2151
2440
|
|
|
@@ -2235,9 +2524,33 @@ class ScrollObserver {
|
|
|
2235
2524
|
}
|
|
2236
2525
|
}
|
|
2237
2526
|
|
|
2527
|
+
class StreamMessageRenderer {
|
|
2528
|
+
render({ fragment }) {
|
|
2529
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
|
|
2530
|
+
}
|
|
2531
|
+
enteringBardo(currentPermanentElement, newPermanentElement) {
|
|
2532
|
+
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
|
2533
|
+
}
|
|
2534
|
+
leavingBardo() { }
|
|
2535
|
+
}
|
|
2536
|
+
function getPermanentElementMapForFragment(fragment) {
|
|
2537
|
+
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
|
2538
|
+
const permanentElementMap = {};
|
|
2539
|
+
for (const permanentElementInDocument of permanentElementsInDocument) {
|
|
2540
|
+
const { id } = permanentElementInDocument;
|
|
2541
|
+
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
|
2542
|
+
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
|
2543
|
+
if (elementInStream) {
|
|
2544
|
+
permanentElementMap[id] = [permanentElementInDocument, elementInStream];
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
return permanentElementMap;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2238
2551
|
class StreamObserver {
|
|
2239
2552
|
constructor(delegate) {
|
|
2240
|
-
this.sources = new Set;
|
|
2553
|
+
this.sources = new Set();
|
|
2241
2554
|
this.started = false;
|
|
2242
2555
|
this.inspectFetchResponse = ((event) => {
|
|
2243
2556
|
const response = fetchResponseFromEvent(event);
|
|
@@ -2287,7 +2600,7 @@ class StreamObserver {
|
|
|
2287
2600
|
}
|
|
2288
2601
|
}
|
|
2289
2602
|
receiveMessageHTML(html) {
|
|
2290
|
-
this.delegate.receivedMessageFromStream(
|
|
2603
|
+
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
|
2291
2604
|
}
|
|
2292
2605
|
}
|
|
2293
2606
|
function fetchResponseFromEvent(event) {
|
|
@@ -2304,20 +2617,24 @@ function fetchResponseIsStream(response) {
|
|
|
2304
2617
|
}
|
|
2305
2618
|
|
|
2306
2619
|
class ErrorRenderer extends Renderer {
|
|
2620
|
+
static renderElement(currentElement, newElement) {
|
|
2621
|
+
const { documentElement, body } = document;
|
|
2622
|
+
documentElement.replaceChild(newElement, body);
|
|
2623
|
+
}
|
|
2307
2624
|
async render() {
|
|
2308
2625
|
this.replaceHeadAndBody();
|
|
2309
2626
|
this.activateScriptElements();
|
|
2310
2627
|
}
|
|
2311
2628
|
replaceHeadAndBody() {
|
|
2312
|
-
const { documentElement, head
|
|
2629
|
+
const { documentElement, head } = document;
|
|
2313
2630
|
documentElement.replaceChild(this.newHead, head);
|
|
2314
|
-
|
|
2631
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
2315
2632
|
}
|
|
2316
2633
|
activateScriptElements() {
|
|
2317
2634
|
for (const replaceableElement of this.scriptElements) {
|
|
2318
2635
|
const parentNode = replaceableElement.parentNode;
|
|
2319
2636
|
if (parentNode) {
|
|
2320
|
-
const element =
|
|
2637
|
+
const element = activateScriptElement(replaceableElement);
|
|
2321
2638
|
parentNode.replaceChild(element, replaceableElement);
|
|
2322
2639
|
}
|
|
2323
2640
|
}
|
|
@@ -2326,20 +2643,40 @@ class ErrorRenderer extends Renderer {
|
|
|
2326
2643
|
return this.newSnapshot.headSnapshot.element;
|
|
2327
2644
|
}
|
|
2328
2645
|
get scriptElements() {
|
|
2329
|
-
return
|
|
2646
|
+
return document.documentElement.querySelectorAll("script");
|
|
2330
2647
|
}
|
|
2331
2648
|
}
|
|
2332
2649
|
|
|
2333
2650
|
class PageRenderer extends Renderer {
|
|
2651
|
+
static renderElement(currentElement, newElement) {
|
|
2652
|
+
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
2653
|
+
document.body.replaceWith(newElement);
|
|
2654
|
+
}
|
|
2655
|
+
else {
|
|
2656
|
+
document.documentElement.appendChild(newElement);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2334
2659
|
get shouldRender() {
|
|
2335
2660
|
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
|
2336
2661
|
}
|
|
2337
|
-
|
|
2338
|
-
this.
|
|
2662
|
+
get reloadReason() {
|
|
2663
|
+
if (!this.newSnapshot.isVisitable) {
|
|
2664
|
+
return {
|
|
2665
|
+
reason: "turbo_visit_control_is_reload",
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
if (!this.trackedElementsAreIdentical) {
|
|
2669
|
+
return {
|
|
2670
|
+
reason: "tracked_element_mismatch",
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
async prepareToRender() {
|
|
2675
|
+
await this.mergeHead();
|
|
2339
2676
|
}
|
|
2340
2677
|
async render() {
|
|
2341
2678
|
if (this.willRender) {
|
|
2342
|
-
this.replaceBody();
|
|
2679
|
+
await this.replaceBody();
|
|
2343
2680
|
}
|
|
2344
2681
|
}
|
|
2345
2682
|
finishRendering() {
|
|
@@ -2357,30 +2694,63 @@ class PageRenderer extends Renderer {
|
|
|
2357
2694
|
get newElement() {
|
|
2358
2695
|
return this.newSnapshot.element;
|
|
2359
2696
|
}
|
|
2360
|
-
mergeHead() {
|
|
2361
|
-
this.
|
|
2697
|
+
async mergeHead() {
|
|
2698
|
+
const mergedHeadElements = this.mergeProvisionalElements();
|
|
2699
|
+
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
|
2362
2700
|
this.copyNewHeadScriptElements();
|
|
2363
|
-
|
|
2364
|
-
|
|
2701
|
+
await mergedHeadElements;
|
|
2702
|
+
await newStylesheetElements;
|
|
2365
2703
|
}
|
|
2366
|
-
replaceBody() {
|
|
2367
|
-
this.preservingPermanentElements(() => {
|
|
2704
|
+
async replaceBody() {
|
|
2705
|
+
await this.preservingPermanentElements(async () => {
|
|
2368
2706
|
this.activateNewBody();
|
|
2369
|
-
this.assignNewBody();
|
|
2707
|
+
await this.assignNewBody();
|
|
2370
2708
|
});
|
|
2371
2709
|
}
|
|
2372
2710
|
get trackedElementsAreIdentical() {
|
|
2373
2711
|
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
|
2374
2712
|
}
|
|
2375
|
-
copyNewHeadStylesheetElements() {
|
|
2713
|
+
async copyNewHeadStylesheetElements() {
|
|
2714
|
+
const loadingElements = [];
|
|
2376
2715
|
for (const element of this.newHeadStylesheetElements) {
|
|
2716
|
+
loadingElements.push(waitForLoad(element));
|
|
2377
2717
|
document.head.appendChild(element);
|
|
2378
2718
|
}
|
|
2719
|
+
await Promise.all(loadingElements);
|
|
2379
2720
|
}
|
|
2380
2721
|
copyNewHeadScriptElements() {
|
|
2381
2722
|
for (const element of this.newHeadScriptElements) {
|
|
2382
|
-
document.head.appendChild(
|
|
2723
|
+
document.head.appendChild(activateScriptElement(element));
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
async mergeProvisionalElements() {
|
|
2727
|
+
const newHeadElements = [...this.newHeadProvisionalElements];
|
|
2728
|
+
for (const element of this.currentHeadProvisionalElements) {
|
|
2729
|
+
if (!this.isCurrentElementInElementList(element, newHeadElements)) {
|
|
2730
|
+
document.head.removeChild(element);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
for (const element of newHeadElements) {
|
|
2734
|
+
document.head.appendChild(element);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
isCurrentElementInElementList(element, elementList) {
|
|
2738
|
+
for (const [index, newElement] of elementList.entries()) {
|
|
2739
|
+
if (element.tagName == "TITLE") {
|
|
2740
|
+
if (newElement.tagName != "TITLE") {
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
if (element.innerHTML == newElement.innerHTML) {
|
|
2744
|
+
elementList.splice(index, 1);
|
|
2745
|
+
return true;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (newElement.isEqualNode(element)) {
|
|
2749
|
+
elementList.splice(index, 1);
|
|
2750
|
+
return true;
|
|
2751
|
+
}
|
|
2383
2752
|
}
|
|
2753
|
+
return false;
|
|
2384
2754
|
}
|
|
2385
2755
|
removeCurrentHeadProvisionalElements() {
|
|
2386
2756
|
for (const element of this.currentHeadProvisionalElements) {
|
|
@@ -2398,17 +2768,12 @@ class PageRenderer extends Renderer {
|
|
|
2398
2768
|
}
|
|
2399
2769
|
activateNewBodyScriptElements() {
|
|
2400
2770
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
2401
|
-
const activatedScriptElement =
|
|
2771
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
2402
2772
|
inertScriptElement.replaceWith(activatedScriptElement);
|
|
2403
2773
|
}
|
|
2404
2774
|
}
|
|
2405
|
-
assignNewBody() {
|
|
2406
|
-
|
|
2407
|
-
document.body.replaceWith(this.newElement);
|
|
2408
|
-
}
|
|
2409
|
-
else {
|
|
2410
|
-
document.documentElement.appendChild(this.newElement);
|
|
2411
|
-
}
|
|
2775
|
+
async assignNewBody() {
|
|
2776
|
+
await this.renderElement(this.currentElement, this.newElement);
|
|
2412
2777
|
}
|
|
2413
2778
|
get newHeadStylesheetElements() {
|
|
2414
2779
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
|
@@ -2477,22 +2842,30 @@ class PageView extends View {
|
|
|
2477
2842
|
super(...arguments);
|
|
2478
2843
|
this.snapshotCache = new SnapshotCache(10);
|
|
2479
2844
|
this.lastRenderedLocation = new URL(location.href);
|
|
2845
|
+
this.forceReloaded = false;
|
|
2480
2846
|
}
|
|
2481
|
-
renderPage(snapshot, isPreview = false, willRender = true) {
|
|
2482
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
|
2847
|
+
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
2848
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
|
2849
|
+
if (!renderer.shouldRender) {
|
|
2850
|
+
this.forceReloaded = true;
|
|
2851
|
+
}
|
|
2852
|
+
else {
|
|
2853
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
|
2854
|
+
}
|
|
2483
2855
|
return this.render(renderer);
|
|
2484
2856
|
}
|
|
2485
|
-
renderError(snapshot) {
|
|
2486
|
-
|
|
2857
|
+
renderError(snapshot, visit) {
|
|
2858
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
|
2859
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
|
2487
2860
|
return this.render(renderer);
|
|
2488
2861
|
}
|
|
2489
2862
|
clearSnapshotCache() {
|
|
2490
2863
|
this.snapshotCache.clear();
|
|
2491
2864
|
}
|
|
2492
|
-
async cacheSnapshot() {
|
|
2493
|
-
if (
|
|
2865
|
+
async cacheSnapshot(snapshot = this.snapshot) {
|
|
2866
|
+
if (snapshot.isCacheable) {
|
|
2494
2867
|
this.delegate.viewWillCacheSnapshot();
|
|
2495
|
-
const {
|
|
2868
|
+
const { lastRenderedLocation: location } = this;
|
|
2496
2869
|
await nextEventLoopTick();
|
|
2497
2870
|
const cachedSnapshot = snapshot.clone();
|
|
2498
2871
|
this.snapshotCache.put(location, cachedSnapshot);
|
|
@@ -2505,8 +2878,44 @@ class PageView extends View {
|
|
|
2505
2878
|
get snapshot() {
|
|
2506
2879
|
return PageSnapshot.fromElement(this.element);
|
|
2507
2880
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
class Preloader {
|
|
2884
|
+
constructor(delegate) {
|
|
2885
|
+
this.selector = "a[data-turbo-preload]";
|
|
2886
|
+
this.delegate = delegate;
|
|
2887
|
+
}
|
|
2888
|
+
get snapshotCache() {
|
|
2889
|
+
return this.delegate.navigator.view.snapshotCache;
|
|
2890
|
+
}
|
|
2891
|
+
start() {
|
|
2892
|
+
if (document.readyState === "loading") {
|
|
2893
|
+
return document.addEventListener("DOMContentLoaded", () => {
|
|
2894
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
else {
|
|
2898
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
preloadOnLoadLinksForView(element) {
|
|
2902
|
+
for (const link of element.querySelectorAll(this.selector)) {
|
|
2903
|
+
this.preloadURL(link);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async preloadURL(link) {
|
|
2907
|
+
const location = new URL(link.href);
|
|
2908
|
+
if (this.snapshotCache.has(location)) {
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
|
|
2913
|
+
const responseText = await response.text();
|
|
2914
|
+
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
|
2915
|
+
this.snapshotCache.put(location, snapshot);
|
|
2916
|
+
}
|
|
2917
|
+
catch (_) {
|
|
2918
|
+
}
|
|
2510
2919
|
}
|
|
2511
2920
|
}
|
|
2512
2921
|
|
|
@@ -2514,30 +2923,36 @@ class Session {
|
|
|
2514
2923
|
constructor() {
|
|
2515
2924
|
this.navigator = new Navigator(this);
|
|
2516
2925
|
this.history = new History(this);
|
|
2926
|
+
this.preloader = new Preloader(this);
|
|
2517
2927
|
this.view = new PageView(this, document.documentElement);
|
|
2518
2928
|
this.adapter = new BrowserAdapter(this);
|
|
2519
2929
|
this.pageObserver = new PageObserver(this);
|
|
2520
2930
|
this.cacheObserver = new CacheObserver();
|
|
2521
|
-
this.linkClickObserver = new LinkClickObserver(this);
|
|
2522
|
-
this.formSubmitObserver = new FormSubmitObserver(this);
|
|
2931
|
+
this.linkClickObserver = new LinkClickObserver(this, window);
|
|
2932
|
+
this.formSubmitObserver = new FormSubmitObserver(this, document);
|
|
2523
2933
|
this.scrollObserver = new ScrollObserver(this);
|
|
2524
2934
|
this.streamObserver = new StreamObserver(this);
|
|
2525
|
-
this.
|
|
2935
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
|
|
2936
|
+
this.frameRedirector = new FrameRedirector(this, document.documentElement);
|
|
2937
|
+
this.streamMessageRenderer = new StreamMessageRenderer();
|
|
2526
2938
|
this.drive = true;
|
|
2527
2939
|
this.enabled = true;
|
|
2528
2940
|
this.progressBarDelay = 500;
|
|
2529
2941
|
this.started = false;
|
|
2942
|
+
this.formMode = "on";
|
|
2530
2943
|
}
|
|
2531
2944
|
start() {
|
|
2532
2945
|
if (!this.started) {
|
|
2533
2946
|
this.pageObserver.start();
|
|
2534
2947
|
this.cacheObserver.start();
|
|
2948
|
+
this.formLinkClickObserver.start();
|
|
2535
2949
|
this.linkClickObserver.start();
|
|
2536
2950
|
this.formSubmitObserver.start();
|
|
2537
2951
|
this.scrollObserver.start();
|
|
2538
2952
|
this.streamObserver.start();
|
|
2539
2953
|
this.frameRedirector.start();
|
|
2540
2954
|
this.history.start();
|
|
2955
|
+
this.preloader.start();
|
|
2541
2956
|
this.started = true;
|
|
2542
2957
|
this.enabled = true;
|
|
2543
2958
|
}
|
|
@@ -2549,6 +2964,7 @@ class Session {
|
|
|
2549
2964
|
if (this.started) {
|
|
2550
2965
|
this.pageObserver.stop();
|
|
2551
2966
|
this.cacheObserver.stop();
|
|
2967
|
+
this.formLinkClickObserver.stop();
|
|
2552
2968
|
this.linkClickObserver.stop();
|
|
2553
2969
|
this.formSubmitObserver.stop();
|
|
2554
2970
|
this.scrollObserver.stop();
|
|
@@ -2562,7 +2978,14 @@ class Session {
|
|
|
2562
2978
|
this.adapter = adapter;
|
|
2563
2979
|
}
|
|
2564
2980
|
visit(location, options = {}) {
|
|
2565
|
-
|
|
2981
|
+
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
|
2982
|
+
if (frameElement instanceof FrameElement) {
|
|
2983
|
+
frameElement.src = location.toString();
|
|
2984
|
+
frameElement.loaded;
|
|
2985
|
+
}
|
|
2986
|
+
else {
|
|
2987
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
|
2988
|
+
}
|
|
2566
2989
|
}
|
|
2567
2990
|
connectStreamSource(source) {
|
|
2568
2991
|
this.streamObserver.connectStreamSource(source);
|
|
@@ -2571,7 +2994,7 @@ class Session {
|
|
|
2571
2994
|
this.streamObserver.disconnectStreamSource(source);
|
|
2572
2995
|
}
|
|
2573
2996
|
renderStreamMessage(message) {
|
|
2574
|
-
|
|
2997
|
+
this.streamMessageRenderer.render(StreamMessage.wrap(message));
|
|
2575
2998
|
}
|
|
2576
2999
|
clearCache() {
|
|
2577
3000
|
this.view.clearSnapshotCache();
|
|
@@ -2579,6 +3002,9 @@ class Session {
|
|
|
2579
3002
|
setProgressBarDelay(delay) {
|
|
2580
3003
|
this.progressBarDelay = delay;
|
|
2581
3004
|
}
|
|
3005
|
+
setFormMode(mode) {
|
|
3006
|
+
this.formMode = mode;
|
|
3007
|
+
}
|
|
2582
3008
|
get location() {
|
|
2583
3009
|
return this.history.location;
|
|
2584
3010
|
}
|
|
@@ -2587,48 +3013,33 @@ class Session {
|
|
|
2587
3013
|
}
|
|
2588
3014
|
historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
|
|
2589
3015
|
if (this.enabled) {
|
|
2590
|
-
this.navigator.startVisit(location, restorationIdentifier, {
|
|
3016
|
+
this.navigator.startVisit(location, restorationIdentifier, {
|
|
3017
|
+
action: "restore",
|
|
3018
|
+
historyChanged: true,
|
|
3019
|
+
});
|
|
2591
3020
|
}
|
|
2592
3021
|
else {
|
|
2593
|
-
this.adapter.pageInvalidated(
|
|
3022
|
+
this.adapter.pageInvalidated({
|
|
3023
|
+
reason: "turbo_disabled",
|
|
3024
|
+
});
|
|
2594
3025
|
}
|
|
2595
3026
|
}
|
|
2596
3027
|
scrollPositionChanged(position) {
|
|
2597
3028
|
this.history.updateRestorationData({ scrollPosition: position });
|
|
2598
3029
|
}
|
|
2599
|
-
|
|
2600
|
-
return this.
|
|
2601
|
-
|
|
2602
|
-
|
|
3030
|
+
willSubmitFormLinkToLocation(link, location) {
|
|
3031
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
|
3032
|
+
}
|
|
3033
|
+
submittedFormLinkToLocation() { }
|
|
3034
|
+
willFollowLinkToLocation(link, location, event) {
|
|
3035
|
+
return (this.elementIsNavigatable(link) &&
|
|
3036
|
+
locationIsVisitable(location, this.snapshot.rootLocation) &&
|
|
3037
|
+
this.applicationAllowsFollowingLinkToLocation(link, location, event));
|
|
2603
3038
|
}
|
|
2604
3039
|
followedLinkToLocation(link, location) {
|
|
2605
3040
|
const action = this.getActionForLink(link);
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
convertLinkWithMethodClickToFormSubmission(link) {
|
|
2609
|
-
const linkMethod = link.getAttribute("data-turbo-method");
|
|
2610
|
-
if (linkMethod) {
|
|
2611
|
-
const form = document.createElement("form");
|
|
2612
|
-
form.method = linkMethod;
|
|
2613
|
-
form.action = link.getAttribute("href") || "undefined";
|
|
2614
|
-
form.hidden = true;
|
|
2615
|
-
if (link.hasAttribute("data-turbo-confirm")) {
|
|
2616
|
-
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
|
|
2617
|
-
}
|
|
2618
|
-
const frame = this.getTargetFrameForLink(link);
|
|
2619
|
-
if (frame) {
|
|
2620
|
-
form.setAttribute("data-turbo-frame", frame);
|
|
2621
|
-
form.addEventListener("turbo:submit-start", () => form.remove());
|
|
2622
|
-
}
|
|
2623
|
-
else {
|
|
2624
|
-
form.addEventListener("submit", () => form.remove());
|
|
2625
|
-
}
|
|
2626
|
-
document.body.appendChild(form);
|
|
2627
|
-
return dispatch("submit", { cancelable: true, target: form });
|
|
2628
|
-
}
|
|
2629
|
-
else {
|
|
2630
|
-
return false;
|
|
2631
|
-
}
|
|
3041
|
+
const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
|
|
3042
|
+
this.visit(location.href, { action, acceptsStreamResponse });
|
|
2632
3043
|
}
|
|
2633
3044
|
allowsVisitingLocationWithAction(location, action) {
|
|
2634
3045
|
return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
|
|
@@ -2638,12 +3049,16 @@ class Session {
|
|
|
2638
3049
|
this.adapter.visitProposedToLocation(location, options);
|
|
2639
3050
|
}
|
|
2640
3051
|
visitStarted(visit) {
|
|
3052
|
+
if (!visit.acceptsStreamResponse) {
|
|
3053
|
+
markAsBusy(document.documentElement);
|
|
3054
|
+
}
|
|
2641
3055
|
extendURLWithDeprecatedProperties(visit.location);
|
|
2642
3056
|
if (!visit.silent) {
|
|
2643
3057
|
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
2644
3058
|
}
|
|
2645
3059
|
}
|
|
2646
3060
|
visitCompleted(visit) {
|
|
3061
|
+
clearBusyState(document.documentElement);
|
|
2647
3062
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
2648
3063
|
}
|
|
2649
3064
|
locationWithActionIsSamePage(location, action) {
|
|
@@ -2654,9 +3069,8 @@ class Session {
|
|
|
2654
3069
|
}
|
|
2655
3070
|
willSubmitForm(form, submitter) {
|
|
2656
3071
|
const action = getAction(form, submitter);
|
|
2657
|
-
return this.
|
|
2658
|
-
|
|
2659
|
-
&& locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
3072
|
+
return (this.submissionIsNavigatable(form, submitter) &&
|
|
3073
|
+
locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
|
|
2660
3074
|
}
|
|
2661
3075
|
formSubmitted(form, submitter) {
|
|
2662
3076
|
this.navigator.submitForm(form, submitter);
|
|
@@ -2680,16 +3094,23 @@ class Session {
|
|
|
2680
3094
|
this.notifyApplicationBeforeCachingSnapshot();
|
|
2681
3095
|
}
|
|
2682
3096
|
}
|
|
2683
|
-
allowsImmediateRender({ element },
|
|
2684
|
-
const event = this.notifyApplicationBeforeRender(element,
|
|
2685
|
-
|
|
3097
|
+
allowsImmediateRender({ element }, options) {
|
|
3098
|
+
const event = this.notifyApplicationBeforeRender(element, options);
|
|
3099
|
+
const { defaultPrevented, detail: { render }, } = event;
|
|
3100
|
+
if (this.view.renderer && render) {
|
|
3101
|
+
this.view.renderer.renderElement = render;
|
|
3102
|
+
}
|
|
3103
|
+
return !defaultPrevented;
|
|
2686
3104
|
}
|
|
2687
|
-
viewRenderedSnapshot(
|
|
3105
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {
|
|
2688
3106
|
this.view.lastRenderedLocation = this.history.location;
|
|
2689
3107
|
this.notifyApplicationAfterRender();
|
|
2690
3108
|
}
|
|
2691
|
-
|
|
2692
|
-
this.
|
|
3109
|
+
preloadOnLoadLinksForView(element) {
|
|
3110
|
+
this.preloader.preloadOnLoadLinksForView(element);
|
|
3111
|
+
}
|
|
3112
|
+
viewInvalidated(reason) {
|
|
3113
|
+
this.adapter.pageInvalidated(reason);
|
|
2693
3114
|
}
|
|
2694
3115
|
frameLoaded(frame) {
|
|
2695
3116
|
this.notifyApplicationAfterFrameLoad(frame);
|
|
@@ -2697,49 +3118,81 @@ class Session {
|
|
|
2697
3118
|
frameRendered(fetchResponse, frame) {
|
|
2698
3119
|
this.notifyApplicationAfterFrameRender(fetchResponse, frame);
|
|
2699
3120
|
}
|
|
2700
|
-
applicationAllowsFollowingLinkToLocation(link, location) {
|
|
2701
|
-
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
|
3121
|
+
applicationAllowsFollowingLinkToLocation(link, location, ev) {
|
|
3122
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
|
|
2702
3123
|
return !event.defaultPrevented;
|
|
2703
3124
|
}
|
|
2704
3125
|
applicationAllowsVisitingLocation(location) {
|
|
2705
3126
|
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
|
2706
3127
|
return !event.defaultPrevented;
|
|
2707
3128
|
}
|
|
2708
|
-
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
|
2709
|
-
return dispatch("turbo:click", {
|
|
3129
|
+
notifyApplicationAfterClickingLinkToLocation(link, location, event) {
|
|
3130
|
+
return dispatch("turbo:click", {
|
|
3131
|
+
target: link,
|
|
3132
|
+
detail: { url: location.href, originalEvent: event },
|
|
3133
|
+
cancelable: true,
|
|
3134
|
+
});
|
|
2710
3135
|
}
|
|
2711
3136
|
notifyApplicationBeforeVisitingLocation(location) {
|
|
2712
|
-
return dispatch("turbo:before-visit", {
|
|
3137
|
+
return dispatch("turbo:before-visit", {
|
|
3138
|
+
detail: { url: location.href },
|
|
3139
|
+
cancelable: true,
|
|
3140
|
+
});
|
|
2713
3141
|
}
|
|
2714
3142
|
notifyApplicationAfterVisitingLocation(location, action) {
|
|
2715
|
-
markAsBusy(document.documentElement);
|
|
2716
3143
|
return dispatch("turbo:visit", { detail: { url: location.href, action } });
|
|
2717
3144
|
}
|
|
2718
3145
|
notifyApplicationBeforeCachingSnapshot() {
|
|
2719
3146
|
return dispatch("turbo:before-cache");
|
|
2720
3147
|
}
|
|
2721
|
-
notifyApplicationBeforeRender(newBody,
|
|
2722
|
-
return dispatch("turbo:before-render", {
|
|
3148
|
+
notifyApplicationBeforeRender(newBody, options) {
|
|
3149
|
+
return dispatch("turbo:before-render", {
|
|
3150
|
+
detail: Object.assign({ newBody }, options),
|
|
3151
|
+
cancelable: true,
|
|
3152
|
+
});
|
|
2723
3153
|
}
|
|
2724
3154
|
notifyApplicationAfterRender() {
|
|
2725
3155
|
return dispatch("turbo:render");
|
|
2726
3156
|
}
|
|
2727
3157
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
2728
|
-
|
|
2729
|
-
|
|
3158
|
+
return dispatch("turbo:load", {
|
|
3159
|
+
detail: { url: this.location.href, timing },
|
|
3160
|
+
});
|
|
2730
3161
|
}
|
|
2731
3162
|
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
2732
|
-
dispatchEvent(new HashChangeEvent("hashchange", {
|
|
3163
|
+
dispatchEvent(new HashChangeEvent("hashchange", {
|
|
3164
|
+
oldURL: oldURL.toString(),
|
|
3165
|
+
newURL: newURL.toString(),
|
|
3166
|
+
}));
|
|
2733
3167
|
}
|
|
2734
3168
|
notifyApplicationAfterFrameLoad(frame) {
|
|
2735
3169
|
return dispatch("turbo:frame-load", { target: frame });
|
|
2736
3170
|
}
|
|
2737
3171
|
notifyApplicationAfterFrameRender(fetchResponse, frame) {
|
|
2738
|
-
return dispatch("turbo:frame-render", {
|
|
3172
|
+
return dispatch("turbo:frame-render", {
|
|
3173
|
+
detail: { fetchResponse },
|
|
3174
|
+
target: frame,
|
|
3175
|
+
cancelable: true,
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
submissionIsNavigatable(form, submitter) {
|
|
3179
|
+
if (this.formMode == "off") {
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
else {
|
|
3183
|
+
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
|
3184
|
+
if (this.formMode == "optin") {
|
|
3185
|
+
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
|
|
3186
|
+
}
|
|
3187
|
+
else {
|
|
3188
|
+
return submitterIsNavigatable && this.elementIsNavigatable(form);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
2739
3191
|
}
|
|
2740
|
-
|
|
2741
|
-
const container = element
|
|
2742
|
-
|
|
3192
|
+
elementIsNavigatable(element) {
|
|
3193
|
+
const container = findClosestRecursively(element, "[data-turbo]");
|
|
3194
|
+
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
|
3195
|
+
if (this.drive || withinFrame) {
|
|
2743
3196
|
if (container) {
|
|
2744
3197
|
return container.getAttribute("data-turbo") != "false";
|
|
2745
3198
|
}
|
|
@@ -2757,20 +3210,7 @@ class Session {
|
|
|
2757
3210
|
}
|
|
2758
3211
|
}
|
|
2759
3212
|
getActionForLink(link) {
|
|
2760
|
-
|
|
2761
|
-
return isAction(action) ? action : "advance";
|
|
2762
|
-
}
|
|
2763
|
-
getTargetFrameForLink(link) {
|
|
2764
|
-
const frame = link.getAttribute("data-turbo-frame");
|
|
2765
|
-
if (frame) {
|
|
2766
|
-
return frame;
|
|
2767
|
-
}
|
|
2768
|
-
else {
|
|
2769
|
-
const container = link.closest("turbo-frame");
|
|
2770
|
-
if (container) {
|
|
2771
|
-
return container.id;
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
3213
|
+
return getVisitAction(link) || "advance";
|
|
2774
3214
|
}
|
|
2775
3215
|
get snapshot() {
|
|
2776
3216
|
return this.view.snapshot;
|
|
@@ -2783,11 +3223,62 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
2783
3223
|
absoluteURL: {
|
|
2784
3224
|
get() {
|
|
2785
3225
|
return this.toString();
|
|
2786
|
-
}
|
|
3226
|
+
},
|
|
3227
|
+
},
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3230
|
+
class Cache {
|
|
3231
|
+
constructor(session) {
|
|
3232
|
+
this.session = session;
|
|
2787
3233
|
}
|
|
3234
|
+
clear() {
|
|
3235
|
+
this.session.clearCache();
|
|
3236
|
+
}
|
|
3237
|
+
resetCacheControl() {
|
|
3238
|
+
this.setCacheControl("");
|
|
3239
|
+
}
|
|
3240
|
+
exemptPageFromCache() {
|
|
3241
|
+
this.setCacheControl("no-cache");
|
|
3242
|
+
}
|
|
3243
|
+
exemptPageFromPreview() {
|
|
3244
|
+
this.setCacheControl("no-preview");
|
|
3245
|
+
}
|
|
3246
|
+
setCacheControl(value) {
|
|
3247
|
+
setMetaContent("turbo-cache-control", value);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
const StreamActions = {
|
|
3252
|
+
after() {
|
|
3253
|
+
this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
|
|
3254
|
+
},
|
|
3255
|
+
append() {
|
|
3256
|
+
this.removeDuplicateTargetChildren();
|
|
3257
|
+
this.targetElements.forEach((e) => e.append(this.templateContent));
|
|
3258
|
+
},
|
|
3259
|
+
before() {
|
|
3260
|
+
this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
|
|
3261
|
+
},
|
|
3262
|
+
prepend() {
|
|
3263
|
+
this.removeDuplicateTargetChildren();
|
|
3264
|
+
this.targetElements.forEach((e) => e.prepend(this.templateContent));
|
|
3265
|
+
},
|
|
3266
|
+
remove() {
|
|
3267
|
+
this.targetElements.forEach((e) => e.remove());
|
|
3268
|
+
},
|
|
3269
|
+
replace() {
|
|
3270
|
+
this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
|
|
3271
|
+
},
|
|
3272
|
+
update() {
|
|
3273
|
+
this.targetElements.forEach((targetElement) => {
|
|
3274
|
+
targetElement.innerHTML = "";
|
|
3275
|
+
targetElement.append(this.templateContent);
|
|
3276
|
+
});
|
|
3277
|
+
},
|
|
2788
3278
|
};
|
|
2789
3279
|
|
|
2790
|
-
const session = new Session;
|
|
3280
|
+
const session = new Session();
|
|
3281
|
+
const cache = new Cache(session);
|
|
2791
3282
|
const { navigator: navigator$1 } = session;
|
|
2792
3283
|
function start() {
|
|
2793
3284
|
session.start();
|
|
@@ -2808,6 +3299,7 @@ function renderStreamMessage(message) {
|
|
|
2808
3299
|
session.renderStreamMessage(message);
|
|
2809
3300
|
}
|
|
2810
3301
|
function clearCache() {
|
|
3302
|
+
console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
2811
3303
|
session.clearCache();
|
|
2812
3304
|
}
|
|
2813
3305
|
function setProgressBarDelay(delay) {
|
|
@@ -2816,13 +3308,18 @@ function setProgressBarDelay(delay) {
|
|
|
2816
3308
|
function setConfirmMethod(confirmMethod) {
|
|
2817
3309
|
FormSubmission.confirmMethod = confirmMethod;
|
|
2818
3310
|
}
|
|
3311
|
+
function setFormMode(mode) {
|
|
3312
|
+
session.setFormMode(mode);
|
|
3313
|
+
}
|
|
2819
3314
|
|
|
2820
3315
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
2821
3316
|
__proto__: null,
|
|
2822
3317
|
navigator: navigator$1,
|
|
2823
3318
|
session: session,
|
|
3319
|
+
cache: cache,
|
|
2824
3320
|
PageRenderer: PageRenderer,
|
|
2825
3321
|
PageSnapshot: PageSnapshot,
|
|
3322
|
+
FrameRenderer: FrameRenderer,
|
|
2826
3323
|
start: start,
|
|
2827
3324
|
registerAdapter: registerAdapter,
|
|
2828
3325
|
visit: visit,
|
|
@@ -2831,41 +3328,59 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
2831
3328
|
renderStreamMessage: renderStreamMessage,
|
|
2832
3329
|
clearCache: clearCache,
|
|
2833
3330
|
setProgressBarDelay: setProgressBarDelay,
|
|
2834
|
-
setConfirmMethod: setConfirmMethod
|
|
3331
|
+
setConfirmMethod: setConfirmMethod,
|
|
3332
|
+
setFormMode: setFormMode,
|
|
3333
|
+
StreamActions: StreamActions
|
|
2835
3334
|
});
|
|
2836
3335
|
|
|
3336
|
+
class TurboFrameMissingError extends Error {
|
|
3337
|
+
}
|
|
3338
|
+
|
|
2837
3339
|
class FrameController {
|
|
2838
3340
|
constructor(element) {
|
|
2839
|
-
this.fetchResponseLoaded = (
|
|
3341
|
+
this.fetchResponseLoaded = (_fetchResponse) => { };
|
|
2840
3342
|
this.currentFetchRequest = null;
|
|
2841
3343
|
this.resolveVisitPromise = () => { };
|
|
2842
3344
|
this.connected = false;
|
|
2843
3345
|
this.hasBeenLoaded = false;
|
|
2844
|
-
this.
|
|
3346
|
+
this.ignoredAttributes = new Set();
|
|
3347
|
+
this.action = null;
|
|
3348
|
+
this.visitCachedSnapshot = ({ element }) => {
|
|
3349
|
+
const frame = element.querySelector("#" + this.element.id);
|
|
3350
|
+
if (frame && this.previousFrameElement) {
|
|
3351
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
|
3352
|
+
}
|
|
3353
|
+
delete this.previousFrameElement;
|
|
3354
|
+
};
|
|
2845
3355
|
this.element = element;
|
|
2846
3356
|
this.view = new FrameView(this, this.element);
|
|
2847
3357
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
|
3358
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
|
|
2848
3359
|
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
|
2849
|
-
this.
|
|
3360
|
+
this.restorationIdentifier = uuid();
|
|
3361
|
+
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
|
2850
3362
|
}
|
|
2851
3363
|
connect() {
|
|
2852
3364
|
if (!this.connected) {
|
|
2853
3365
|
this.connected = true;
|
|
2854
|
-
this.reloadable = false;
|
|
2855
3366
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
2856
3367
|
this.appearanceObserver.start();
|
|
2857
3368
|
}
|
|
3369
|
+
else {
|
|
3370
|
+
this.loadSourceURL();
|
|
3371
|
+
}
|
|
3372
|
+
this.formLinkClickObserver.start();
|
|
2858
3373
|
this.linkInterceptor.start();
|
|
2859
|
-
this.
|
|
2860
|
-
this.sourceURLChanged();
|
|
3374
|
+
this.formSubmitObserver.start();
|
|
2861
3375
|
}
|
|
2862
3376
|
}
|
|
2863
3377
|
disconnect() {
|
|
2864
3378
|
if (this.connected) {
|
|
2865
3379
|
this.connected = false;
|
|
2866
3380
|
this.appearanceObserver.stop();
|
|
3381
|
+
this.formLinkClickObserver.stop();
|
|
2867
3382
|
this.linkInterceptor.stop();
|
|
2868
|
-
this.
|
|
3383
|
+
this.formSubmitObserver.stop();
|
|
2869
3384
|
}
|
|
2870
3385
|
}
|
|
2871
3386
|
disabledChanged() {
|
|
@@ -2874,10 +3389,29 @@ class FrameController {
|
|
|
2874
3389
|
}
|
|
2875
3390
|
}
|
|
2876
3391
|
sourceURLChanged() {
|
|
3392
|
+
if (this.isIgnoringChangesTo("src"))
|
|
3393
|
+
return;
|
|
3394
|
+
if (this.element.isConnected) {
|
|
3395
|
+
this.complete = false;
|
|
3396
|
+
}
|
|
2877
3397
|
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
|
2878
3398
|
this.loadSourceURL();
|
|
2879
3399
|
}
|
|
2880
3400
|
}
|
|
3401
|
+
sourceURLReloaded() {
|
|
3402
|
+
const { src } = this.element;
|
|
3403
|
+
this.ignoringChangesToAttribute("complete", () => {
|
|
3404
|
+
this.element.removeAttribute("complete");
|
|
3405
|
+
});
|
|
3406
|
+
this.element.src = null;
|
|
3407
|
+
this.element.src = src;
|
|
3408
|
+
return this.element.loaded;
|
|
3409
|
+
}
|
|
3410
|
+
completeChanged() {
|
|
3411
|
+
if (this.isIgnoringChangesTo("complete"))
|
|
3412
|
+
return;
|
|
3413
|
+
this.loadSourceURL();
|
|
3414
|
+
}
|
|
2881
3415
|
loadingStyleChanged() {
|
|
2882
3416
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
2883
3417
|
this.appearanceObserver.start();
|
|
@@ -2888,21 +3422,11 @@ class FrameController {
|
|
|
2888
3422
|
}
|
|
2889
3423
|
}
|
|
2890
3424
|
async loadSourceURL() {
|
|
2891
|
-
if (
|
|
2892
|
-
|
|
2893
|
-
this.
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
|
2897
|
-
this.appearanceObserver.stop();
|
|
2898
|
-
await this.element.loaded;
|
|
2899
|
-
this.hasBeenLoaded = true;
|
|
2900
|
-
}
|
|
2901
|
-
catch (error) {
|
|
2902
|
-
this.currentURL = previousURL;
|
|
2903
|
-
throw error;
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
3425
|
+
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
|
3426
|
+
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
|
3427
|
+
this.appearanceObserver.stop();
|
|
3428
|
+
await this.element.loaded;
|
|
3429
|
+
this.hasBeenLoaded = true;
|
|
2906
3430
|
}
|
|
2907
3431
|
}
|
|
2908
3432
|
async loadResponse(fetchResponse) {
|
|
@@ -2912,75 +3436,76 @@ class FrameController {
|
|
|
2912
3436
|
try {
|
|
2913
3437
|
const html = await fetchResponse.responseHTML;
|
|
2914
3438
|
if (html) {
|
|
2915
|
-
const
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
this.fetchResponseLoaded(fetchResponse);
|
|
3439
|
+
const document = parseHTMLDocument(html);
|
|
3440
|
+
const pageSnapshot = PageSnapshot.fromDocument(document);
|
|
3441
|
+
if (pageSnapshot.isVisitable) {
|
|
3442
|
+
await this.loadFrameResponse(fetchResponse, document);
|
|
3443
|
+
}
|
|
3444
|
+
else {
|
|
3445
|
+
await this.handleUnvisitableFrameResponse(fetchResponse);
|
|
3446
|
+
}
|
|
2924
3447
|
}
|
|
2925
3448
|
}
|
|
2926
|
-
catch (error) {
|
|
2927
|
-
console.error(error);
|
|
2928
|
-
this.view.invalidate();
|
|
2929
|
-
}
|
|
2930
3449
|
finally {
|
|
2931
3450
|
this.fetchResponseLoaded = () => { };
|
|
2932
3451
|
}
|
|
2933
3452
|
}
|
|
2934
3453
|
elementAppearedInViewport(element) {
|
|
3454
|
+
this.proposeVisitIfNavigatedWithAction(element, element);
|
|
2935
3455
|
this.loadSourceURL();
|
|
2936
3456
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
return false;
|
|
2940
|
-
}
|
|
2941
|
-
else {
|
|
2942
|
-
return this.shouldInterceptNavigation(element);
|
|
2943
|
-
}
|
|
3457
|
+
willSubmitFormLinkToLocation(link) {
|
|
3458
|
+
return this.shouldInterceptNavigation(link);
|
|
2944
3459
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3460
|
+
submittedFormLinkToLocation(link, _location, form) {
|
|
3461
|
+
const frame = this.findFrameElement(link);
|
|
3462
|
+
if (frame)
|
|
3463
|
+
form.setAttribute("data-turbo-frame", frame.id);
|
|
2948
3464
|
}
|
|
2949
|
-
|
|
2950
|
-
return this.shouldInterceptNavigation(element
|
|
3465
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
|
3466
|
+
return this.shouldInterceptNavigation(element);
|
|
2951
3467
|
}
|
|
2952
|
-
|
|
3468
|
+
linkClickIntercepted(element, location) {
|
|
3469
|
+
this.navigateFrame(element, location);
|
|
3470
|
+
}
|
|
3471
|
+
willSubmitForm(element, submitter) {
|
|
3472
|
+
return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
|
|
3473
|
+
}
|
|
3474
|
+
formSubmitted(element, submitter) {
|
|
2953
3475
|
if (this.formSubmission) {
|
|
2954
3476
|
this.formSubmission.stop();
|
|
2955
3477
|
}
|
|
2956
|
-
this.reloadable = false;
|
|
2957
3478
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
2958
3479
|
const { fetchRequest } = this.formSubmission;
|
|
2959
|
-
this.
|
|
3480
|
+
this.prepareRequest(fetchRequest);
|
|
2960
3481
|
this.formSubmission.start();
|
|
2961
3482
|
}
|
|
2962
|
-
|
|
2963
|
-
|
|
3483
|
+
prepareRequest(request) {
|
|
3484
|
+
var _a;
|
|
3485
|
+
request.headers["Turbo-Frame"] = this.id;
|
|
3486
|
+
if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
|
|
3487
|
+
request.acceptResponseType(StreamMessage.contentType);
|
|
3488
|
+
}
|
|
2964
3489
|
}
|
|
2965
|
-
requestStarted(
|
|
3490
|
+
requestStarted(_request) {
|
|
2966
3491
|
markAsBusy(this.element);
|
|
2967
3492
|
}
|
|
2968
|
-
requestPreventedHandlingResponse(
|
|
3493
|
+
requestPreventedHandlingResponse(_request, _response) {
|
|
2969
3494
|
this.resolveVisitPromise();
|
|
2970
3495
|
}
|
|
2971
3496
|
async requestSucceededWithResponse(request, response) {
|
|
2972
3497
|
await this.loadResponse(response);
|
|
2973
3498
|
this.resolveVisitPromise();
|
|
2974
3499
|
}
|
|
2975
|
-
requestFailedWithResponse(request, response) {
|
|
2976
|
-
|
|
3500
|
+
async requestFailedWithResponse(request, response) {
|
|
3501
|
+
await this.loadResponse(response);
|
|
2977
3502
|
this.resolveVisitPromise();
|
|
2978
3503
|
}
|
|
2979
3504
|
requestErrored(request, error) {
|
|
2980
3505
|
console.error(error);
|
|
2981
3506
|
this.resolveVisitPromise();
|
|
2982
3507
|
}
|
|
2983
|
-
requestFinished(
|
|
3508
|
+
requestFinished(_request) {
|
|
2984
3509
|
clearBusyState(this.element);
|
|
2985
3510
|
}
|
|
2986
3511
|
formSubmissionStarted({ formElement }) {
|
|
@@ -2988,11 +3513,15 @@ class FrameController {
|
|
|
2988
3513
|
}
|
|
2989
3514
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
2990
3515
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
2991
|
-
|
|
3516
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
2992
3517
|
frame.delegate.loadResponse(response);
|
|
3518
|
+
if (!formSubmission.isSafe) {
|
|
3519
|
+
session.clearCache();
|
|
3520
|
+
}
|
|
2993
3521
|
}
|
|
2994
3522
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
2995
3523
|
this.element.delegate.loadResponse(fetchResponse);
|
|
3524
|
+
session.clearCache();
|
|
2996
3525
|
}
|
|
2997
3526
|
formSubmissionErrored(formSubmission, error) {
|
|
2998
3527
|
console.error(error);
|
|
@@ -3000,19 +3529,50 @@ class FrameController {
|
|
|
3000
3529
|
formSubmissionFinished({ formElement }) {
|
|
3001
3530
|
clearBusyState(formElement, this.findFrameElement(formElement));
|
|
3002
3531
|
}
|
|
3003
|
-
allowsImmediateRender(
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3532
|
+
allowsImmediateRender({ element: newFrame }, options) {
|
|
3533
|
+
const event = dispatch("turbo:before-frame-render", {
|
|
3534
|
+
target: this.element,
|
|
3535
|
+
detail: Object.assign({ newFrame }, options),
|
|
3536
|
+
cancelable: true,
|
|
3537
|
+
});
|
|
3538
|
+
const { defaultPrevented, detail: { render }, } = event;
|
|
3539
|
+
if (this.view.renderer && render) {
|
|
3540
|
+
this.view.renderer.renderElement = render;
|
|
3541
|
+
}
|
|
3542
|
+
return !defaultPrevented;
|
|
3543
|
+
}
|
|
3544
|
+
viewRenderedSnapshot(_snapshot, _isPreview) { }
|
|
3545
|
+
preloadOnLoadLinksForView(element) {
|
|
3546
|
+
session.preloadOnLoadLinksForView(element);
|
|
3547
|
+
}
|
|
3548
|
+
viewInvalidated() { }
|
|
3549
|
+
willRenderFrame(currentElement, _newElement) {
|
|
3550
|
+
this.previousFrameElement = currentElement.cloneNode(true);
|
|
3551
|
+
}
|
|
3552
|
+
async loadFrameResponse(fetchResponse, document) {
|
|
3553
|
+
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
|
3554
|
+
if (newFrameElement) {
|
|
3555
|
+
const snapshot = new Snapshot(newFrameElement);
|
|
3556
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
|
3557
|
+
if (this.view.renderPromise)
|
|
3558
|
+
await this.view.renderPromise;
|
|
3559
|
+
this.changeHistory();
|
|
3560
|
+
await this.view.render(renderer);
|
|
3561
|
+
this.complete = true;
|
|
3562
|
+
session.frameRendered(fetchResponse, this.element);
|
|
3563
|
+
session.frameLoaded(this.element);
|
|
3564
|
+
this.fetchResponseLoaded(fetchResponse);
|
|
3565
|
+
}
|
|
3566
|
+
else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
|
|
3567
|
+
this.handleFrameMissingFromResponse(fetchResponse);
|
|
3568
|
+
}
|
|
3009
3569
|
}
|
|
3010
3570
|
async visit(url) {
|
|
3011
3571
|
var _a;
|
|
3012
|
-
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
|
3572
|
+
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
|
|
3013
3573
|
(_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
3014
3574
|
this.currentFetchRequest = request;
|
|
3015
|
-
return new Promise(resolve => {
|
|
3575
|
+
return new Promise((resolve) => {
|
|
3016
3576
|
this.resolveVisitPromise = () => {
|
|
3017
3577
|
this.resolveVisitPromise = () => { };
|
|
3018
3578
|
this.currentFetchRequest = null;
|
|
@@ -3023,24 +3583,78 @@ class FrameController {
|
|
|
3023
3583
|
}
|
|
3024
3584
|
navigateFrame(element, url, submitter) {
|
|
3025
3585
|
const frame = this.findFrameElement(element, submitter);
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3586
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
3587
|
+
this.withCurrentNavigationElement(element, () => {
|
|
3588
|
+
frame.src = url;
|
|
3589
|
+
});
|
|
3029
3590
|
}
|
|
3030
3591
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3031
|
-
|
|
3032
|
-
if (
|
|
3033
|
-
const
|
|
3592
|
+
this.action = getVisitAction(submitter, element, frame);
|
|
3593
|
+
if (this.action) {
|
|
3594
|
+
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
|
3595
|
+
const { visitCachedSnapshot } = frame.delegate;
|
|
3034
3596
|
frame.delegate.fetchResponseLoaded = (fetchResponse) => {
|
|
3035
3597
|
if (frame.src) {
|
|
3036
3598
|
const { statusCode, redirected } = fetchResponse;
|
|
3037
3599
|
const responseHTML = frame.ownerDocument.documentElement.outerHTML;
|
|
3038
3600
|
const response = { statusCode, redirected, responseHTML };
|
|
3039
|
-
|
|
3601
|
+
const options = {
|
|
3602
|
+
response,
|
|
3603
|
+
visitCachedSnapshot,
|
|
3604
|
+
willRender: false,
|
|
3605
|
+
updateHistory: false,
|
|
3606
|
+
restorationIdentifier: this.restorationIdentifier,
|
|
3607
|
+
snapshot: pageSnapshot,
|
|
3608
|
+
};
|
|
3609
|
+
if (this.action)
|
|
3610
|
+
options.action = this.action;
|
|
3611
|
+
session.visit(frame.src, options);
|
|
3040
3612
|
}
|
|
3041
3613
|
};
|
|
3042
3614
|
}
|
|
3043
3615
|
}
|
|
3616
|
+
changeHistory() {
|
|
3617
|
+
if (this.action) {
|
|
3618
|
+
const method = getHistoryMethodForAction(this.action);
|
|
3619
|
+
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
async handleUnvisitableFrameResponse(fetchResponse) {
|
|
3623
|
+
console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
|
|
3624
|
+
await this.visitResponse(fetchResponse.response);
|
|
3625
|
+
}
|
|
3626
|
+
willHandleFrameMissingFromResponse(fetchResponse) {
|
|
3627
|
+
this.element.setAttribute("complete", "");
|
|
3628
|
+
const response = fetchResponse.response;
|
|
3629
|
+
const visit = async (url, options = {}) => {
|
|
3630
|
+
if (url instanceof Response) {
|
|
3631
|
+
this.visitResponse(url);
|
|
3632
|
+
}
|
|
3633
|
+
else {
|
|
3634
|
+
session.visit(url, options);
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
const event = dispatch("turbo:frame-missing", {
|
|
3638
|
+
target: this.element,
|
|
3639
|
+
detail: { response, visit },
|
|
3640
|
+
cancelable: true,
|
|
3641
|
+
});
|
|
3642
|
+
return !event.defaultPrevented;
|
|
3643
|
+
}
|
|
3644
|
+
handleFrameMissingFromResponse(fetchResponse) {
|
|
3645
|
+
this.view.missing();
|
|
3646
|
+
this.throwFrameMissingError(fetchResponse);
|
|
3647
|
+
}
|
|
3648
|
+
throwFrameMissingError(fetchResponse) {
|
|
3649
|
+
const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
|
|
3650
|
+
throw new TurboFrameMissingError(message);
|
|
3651
|
+
}
|
|
3652
|
+
async visitResponse(response) {
|
|
3653
|
+
const wrapped = new FetchResponse(response);
|
|
3654
|
+
const responseHTML = await wrapped.responseHTML;
|
|
3655
|
+
const { location, redirected, statusCode } = wrapped;
|
|
3656
|
+
return session.visit(location, { response: { redirected, statusCode, responseHTML } });
|
|
3657
|
+
}
|
|
3044
3658
|
findFrameElement(element, submitter) {
|
|
3045
3659
|
var _a;
|
|
3046
3660
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
@@ -3050,19 +3664,21 @@ class FrameController {
|
|
|
3050
3664
|
let element;
|
|
3051
3665
|
const id = CSS.escape(this.id);
|
|
3052
3666
|
try {
|
|
3053
|
-
|
|
3667
|
+
element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
|
|
3668
|
+
if (element) {
|
|
3054
3669
|
return element;
|
|
3055
3670
|
}
|
|
3056
|
-
|
|
3671
|
+
element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
|
|
3672
|
+
if (element) {
|
|
3057
3673
|
await element.loaded;
|
|
3058
3674
|
return await this.extractForeignFrameElement(element);
|
|
3059
3675
|
}
|
|
3060
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
|
3061
3676
|
}
|
|
3062
3677
|
catch (error) {
|
|
3063
3678
|
console.error(error);
|
|
3679
|
+
return new FrameElement();
|
|
3064
3680
|
}
|
|
3065
|
-
return
|
|
3681
|
+
return null;
|
|
3066
3682
|
}
|
|
3067
3683
|
formActionIsVisitable(form, submitter) {
|
|
3068
3684
|
const action = getAction(form, submitter);
|
|
@@ -3082,10 +3698,10 @@ class FrameController {
|
|
|
3082
3698
|
return !frameElement.disabled;
|
|
3083
3699
|
}
|
|
3084
3700
|
}
|
|
3085
|
-
if (!session.
|
|
3701
|
+
if (!session.elementIsNavigatable(element)) {
|
|
3086
3702
|
return false;
|
|
3087
3703
|
}
|
|
3088
|
-
if (submitter && !session.
|
|
3704
|
+
if (submitter && !session.elementIsNavigatable(submitter)) {
|
|
3089
3705
|
return false;
|
|
3090
3706
|
}
|
|
3091
3707
|
return true;
|
|
@@ -3101,24 +3717,10 @@ class FrameController {
|
|
|
3101
3717
|
return this.element.src;
|
|
3102
3718
|
}
|
|
3103
3719
|
}
|
|
3104
|
-
get reloadable() {
|
|
3105
|
-
const frame = this.findFrameElement(this.element);
|
|
3106
|
-
return frame.hasAttribute("reloadable");
|
|
3107
|
-
}
|
|
3108
|
-
set reloadable(value) {
|
|
3109
|
-
const frame = this.findFrameElement(this.element);
|
|
3110
|
-
if (value) {
|
|
3111
|
-
frame.setAttribute("reloadable", "");
|
|
3112
|
-
}
|
|
3113
|
-
else {
|
|
3114
|
-
frame.removeAttribute("reloadable");
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
3720
|
set sourceURL(sourceURL) {
|
|
3118
|
-
this.
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
this.settingSourceURL = false;
|
|
3721
|
+
this.ignoringChangesToAttribute("src", () => {
|
|
3722
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
|
3723
|
+
});
|
|
3122
3724
|
}
|
|
3123
3725
|
get loadingStyle() {
|
|
3124
3726
|
return this.element.loading;
|
|
@@ -3126,6 +3728,19 @@ class FrameController {
|
|
|
3126
3728
|
get isLoading() {
|
|
3127
3729
|
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
|
3128
3730
|
}
|
|
3731
|
+
get complete() {
|
|
3732
|
+
return this.element.hasAttribute("complete");
|
|
3733
|
+
}
|
|
3734
|
+
set complete(value) {
|
|
3735
|
+
this.ignoringChangesToAttribute("complete", () => {
|
|
3736
|
+
if (value) {
|
|
3737
|
+
this.element.setAttribute("complete", "");
|
|
3738
|
+
}
|
|
3739
|
+
else {
|
|
3740
|
+
this.element.removeAttribute("complete");
|
|
3741
|
+
}
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3129
3744
|
get isActive() {
|
|
3130
3745
|
return this.element.isActive && this.connected;
|
|
3131
3746
|
}
|
|
@@ -3135,16 +3750,18 @@ class FrameController {
|
|
|
3135
3750
|
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
|
3136
3751
|
return expandURL(root);
|
|
3137
3752
|
}
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
this.
|
|
3753
|
+
isIgnoringChangesTo(attributeName) {
|
|
3754
|
+
return this.ignoredAttributes.has(attributeName);
|
|
3755
|
+
}
|
|
3756
|
+
ignoringChangesToAttribute(attributeName, callback) {
|
|
3757
|
+
this.ignoredAttributes.add(attributeName);
|
|
3758
|
+
callback();
|
|
3759
|
+
this.ignoredAttributes.delete(attributeName);
|
|
3760
|
+
}
|
|
3761
|
+
withCurrentNavigationElement(element, callback) {
|
|
3762
|
+
this.currentNavigationElement = element;
|
|
3763
|
+
callback();
|
|
3764
|
+
delete this.currentNavigationElement;
|
|
3148
3765
|
}
|
|
3149
3766
|
}
|
|
3150
3767
|
function getFrameElementById(id) {
|
|
@@ -3172,36 +3789,10 @@ function activateElement(element, currentURL) {
|
|
|
3172
3789
|
}
|
|
3173
3790
|
}
|
|
3174
3791
|
|
|
3175
|
-
const StreamActions = {
|
|
3176
|
-
after() {
|
|
3177
|
-
this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
|
|
3178
|
-
},
|
|
3179
|
-
append() {
|
|
3180
|
-
this.removeDuplicateTargetChildren();
|
|
3181
|
-
this.targetElements.forEach(e => e.append(this.templateContent));
|
|
3182
|
-
},
|
|
3183
|
-
before() {
|
|
3184
|
-
this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
|
|
3185
|
-
},
|
|
3186
|
-
prepend() {
|
|
3187
|
-
this.removeDuplicateTargetChildren();
|
|
3188
|
-
this.targetElements.forEach(e => e.prepend(this.templateContent));
|
|
3189
|
-
},
|
|
3190
|
-
remove() {
|
|
3191
|
-
this.targetElements.forEach(e => e.remove());
|
|
3192
|
-
},
|
|
3193
|
-
replace() {
|
|
3194
|
-
this.targetElements.forEach(e => e.replaceWith(this.templateContent));
|
|
3195
|
-
},
|
|
3196
|
-
update() {
|
|
3197
|
-
this.targetElements.forEach(e => {
|
|
3198
|
-
e.innerHTML = "";
|
|
3199
|
-
e.append(this.templateContent);
|
|
3200
|
-
});
|
|
3201
|
-
}
|
|
3202
|
-
};
|
|
3203
|
-
|
|
3204
3792
|
class StreamElement extends HTMLElement {
|
|
3793
|
+
static async renderElement(newElement) {
|
|
3794
|
+
await newElement.performAction();
|
|
3795
|
+
}
|
|
3205
3796
|
async connectedCallback() {
|
|
3206
3797
|
try {
|
|
3207
3798
|
await this.render();
|
|
@@ -3215,12 +3806,13 @@ class StreamElement extends HTMLElement {
|
|
|
3215
3806
|
}
|
|
3216
3807
|
async render() {
|
|
3217
3808
|
var _a;
|
|
3218
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
|
|
3219
|
-
|
|
3809
|
+
return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
|
|
3810
|
+
const event = this.beforeRenderEvent;
|
|
3811
|
+
if (this.dispatchEvent(event)) {
|
|
3220
3812
|
await nextAnimationFrame();
|
|
3221
|
-
|
|
3813
|
+
await event.detail.render(this);
|
|
3222
3814
|
}
|
|
3223
|
-
})());
|
|
3815
|
+
})()));
|
|
3224
3816
|
}
|
|
3225
3817
|
disconnect() {
|
|
3226
3818
|
try {
|
|
@@ -3229,13 +3821,13 @@ class StreamElement extends HTMLElement {
|
|
|
3229
3821
|
catch (_a) { }
|
|
3230
3822
|
}
|
|
3231
3823
|
removeDuplicateTargetChildren() {
|
|
3232
|
-
this.duplicateChildren.forEach(c => c.remove());
|
|
3824
|
+
this.duplicateChildren.forEach((c) => c.remove());
|
|
3233
3825
|
}
|
|
3234
3826
|
get duplicateChildren() {
|
|
3235
3827
|
var _a;
|
|
3236
|
-
const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
|
|
3237
|
-
const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
|
|
3238
|
-
return existingChildren.filter(c => newChildrenIds.includes(c.id));
|
|
3828
|
+
const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
|
|
3829
|
+
const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
|
|
3830
|
+
return existingChildren.filter((c) => newChildrenIds.includes(c.id));
|
|
3239
3831
|
}
|
|
3240
3832
|
get performAction() {
|
|
3241
3833
|
if (this.action) {
|
|
@@ -3262,7 +3854,12 @@ class StreamElement extends HTMLElement {
|
|
|
3262
3854
|
return this.templateElement.content.cloneNode(true);
|
|
3263
3855
|
}
|
|
3264
3856
|
get templateElement() {
|
|
3265
|
-
if (this.firstElementChild
|
|
3857
|
+
if (this.firstElementChild === null) {
|
|
3858
|
+
const template = this.ownerDocument.createElement("template");
|
|
3859
|
+
this.appendChild(template);
|
|
3860
|
+
return template;
|
|
3861
|
+
}
|
|
3862
|
+
else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
|
3266
3863
|
return this.firstElementChild;
|
|
3267
3864
|
}
|
|
3268
3865
|
this.raise("first child element must be a <template> element");
|
|
@@ -3284,7 +3881,11 @@ class StreamElement extends HTMLElement {
|
|
|
3284
3881
|
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
|
3285
3882
|
}
|
|
3286
3883
|
get beforeRenderEvent() {
|
|
3287
|
-
return new CustomEvent("turbo:before-stream-render", {
|
|
3884
|
+
return new CustomEvent("turbo:before-stream-render", {
|
|
3885
|
+
bubbles: true,
|
|
3886
|
+
cancelable: true,
|
|
3887
|
+
detail: { newStream: this, render: StreamElement.renderElement },
|
|
3888
|
+
});
|
|
3288
3889
|
}
|
|
3289
3890
|
get targetElementsById() {
|
|
3290
3891
|
var _a;
|
|
@@ -3308,9 +3909,35 @@ class StreamElement extends HTMLElement {
|
|
|
3308
3909
|
}
|
|
3309
3910
|
}
|
|
3310
3911
|
|
|
3912
|
+
class StreamSourceElement extends HTMLElement {
|
|
3913
|
+
constructor() {
|
|
3914
|
+
super(...arguments);
|
|
3915
|
+
this.streamSource = null;
|
|
3916
|
+
}
|
|
3917
|
+
connectedCallback() {
|
|
3918
|
+
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
|
3919
|
+
connectStreamSource(this.streamSource);
|
|
3920
|
+
}
|
|
3921
|
+
disconnectedCallback() {
|
|
3922
|
+
if (this.streamSource) {
|
|
3923
|
+
disconnectStreamSource(this.streamSource);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
get src() {
|
|
3927
|
+
return this.getAttribute("src") || "";
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3311
3931
|
FrameElement.delegateConstructor = FrameController;
|
|
3312
|
-
customElements.
|
|
3313
|
-
customElements.define("turbo-
|
|
3932
|
+
if (customElements.get("turbo-frame") === undefined) {
|
|
3933
|
+
customElements.define("turbo-frame", FrameElement);
|
|
3934
|
+
}
|
|
3935
|
+
if (customElements.get("turbo-stream") === undefined) {
|
|
3936
|
+
customElements.define("turbo-stream", StreamElement);
|
|
3937
|
+
}
|
|
3938
|
+
if (customElements.get("turbo-stream-source") === undefined) {
|
|
3939
|
+
customElements.define("turbo-stream-source", StreamSourceElement);
|
|
3940
|
+
}
|
|
3314
3941
|
|
|
3315
3942
|
(() => {
|
|
3316
3943
|
let element = document.currentScript;
|
|
@@ -3318,7 +3945,8 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3318
3945
|
return;
|
|
3319
3946
|
if (element.hasAttribute("data-turbo-suppress-warning"))
|
|
3320
3947
|
return;
|
|
3321
|
-
|
|
3948
|
+
element = element.parentElement;
|
|
3949
|
+
while (element) {
|
|
3322
3950
|
if (element == document.body) {
|
|
3323
3951
|
return console.warn(unindent `
|
|
3324
3952
|
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
|
@@ -3331,10 +3959,11 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3331
3959
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
3332
3960
|
`, element.outerHTML);
|
|
3333
3961
|
}
|
|
3962
|
+
element = element.parentElement;
|
|
3334
3963
|
}
|
|
3335
3964
|
})();
|
|
3336
3965
|
|
|
3337
3966
|
window.Turbo = Turbo;
|
|
3338
3967
|
start();
|
|
3339
3968
|
|
|
3340
|
-
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
|
|
3969
|
+
export { FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
|