@hotwired/turbo 7.0.0-beta.3 → 7.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/turbo.es2017-esm.js +647 -574
- package/dist/turbo.es2017-esm.js.map +1 -1
- package/dist/turbo.es2017-umd.js +647 -574
- package/dist/turbo.es2017-umd.js.map +1 -1
- package/dist/turbo.es5-umd.js +1153 -843
- package/dist/turbo.es5-umd.js.map +1 -1
- package/dist/types/core/drive/error_renderer.d.ts +7 -11
- package/dist/types/core/drive/form_submission.d.ts +11 -3
- package/dist/types/core/drive/head_snapshot.d.ts +21 -0
- package/dist/types/core/drive/history.d.ts +5 -6
- package/dist/types/core/drive/navigator.d.ts +7 -7
- package/dist/types/core/drive/page_renderer.d.ts +26 -0
- package/dist/types/core/drive/page_snapshot.d.ts +17 -0
- package/dist/types/core/drive/page_view.d.ts +21 -0
- package/dist/types/core/drive/snapshot_cache.d.ts +8 -9
- package/dist/types/core/drive/visit.d.ts +13 -15
- package/dist/types/core/frames/frame_controller.d.ts +11 -10
- package/dist/types/core/frames/frame_renderer.d.ts +8 -0
- package/dist/types/core/frames/frame_view.d.ts +7 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/native/adapter.d.ts +1 -2
- package/dist/types/core/native/browser_adapter.d.ts +1 -2
- package/dist/types/core/renderer.d.ts +26 -0
- package/dist/types/core/session.d.ts +23 -21
- package/dist/types/core/snapshot.d.ts +11 -0
- package/dist/types/core/streams/stream_message.d.ts +1 -0
- package/dist/types/core/url.d.ts +7 -0
- package/dist/types/core/view.d.ts +27 -0
- package/dist/types/http/fetch_request.d.ts +8 -11
- package/dist/types/http/fetch_response.d.ts +1 -2
- package/dist/types/observers/link_click_observer.d.ts +3 -4
- package/dist/types/observers/stream_observer.d.ts +0 -1
- package/dist/types/tests/functional/form_submission_tests.d.ts +11 -3
- package/dist/types/tests/functional/frame_tests.d.ts +4 -0
- package/dist/types/tests/functional/navigation_tests.d.ts +1 -0
- package/dist/types/tests/functional/rendering_tests.d.ts +2 -0
- package/dist/types/tests/functional/stream_tests.d.ts +0 -2
- package/dist/types/tests/helpers/functional_test_case.d.ts +3 -0
- package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +21 -0
- package/dist/types/tests/unit/index.d.ts +1 -0
- package/dist/types/util.d.ts +2 -0
- package/package.json +1 -1
- package/dist/types/core/drive/head_details.d.ts +0 -22
- package/dist/types/core/drive/renderer.d.ts +0 -13
- package/dist/types/core/drive/snapshot.d.ts +0 -24
- package/dist/types/core/drive/snapshot_renderer.d.ts +0 -43
- package/dist/types/core/drive/view.d.ts +0 -34
- package/dist/types/core/location.d.ts +0 -22
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.0.0-beta.
|
|
2
|
+
Turbo 7.0.0-beta.4
|
|
3
3
|
Copyright © 2021 Basecamp, LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -141,80 +141,53 @@ Copyright © 2021 Basecamp, LLC
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
else {
|
|
154
|
-
this.requestURL = this.absoluteURL.slice(0, -anchorLength);
|
|
155
|
-
this.anchor = linkWithAnchor.hash.slice(1);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
static get currentLocation() {
|
|
159
|
-
return this.wrap(window.location.toString());
|
|
160
|
-
}
|
|
161
|
-
static wrap(locatable) {
|
|
162
|
-
if (typeof locatable == "string") {
|
|
163
|
-
return new this(locatable);
|
|
164
|
-
}
|
|
165
|
-
else if (locatable != null) {
|
|
166
|
-
return locatable;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
getOrigin() {
|
|
170
|
-
return this.absoluteURL.split("/", 3).join("/");
|
|
171
|
-
}
|
|
172
|
-
getPath() {
|
|
173
|
-
return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
|
|
174
|
-
}
|
|
175
|
-
getPathComponents() {
|
|
176
|
-
return this.getPath().split("/").slice(1);
|
|
177
|
-
}
|
|
178
|
-
getLastPathComponent() {
|
|
179
|
-
return this.getPathComponents().slice(-1)[0];
|
|
180
|
-
}
|
|
181
|
-
getExtension() {
|
|
182
|
-
return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
|
|
183
|
-
}
|
|
184
|
-
isHTML() {
|
|
185
|
-
return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
|
|
186
|
-
}
|
|
187
|
-
isPrefixedBy(location) {
|
|
188
|
-
const prefixURL = getPrefixURL(location);
|
|
189
|
-
return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
|
|
190
|
-
}
|
|
191
|
-
isEqualTo(location) {
|
|
192
|
-
return location && this.absoluteURL === location.absoluteURL;
|
|
144
|
+
function expandURL(locatable) {
|
|
145
|
+
const anchor = document.createElement("a");
|
|
146
|
+
anchor.href = locatable.toString();
|
|
147
|
+
return new URL(anchor.href);
|
|
148
|
+
}
|
|
149
|
+
function getAnchor(url) {
|
|
150
|
+
let anchorMatch;
|
|
151
|
+
if (url.hash) {
|
|
152
|
+
return url.hash.slice(1);
|
|
193
153
|
}
|
|
194
|
-
|
|
195
|
-
return
|
|
154
|
+
else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
|
155
|
+
return anchorMatch[1];
|
|
196
156
|
}
|
|
197
|
-
|
|
198
|
-
return
|
|
157
|
+
else {
|
|
158
|
+
return "";
|
|
199
159
|
}
|
|
200
|
-
|
|
201
|
-
|
|
160
|
+
}
|
|
161
|
+
function getExtension(url) {
|
|
162
|
+
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
163
|
+
}
|
|
164
|
+
function isHTML(url) {
|
|
165
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
|
166
|
+
}
|
|
167
|
+
function isPrefixedBy(baseURL, url) {
|
|
168
|
+
const prefix = getPrefix(url);
|
|
169
|
+
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
|
170
|
+
}
|
|
171
|
+
function toCacheKey(url) {
|
|
172
|
+
const anchorLength = url.hash.length;
|
|
173
|
+
if (anchorLength < 2) {
|
|
174
|
+
return url.href;
|
|
202
175
|
}
|
|
203
|
-
|
|
204
|
-
return
|
|
176
|
+
else {
|
|
177
|
+
return url.href.slice(0, -anchorLength);
|
|
205
178
|
}
|
|
206
179
|
}
|
|
207
|
-
function
|
|
208
|
-
return
|
|
180
|
+
function getPathComponents(url) {
|
|
181
|
+
return url.pathname.split("/").slice(1);
|
|
209
182
|
}
|
|
210
|
-
function
|
|
211
|
-
return
|
|
183
|
+
function getLastPathComponent(url) {
|
|
184
|
+
return getPathComponents(url).slice(-1)[0];
|
|
212
185
|
}
|
|
213
|
-
function
|
|
214
|
-
return
|
|
186
|
+
function getPrefix(url) {
|
|
187
|
+
return addTrailingSlash(url.origin + url.pathname);
|
|
215
188
|
}
|
|
216
|
-
function
|
|
217
|
-
return
|
|
189
|
+
function addTrailingSlash(value) {
|
|
190
|
+
return value.endsWith("/") ? value : value + "/";
|
|
218
191
|
}
|
|
219
192
|
|
|
220
193
|
class FetchResponse {
|
|
@@ -237,7 +210,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
237
210
|
return this.response.redirected;
|
|
238
211
|
}
|
|
239
212
|
get location() {
|
|
240
|
-
return
|
|
213
|
+
return expandURL(this.response.url);
|
|
241
214
|
}
|
|
242
215
|
get isHTML() {
|
|
243
216
|
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
|
@@ -272,9 +245,15 @@ Copyright © 2021 Basecamp, LLC
|
|
|
272
245
|
function nextAnimationFrame() {
|
|
273
246
|
return new Promise(resolve => requestAnimationFrame(() => resolve()));
|
|
274
247
|
}
|
|
248
|
+
function nextEventLoopTick() {
|
|
249
|
+
return new Promise(resolve => setTimeout(() => resolve(), 0));
|
|
250
|
+
}
|
|
275
251
|
function nextMicrotask() {
|
|
276
252
|
return Promise.resolve();
|
|
277
253
|
}
|
|
254
|
+
function parseHTMLDocument(html = "") {
|
|
255
|
+
return new DOMParser().parseFromString(html, "text/html");
|
|
256
|
+
}
|
|
278
257
|
function unindent(strings, ...values) {
|
|
279
258
|
const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
|
|
280
259
|
const match = lines[0].match(/^\s+/);
|
|
@@ -322,28 +301,23 @@ Copyright © 2021 Basecamp, LLC
|
|
|
322
301
|
}
|
|
323
302
|
}
|
|
324
303
|
class FetchRequest {
|
|
325
|
-
constructor(delegate, method, location, body) {
|
|
304
|
+
constructor(delegate, method, location, body = new URLSearchParams) {
|
|
326
305
|
this.abortController = new AbortController;
|
|
327
306
|
this.delegate = delegate;
|
|
328
307
|
this.method = method;
|
|
329
|
-
this.
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
get url() {
|
|
333
|
-
const url = this.location.absoluteURL;
|
|
334
|
-
const query = this.params.toString();
|
|
335
|
-
if (this.isIdempotent && query.length) {
|
|
336
|
-
return [url, query].join(url.includes("?") ? "&" : "?");
|
|
308
|
+
if (this.isIdempotent) {
|
|
309
|
+
this.url = mergeFormDataEntries(location, [...body.entries()]);
|
|
337
310
|
}
|
|
338
311
|
else {
|
|
339
|
-
|
|
312
|
+
this.body = body;
|
|
313
|
+
this.url = location;
|
|
340
314
|
}
|
|
341
315
|
}
|
|
316
|
+
get location() {
|
|
317
|
+
return this.url;
|
|
318
|
+
}
|
|
342
319
|
get params() {
|
|
343
|
-
return this.
|
|
344
|
-
params.append(name, value.toString());
|
|
345
|
-
return params;
|
|
346
|
-
}, new URLSearchParams);
|
|
320
|
+
return this.url.searchParams;
|
|
347
321
|
}
|
|
348
322
|
get entries() {
|
|
349
323
|
return this.body ? Array.from(this.body.entries()) : [];
|
|
@@ -356,7 +330,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
356
330
|
dispatch("turbo:before-fetch-request", { detail: { fetchOptions } });
|
|
357
331
|
try {
|
|
358
332
|
this.delegate.requestStarted(this);
|
|
359
|
-
const response = await fetch(this.url, fetchOptions);
|
|
333
|
+
const response = await fetch(this.url.href, fetchOptions);
|
|
360
334
|
return await this.receive(response);
|
|
361
335
|
}
|
|
362
336
|
catch (error) {
|
|
@@ -387,7 +361,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
387
361
|
credentials: "same-origin",
|
|
388
362
|
headers: this.headers,
|
|
389
363
|
redirect: "follow",
|
|
390
|
-
body: this.
|
|
364
|
+
body: this.body,
|
|
391
365
|
signal: this.abortSignal
|
|
392
366
|
};
|
|
393
367
|
}
|
|
@@ -395,19 +369,35 @@ Copyright © 2021 Basecamp, LLC
|
|
|
395
369
|
return this.method == FetchMethod.get;
|
|
396
370
|
}
|
|
397
371
|
get headers() {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (typeof this.delegate.additionalHeadersForRequest == "function") {
|
|
402
|
-
return this.delegate.additionalHeadersForRequest(this);
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
return {};
|
|
372
|
+
const headers = Object.assign({}, this.defaultHeaders);
|
|
373
|
+
if (typeof this.delegate.prepareHeadersForRequest == "function") {
|
|
374
|
+
this.delegate.prepareHeadersForRequest(headers, this);
|
|
406
375
|
}
|
|
376
|
+
return headers;
|
|
407
377
|
}
|
|
408
378
|
get abortSignal() {
|
|
409
379
|
return this.abortController.signal;
|
|
410
380
|
}
|
|
381
|
+
get defaultHeaders() {
|
|
382
|
+
return {
|
|
383
|
+
"Accept": "text/html, application/xhtml+xml"
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function mergeFormDataEntries(url, entries) {
|
|
388
|
+
const currentSearchParams = new URLSearchParams(url.search);
|
|
389
|
+
for (const [name, value] of entries) {
|
|
390
|
+
if (value instanceof File)
|
|
391
|
+
continue;
|
|
392
|
+
if (currentSearchParams.has(name)) {
|
|
393
|
+
currentSearchParams.delete(name);
|
|
394
|
+
url.searchParams.set(name, value);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
url.searchParams.append(name, value);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return url;
|
|
411
401
|
}
|
|
412
402
|
|
|
413
403
|
class AppearanceObserver {
|
|
@@ -437,6 +427,42 @@ Copyright © 2021 Basecamp, LLC
|
|
|
437
427
|
}
|
|
438
428
|
}
|
|
439
429
|
|
|
430
|
+
class StreamMessage {
|
|
431
|
+
constructor(html) {
|
|
432
|
+
this.templateElement = document.createElement("template");
|
|
433
|
+
this.templateElement.innerHTML = html;
|
|
434
|
+
}
|
|
435
|
+
static wrap(message) {
|
|
436
|
+
if (typeof message == "string") {
|
|
437
|
+
return new this(message);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
return message;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
get fragment() {
|
|
444
|
+
const fragment = document.createDocumentFragment();
|
|
445
|
+
for (const element of this.foreignElements) {
|
|
446
|
+
fragment.appendChild(document.importNode(element, true));
|
|
447
|
+
}
|
|
448
|
+
return fragment;
|
|
449
|
+
}
|
|
450
|
+
get foreignElements() {
|
|
451
|
+
return this.templateChildren.reduce((streamElements, child) => {
|
|
452
|
+
if (child.tagName.toLowerCase() == "turbo-stream") {
|
|
453
|
+
return [...streamElements, child];
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
return streamElements;
|
|
457
|
+
}
|
|
458
|
+
}, []);
|
|
459
|
+
}
|
|
460
|
+
get templateChildren() {
|
|
461
|
+
return Array.from(this.templateElement.content.children);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
|
465
|
+
|
|
440
466
|
var FormSubmissionState;
|
|
441
467
|
(function (FormSubmissionState) {
|
|
442
468
|
FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
|
|
@@ -446,14 +472,27 @@ Copyright © 2021 Basecamp, LLC
|
|
|
446
472
|
FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
|
|
447
473
|
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
|
448
474
|
})(FormSubmissionState || (FormSubmissionState = {}));
|
|
475
|
+
var FormEnctype;
|
|
476
|
+
(function (FormEnctype) {
|
|
477
|
+
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
|
|
478
|
+
FormEnctype["multipart"] = "multipart/form-data";
|
|
479
|
+
FormEnctype["plain"] = "text/plain";
|
|
480
|
+
})(FormEnctype || (FormEnctype = {}));
|
|
481
|
+
function formEnctypeFromString(encoding) {
|
|
482
|
+
switch (encoding.toLowerCase()) {
|
|
483
|
+
case FormEnctype.multipart: return FormEnctype.multipart;
|
|
484
|
+
case FormEnctype.plain: return FormEnctype.plain;
|
|
485
|
+
default: return FormEnctype.urlEncoded;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
449
488
|
class FormSubmission {
|
|
450
489
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
|
451
490
|
this.state = FormSubmissionState.initialized;
|
|
452
491
|
this.delegate = delegate;
|
|
453
492
|
this.formElement = formElement;
|
|
454
|
-
this.formData = buildFormData(formElement, submitter);
|
|
455
493
|
this.submitter = submitter;
|
|
456
|
-
this.
|
|
494
|
+
this.formData = buildFormData(formElement, submitter);
|
|
495
|
+
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
|
|
457
496
|
this.mustRedirect = mustRedirect;
|
|
458
497
|
}
|
|
459
498
|
get method() {
|
|
@@ -466,7 +505,24 @@ Copyright © 2021 Basecamp, LLC
|
|
|
466
505
|
return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
|
|
467
506
|
}
|
|
468
507
|
get location() {
|
|
469
|
-
return
|
|
508
|
+
return expandURL(this.action);
|
|
509
|
+
}
|
|
510
|
+
get body() {
|
|
511
|
+
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
|
512
|
+
return new URLSearchParams(this.stringFormData);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
return this.formData;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
get enctype() {
|
|
519
|
+
var _a;
|
|
520
|
+
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
|
521
|
+
}
|
|
522
|
+
get stringFormData() {
|
|
523
|
+
return [...this.formData].reduce((entries, [name, value]) => {
|
|
524
|
+
return entries.concat(typeof value == "string" ? [[name, value]] : []);
|
|
525
|
+
}, []);
|
|
470
526
|
}
|
|
471
527
|
async start() {
|
|
472
528
|
const { initialized, requesting } = FormSubmissionState;
|
|
@@ -483,15 +539,14 @@ Copyright © 2021 Basecamp, LLC
|
|
|
483
539
|
return true;
|
|
484
540
|
}
|
|
485
541
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (this.method != FetchMethod.get) {
|
|
542
|
+
prepareHeadersForRequest(headers, request) {
|
|
543
|
+
if (!request.isIdempotent) {
|
|
489
544
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
|
490
545
|
if (token) {
|
|
491
546
|
headers["X-CSRF-Token"] = token;
|
|
492
547
|
}
|
|
548
|
+
headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
|
|
493
549
|
}
|
|
494
|
-
return headers;
|
|
495
550
|
}
|
|
496
551
|
requestStarted(request) {
|
|
497
552
|
this.state = FormSubmissionState.waiting;
|
|
@@ -559,6 +614,38 @@ Copyright © 2021 Basecamp, LLC
|
|
|
559
614
|
return response.statusCode == 200 && !response.redirected;
|
|
560
615
|
}
|
|
561
616
|
|
|
617
|
+
class Snapshot {
|
|
618
|
+
constructor(element) {
|
|
619
|
+
this.element = element;
|
|
620
|
+
}
|
|
621
|
+
get children() {
|
|
622
|
+
return [...this.element.children];
|
|
623
|
+
}
|
|
624
|
+
hasAnchor(anchor) {
|
|
625
|
+
return this.getElementForAnchor(anchor) != null;
|
|
626
|
+
}
|
|
627
|
+
getElementForAnchor(anchor) {
|
|
628
|
+
try {
|
|
629
|
+
return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
|
630
|
+
}
|
|
631
|
+
catch (_a) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
get firstAutofocusableElement() {
|
|
636
|
+
return this.element.querySelector("[autofocus]");
|
|
637
|
+
}
|
|
638
|
+
get permanentElements() {
|
|
639
|
+
return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
|
|
640
|
+
}
|
|
641
|
+
getPermanentElementById(id) {
|
|
642
|
+
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
|
|
643
|
+
}
|
|
644
|
+
getPermanentElementsPresentInSnapshot(snapshot) {
|
|
645
|
+
return this.permanentElements.filter(({ id }) => snapshot.getPermanentElementById(id));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
562
649
|
class FormInterceptor {
|
|
563
650
|
constructor(delegate, element) {
|
|
564
651
|
this.submitBubbled = ((event) => {
|
|
@@ -583,6 +670,83 @@ Copyright © 2021 Basecamp, LLC
|
|
|
583
670
|
}
|
|
584
671
|
}
|
|
585
672
|
|
|
673
|
+
class View {
|
|
674
|
+
constructor(delegate, element) {
|
|
675
|
+
this.delegate = delegate;
|
|
676
|
+
this.element = element;
|
|
677
|
+
}
|
|
678
|
+
scrollToAnchor(anchor) {
|
|
679
|
+
const element = this.snapshot.getElementForAnchor(anchor);
|
|
680
|
+
if (element) {
|
|
681
|
+
this.scrollToElement(element);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
this.scrollToPosition({ x: 0, y: 0 });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
scrollToElement(element) {
|
|
688
|
+
element.scrollIntoView();
|
|
689
|
+
}
|
|
690
|
+
scrollToPosition({ x, y }) {
|
|
691
|
+
this.scrollRoot.scrollTo(x, y);
|
|
692
|
+
}
|
|
693
|
+
get scrollRoot() {
|
|
694
|
+
return window;
|
|
695
|
+
}
|
|
696
|
+
async render(renderer) {
|
|
697
|
+
if (this.renderer) {
|
|
698
|
+
throw new Error("rendering is already in progress");
|
|
699
|
+
}
|
|
700
|
+
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
|
|
701
|
+
if (shouldRender) {
|
|
702
|
+
try {
|
|
703
|
+
this.renderer = renderer;
|
|
704
|
+
this.prepareToRenderSnapshot(renderer);
|
|
705
|
+
this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
|
|
706
|
+
await this.renderSnapshot(renderer);
|
|
707
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
|
708
|
+
this.finishRenderingSnapshot(renderer);
|
|
709
|
+
}
|
|
710
|
+
finally {
|
|
711
|
+
delete this.renderer;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
this.invalidate();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
invalidate() {
|
|
719
|
+
this.delegate.viewInvalidated();
|
|
720
|
+
}
|
|
721
|
+
prepareToRenderSnapshot(renderer) {
|
|
722
|
+
this.markAsPreview(renderer.isPreview);
|
|
723
|
+
renderer.prepareToRender();
|
|
724
|
+
}
|
|
725
|
+
markAsPreview(isPreview) {
|
|
726
|
+
if (isPreview) {
|
|
727
|
+
this.element.setAttribute("data-turbo-preview", "");
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
this.element.removeAttribute("data-turbo-preview");
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async renderSnapshot(renderer) {
|
|
734
|
+
await renderer.render();
|
|
735
|
+
}
|
|
736
|
+
finishRenderingSnapshot(renderer) {
|
|
737
|
+
renderer.finishRendering();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
class FrameView extends View {
|
|
742
|
+
invalidate() {
|
|
743
|
+
this.element.innerHTML = "";
|
|
744
|
+
}
|
|
745
|
+
get snapshot() {
|
|
746
|
+
return new Snapshot(this.element);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
586
750
|
class LinkInterceptor {
|
|
587
751
|
constructor(delegate, element) {
|
|
588
752
|
this.clickBubbled = (event) => {
|
|
@@ -629,10 +793,147 @@ Copyright © 2021 Basecamp, LLC
|
|
|
629
793
|
}
|
|
630
794
|
}
|
|
631
795
|
|
|
796
|
+
class Renderer {
|
|
797
|
+
constructor(currentSnapshot, newSnapshot, isPreview) {
|
|
798
|
+
this.currentSnapshot = currentSnapshot;
|
|
799
|
+
this.newSnapshot = newSnapshot;
|
|
800
|
+
this.isPreview = isPreview;
|
|
801
|
+
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
|
|
802
|
+
}
|
|
803
|
+
get shouldRender() {
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
prepareToRender() {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
finishRendering() {
|
|
810
|
+
if (this.resolvingFunctions) {
|
|
811
|
+
this.resolvingFunctions.resolve();
|
|
812
|
+
delete this.resolvingFunctions;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
createScriptElement(element) {
|
|
816
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
|
817
|
+
return element;
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
const createdScriptElement = document.createElement("script");
|
|
821
|
+
createdScriptElement.textContent = element.textContent;
|
|
822
|
+
createdScriptElement.async = false;
|
|
823
|
+
copyElementAttributes(createdScriptElement, element);
|
|
824
|
+
return createdScriptElement;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
preservingPermanentElements(callback) {
|
|
828
|
+
const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
|
|
829
|
+
callback();
|
|
830
|
+
replacePlaceholderElementsWithClonedPermanentElements(placeholders);
|
|
831
|
+
}
|
|
832
|
+
focusFirstAutofocusableElement() {
|
|
833
|
+
const element = this.newSnapshot.firstAutofocusableElement;
|
|
834
|
+
if (elementIsFocusable(element)) {
|
|
835
|
+
element.focus();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
get currentElement() {
|
|
839
|
+
return this.currentSnapshot.element;
|
|
840
|
+
}
|
|
841
|
+
get newElement() {
|
|
842
|
+
return this.newSnapshot.element;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function replaceElementWithElement(fromElement, toElement) {
|
|
846
|
+
const parentElement = fromElement.parentElement;
|
|
847
|
+
if (parentElement) {
|
|
848
|
+
return parentElement.replaceChild(toElement, fromElement);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
|
852
|
+
for (const { name, value } of [...sourceElement.attributes]) {
|
|
853
|
+
destinationElement.setAttribute(name, value);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function createPlaceholderForPermanentElement(permanentElement) {
|
|
857
|
+
const element = document.createElement("meta");
|
|
858
|
+
element.setAttribute("name", "turbo-permanent-placeholder");
|
|
859
|
+
element.setAttribute("content", permanentElement.id);
|
|
860
|
+
return { element, permanentElement };
|
|
861
|
+
}
|
|
862
|
+
function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
|
863
|
+
for (const { element, permanentElement } of placeholders) {
|
|
864
|
+
const clonedElement = permanentElement.cloneNode(true);
|
|
865
|
+
replaceElementWithElement(element, clonedElement);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function relocatePermanentElements(currentSnapshot, newSnapshot) {
|
|
869
|
+
return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce((placeholders, permanentElement) => {
|
|
870
|
+
const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
|
|
871
|
+
if (newElement) {
|
|
872
|
+
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
|
873
|
+
replaceElementWithElement(permanentElement, placeholder.element);
|
|
874
|
+
replaceElementWithElement(newElement, permanentElement);
|
|
875
|
+
return [...placeholders, placeholder];
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
return placeholders;
|
|
879
|
+
}
|
|
880
|
+
}, []);
|
|
881
|
+
}
|
|
882
|
+
function elementIsFocusable(element) {
|
|
883
|
+
return element && typeof element.focus == "function";
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
class FrameRenderer extends Renderer {
|
|
887
|
+
get shouldRender() {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
async render() {
|
|
891
|
+
await nextAnimationFrame();
|
|
892
|
+
this.preservingPermanentElements(() => {
|
|
893
|
+
this.loadFrameElement();
|
|
894
|
+
});
|
|
895
|
+
this.scrollFrameIntoView();
|
|
896
|
+
await nextAnimationFrame();
|
|
897
|
+
this.focusFirstAutofocusableElement();
|
|
898
|
+
}
|
|
899
|
+
loadFrameElement() {
|
|
900
|
+
var _a;
|
|
901
|
+
const destinationRange = document.createRange();
|
|
902
|
+
destinationRange.selectNodeContents(this.currentElement);
|
|
903
|
+
destinationRange.deleteContents();
|
|
904
|
+
const frameElement = this.newElement;
|
|
905
|
+
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
906
|
+
if (sourceRange) {
|
|
907
|
+
sourceRange.selectNodeContents(frameElement);
|
|
908
|
+
this.currentElement.appendChild(sourceRange.extractContents());
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
scrollFrameIntoView() {
|
|
912
|
+
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
|
913
|
+
const element = this.currentElement.firstElementChild;
|
|
914
|
+
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
|
915
|
+
if (element) {
|
|
916
|
+
element.scrollIntoView({ block });
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function readScrollLogicalPosition(value, defaultValue) {
|
|
924
|
+
if (value == "end" || value == "start" || value == "center" || value == "nearest") {
|
|
925
|
+
return value;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
return defaultValue;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
632
932
|
class FrameController {
|
|
633
933
|
constructor(element) {
|
|
634
934
|
this.resolveVisitPromise = () => { };
|
|
635
935
|
this.element = element;
|
|
936
|
+
this.view = new FrameView(this, this.element);
|
|
636
937
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
|
637
938
|
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
|
638
939
|
this.formInterceptor = new FormInterceptor(this, this.element);
|
|
@@ -677,14 +978,18 @@ Copyright © 2021 Basecamp, LLC
|
|
|
677
978
|
}
|
|
678
979
|
}
|
|
679
980
|
async loadResponse(response) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
981
|
+
try {
|
|
982
|
+
const html = await response.responseHTML;
|
|
983
|
+
if (html) {
|
|
984
|
+
const { body } = parseHTMLDocument(html);
|
|
985
|
+
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
|
986
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
|
987
|
+
await this.view.render(renderer);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
console.error(error);
|
|
992
|
+
this.view.invalidate();
|
|
688
993
|
}
|
|
689
994
|
}
|
|
690
995
|
elementAppearedInViewport(element) {
|
|
@@ -705,14 +1010,14 @@ Copyright © 2021 Basecamp, LLC
|
|
|
705
1010
|
}
|
|
706
1011
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
707
1012
|
if (this.formSubmission.fetchRequest.isIdempotent) {
|
|
708
|
-
this.navigateFrame(element, this.formSubmission.fetchRequest.url);
|
|
1013
|
+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
|
|
709
1014
|
}
|
|
710
1015
|
else {
|
|
711
1016
|
this.formSubmission.start();
|
|
712
1017
|
}
|
|
713
1018
|
}
|
|
714
|
-
|
|
715
|
-
|
|
1019
|
+
prepareHeadersForRequest(headers, request) {
|
|
1020
|
+
headers["Turbo-Frame"] = this.id;
|
|
716
1021
|
}
|
|
717
1022
|
requestStarted(request) {
|
|
718
1023
|
this.element.setAttribute("busy", "");
|
|
@@ -748,9 +1053,14 @@ Copyright © 2021 Basecamp, LLC
|
|
|
748
1053
|
}
|
|
749
1054
|
formSubmissionFinished(formSubmission) {
|
|
750
1055
|
}
|
|
1056
|
+
viewWillRenderSnapshot(snapshot, isPreview) {
|
|
1057
|
+
}
|
|
1058
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
|
1059
|
+
}
|
|
1060
|
+
viewInvalidated() {
|
|
1061
|
+
}
|
|
751
1062
|
async visit(url) {
|
|
752
|
-
const
|
|
753
|
-
const request = new FetchRequest(this, FetchMethod.get, location);
|
|
1063
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
|
754
1064
|
return new Promise(resolve => {
|
|
755
1065
|
this.resolveVisitPromise = () => {
|
|
756
1066
|
this.resolveVisitPromise = () => { };
|
|
@@ -765,7 +1075,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
765
1075
|
}
|
|
766
1076
|
findFrameElement(element) {
|
|
767
1077
|
var _a;
|
|
768
|
-
const id = element.getAttribute("data-turbo-frame");
|
|
1078
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
|
769
1079
|
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
|
770
1080
|
}
|
|
771
1081
|
async extractForeignFrameElement(container) {
|
|
@@ -781,36 +1091,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
781
1091
|
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
|
782
1092
|
return new FrameElement();
|
|
783
1093
|
}
|
|
784
|
-
loadFrameElement(frameElement) {
|
|
785
|
-
var _a;
|
|
786
|
-
const destinationRange = document.createRange();
|
|
787
|
-
destinationRange.selectNodeContents(this.element);
|
|
788
|
-
destinationRange.deleteContents();
|
|
789
|
-
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
790
|
-
if (sourceRange) {
|
|
791
|
-
sourceRange.selectNodeContents(frameElement);
|
|
792
|
-
this.element.appendChild(sourceRange.extractContents());
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
focusFirstAutofocusableElement() {
|
|
796
|
-
const element = this.firstAutofocusableElement;
|
|
797
|
-
if (element) {
|
|
798
|
-
element.focus();
|
|
799
|
-
return true;
|
|
800
|
-
}
|
|
801
|
-
return false;
|
|
802
|
-
}
|
|
803
|
-
scrollFrameIntoView(frame) {
|
|
804
|
-
if (this.element.autoscroll || frame.autoscroll) {
|
|
805
|
-
const element = this.element.firstElementChild;
|
|
806
|
-
const block = readScrollLogicalPosition(this.element.getAttribute("data-autoscroll-block"), "end");
|
|
807
|
-
if (element) {
|
|
808
|
-
element.scrollIntoView({ block });
|
|
809
|
-
return true;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return false;
|
|
813
|
-
}
|
|
814
1094
|
shouldInterceptNavigation(element) {
|
|
815
1095
|
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
|
816
1096
|
if (!this.enabled || id == "_top") {
|
|
@@ -824,10 +1104,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
824
1104
|
}
|
|
825
1105
|
return true;
|
|
826
1106
|
}
|
|
827
|
-
get firstAutofocusableElement() {
|
|
828
|
-
const element = this.element.querySelector("[autofocus]");
|
|
829
|
-
return element instanceof HTMLElement ? element : null;
|
|
830
|
-
}
|
|
831
1107
|
get id() {
|
|
832
1108
|
return this.element.id;
|
|
833
1109
|
}
|
|
@@ -855,20 +1131,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
855
1131
|
}
|
|
856
1132
|
}
|
|
857
1133
|
}
|
|
858
|
-
function readScrollLogicalPosition(value, defaultValue) {
|
|
859
|
-
if (value == "end" || value == "start" || value == "center" || value == "nearest") {
|
|
860
|
-
return value;
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
return defaultValue;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
function fragmentFromHTML(html) {
|
|
867
|
-
if (html) {
|
|
868
|
-
const foreignDocument = document.implementation.createHTMLDocument();
|
|
869
|
-
return foreignDocument.createRange().createContextualFragment(html);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
1134
|
function activateElement(element) {
|
|
873
1135
|
if (element && element.ownerDocument !== document) {
|
|
874
1136
|
element = document.importNode(element, true);
|
|
@@ -1098,9 +1360,10 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1098
1360
|
}
|
|
1099
1361
|
ProgressBar.animationDuration = 300;
|
|
1100
1362
|
|
|
1101
|
-
class
|
|
1102
|
-
constructor(
|
|
1103
|
-
|
|
1363
|
+
class HeadSnapshot extends Snapshot {
|
|
1364
|
+
constructor() {
|
|
1365
|
+
super(...arguments);
|
|
1366
|
+
this.detailsByOuterHTML = this.children.reduce((result, element) => {
|
|
1104
1367
|
const { outerHTML } = element;
|
|
1105
1368
|
const details = outerHTML in result
|
|
1106
1369
|
? result[outerHTML]
|
|
@@ -1112,29 +1375,25 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1112
1375
|
return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
|
|
1113
1376
|
}, {});
|
|
1114
1377
|
}
|
|
1115
|
-
|
|
1116
|
-
const children = headElement ? [...headElement.children] : [];
|
|
1117
|
-
return new this(children);
|
|
1118
|
-
}
|
|
1119
|
-
getTrackedElementSignature() {
|
|
1378
|
+
get trackedElementSignature() {
|
|
1120
1379
|
return Object.keys(this.detailsByOuterHTML)
|
|
1121
1380
|
.filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
|
|
1122
1381
|
.join("");
|
|
1123
1382
|
}
|
|
1124
|
-
|
|
1125
|
-
return this.
|
|
1383
|
+
getScriptElementsNotInSnapshot(snapshot) {
|
|
1384
|
+
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
|
1126
1385
|
}
|
|
1127
|
-
|
|
1128
|
-
return this.
|
|
1386
|
+
getStylesheetElementsNotInSnapshot(snapshot) {
|
|
1387
|
+
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
|
1129
1388
|
}
|
|
1130
|
-
|
|
1389
|
+
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
|
1131
1390
|
return Object.keys(this.detailsByOuterHTML)
|
|
1132
|
-
.filter(outerHTML => !(outerHTML in
|
|
1391
|
+
.filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
|
|
1133
1392
|
.map(outerHTML => this.detailsByOuterHTML[outerHTML])
|
|
1134
1393
|
.filter(({ type }) => type == matchedType)
|
|
1135
1394
|
.map(({ elements: [element] }) => element);
|
|
1136
1395
|
}
|
|
1137
|
-
|
|
1396
|
+
get provisionalElements() {
|
|
1138
1397
|
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
|
|
1139
1398
|
const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];
|
|
1140
1399
|
if (type == null && !tracked) {
|
|
@@ -1184,79 +1443,46 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1184
1443
|
const tagName = element.tagName.toLowerCase();
|
|
1185
1444
|
return tagName == "meta" && element.getAttribute("name") == name;
|
|
1186
1445
|
}
|
|
1187
|
-
|
|
1188
|
-
class Snapshot {
|
|
1189
|
-
constructor(
|
|
1190
|
-
|
|
1191
|
-
this.
|
|
1192
|
-
}
|
|
1193
|
-
static wrap(value) {
|
|
1194
|
-
if (value instanceof this) {
|
|
1195
|
-
return value;
|
|
1196
|
-
}
|
|
1197
|
-
else if (typeof value == "string") {
|
|
1198
|
-
return this.fromHTMLString(value);
|
|
1199
|
-
}
|
|
1200
|
-
else {
|
|
1201
|
-
return this.fromHTMLElement(value);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
static fromHTMLString(html) {
|
|
1205
|
-
const { documentElement } = new DOMParser().parseFromString(html, "text/html");
|
|
1206
|
-
return this.fromHTMLElement(documentElement);
|
|
1207
|
-
}
|
|
1208
|
-
static fromHTMLElement(htmlElement) {
|
|
1209
|
-
const headElement = htmlElement.querySelector("head");
|
|
1210
|
-
const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
|
|
1211
|
-
const headDetails = HeadDetails.fromHeadElement(headElement);
|
|
1212
|
-
return new this(headDetails, bodyElement);
|
|
1213
|
-
}
|
|
1214
|
-
clone() {
|
|
1215
|
-
const { bodyElement } = Snapshot.fromHTMLString(this.bodyElement.outerHTML);
|
|
1216
|
-
return new Snapshot(this.headDetails, bodyElement);
|
|
1217
|
-
}
|
|
1218
|
-
getRootLocation() {
|
|
1219
|
-
const root = this.getSetting("root", "/");
|
|
1220
|
-
return new Location(root);
|
|
1446
|
+
|
|
1447
|
+
class PageSnapshot extends Snapshot {
|
|
1448
|
+
constructor(element, headSnapshot) {
|
|
1449
|
+
super(element);
|
|
1450
|
+
this.headSnapshot = headSnapshot;
|
|
1221
1451
|
}
|
|
1222
|
-
|
|
1223
|
-
return this.
|
|
1452
|
+
static fromHTMLString(html = "") {
|
|
1453
|
+
return this.fromDocument(parseHTMLDocument(html));
|
|
1224
1454
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
|
1228
|
-
}
|
|
1229
|
-
catch (_a) {
|
|
1230
|
-
return null;
|
|
1231
|
-
}
|
|
1455
|
+
static fromElement(element) {
|
|
1456
|
+
return this.fromDocument(element.ownerDocument);
|
|
1232
1457
|
}
|
|
1233
|
-
|
|
1234
|
-
return
|
|
1458
|
+
static fromDocument({ head, body }) {
|
|
1459
|
+
return new this(body, new HeadSnapshot(head));
|
|
1235
1460
|
}
|
|
1236
|
-
|
|
1237
|
-
return this.
|
|
1461
|
+
clone() {
|
|
1462
|
+
return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
|
|
1238
1463
|
}
|
|
1239
|
-
|
|
1240
|
-
return this.
|
|
1464
|
+
get headElement() {
|
|
1465
|
+
return this.headSnapshot.element;
|
|
1241
1466
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1467
|
+
get rootLocation() {
|
|
1468
|
+
var _a;
|
|
1469
|
+
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
|
1470
|
+
return expandURL(root);
|
|
1244
1471
|
}
|
|
1245
|
-
|
|
1246
|
-
return this.
|
|
1472
|
+
get cacheControlValue() {
|
|
1473
|
+
return this.getSetting("cache-control");
|
|
1247
1474
|
}
|
|
1248
|
-
isPreviewable() {
|
|
1249
|
-
return this.
|
|
1475
|
+
get isPreviewable() {
|
|
1476
|
+
return this.cacheControlValue != "no-preview";
|
|
1250
1477
|
}
|
|
1251
|
-
isCacheable() {
|
|
1252
|
-
return this.
|
|
1478
|
+
get isCacheable() {
|
|
1479
|
+
return this.cacheControlValue != "no-cache";
|
|
1253
1480
|
}
|
|
1254
|
-
isVisitable() {
|
|
1481
|
+
get isVisitable() {
|
|
1255
1482
|
return this.getSetting("visit-control") != "reload";
|
|
1256
1483
|
}
|
|
1257
|
-
getSetting(name
|
|
1258
|
-
|
|
1259
|
-
return value == null ? defaultValue : value;
|
|
1484
|
+
getSetting(name) {
|
|
1485
|
+
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
|
1260
1486
|
}
|
|
1261
1487
|
}
|
|
1262
1488
|
|
|
@@ -1294,17 +1520,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1294
1520
|
this.scrolled = false;
|
|
1295
1521
|
this.snapshotCached = false;
|
|
1296
1522
|
this.state = VisitState.initialized;
|
|
1297
|
-
this.performScroll = () => {
|
|
1298
|
-
if (!this.scrolled) {
|
|
1299
|
-
if (this.action == "restore") {
|
|
1300
|
-
this.scrollToRestoredPosition() || this.scrollToTop();
|
|
1301
|
-
}
|
|
1302
|
-
else {
|
|
1303
|
-
this.scrollToAnchor() || this.scrollToTop();
|
|
1304
|
-
}
|
|
1305
|
-
this.scrolled = true;
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
1523
|
this.delegate = delegate;
|
|
1309
1524
|
this.location = location;
|
|
1310
1525
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
@@ -1359,8 +1574,9 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1359
1574
|
}
|
|
1360
1575
|
}
|
|
1361
1576
|
changeHistory() {
|
|
1577
|
+
var _a;
|
|
1362
1578
|
if (!this.historyChanged) {
|
|
1363
|
-
const actionForHistory = this.location.
|
|
1579
|
+
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
|
1364
1580
|
const method = this.getHistoryMethodForAction(actionForHistory);
|
|
1365
1581
|
this.history.update(method, this.location, this.restorationIdentifier);
|
|
1366
1582
|
this.historyChanged = true;
|
|
@@ -1405,15 +1621,15 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1405
1621
|
loadResponse() {
|
|
1406
1622
|
if (this.response) {
|
|
1407
1623
|
const { statusCode, responseHTML } = this.response;
|
|
1408
|
-
this.render(() => {
|
|
1624
|
+
this.render(async () => {
|
|
1409
1625
|
this.cacheSnapshot();
|
|
1410
1626
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1411
|
-
this.view.
|
|
1627
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
|
1412
1628
|
this.adapter.visitRendered(this);
|
|
1413
1629
|
this.complete();
|
|
1414
1630
|
}
|
|
1415
1631
|
else {
|
|
1416
|
-
this.view.
|
|
1632
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
|
1417
1633
|
this.adapter.visitRendered(this);
|
|
1418
1634
|
this.fail();
|
|
1419
1635
|
}
|
|
@@ -1422,15 +1638,15 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1422
1638
|
}
|
|
1423
1639
|
getCachedSnapshot() {
|
|
1424
1640
|
const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
|
|
1425
|
-
if (snapshot && (!this.location
|
|
1426
|
-
if (this.action == "restore" || snapshot.isPreviewable
|
|
1641
|
+
if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
|
|
1642
|
+
if (this.action == "restore" || snapshot.isPreviewable) {
|
|
1427
1643
|
return snapshot;
|
|
1428
1644
|
}
|
|
1429
1645
|
}
|
|
1430
1646
|
}
|
|
1431
1647
|
getPreloadedSnapshot() {
|
|
1432
1648
|
if (this.snapshotHTML) {
|
|
1433
|
-
return
|
|
1649
|
+
return PageSnapshot.fromHTMLString(this.snapshotHTML);
|
|
1434
1650
|
}
|
|
1435
1651
|
}
|
|
1436
1652
|
hasCachedSnapshot() {
|
|
@@ -1440,9 +1656,9 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1440
1656
|
const snapshot = this.getCachedSnapshot();
|
|
1441
1657
|
if (snapshot) {
|
|
1442
1658
|
const isPreview = this.shouldIssueRequest();
|
|
1443
|
-
this.render(() => {
|
|
1659
|
+
this.render(async () => {
|
|
1444
1660
|
this.cacheSnapshot();
|
|
1445
|
-
this.view.
|
|
1661
|
+
await this.view.renderPage(snapshot);
|
|
1446
1662
|
this.adapter.visitRendered(this);
|
|
1447
1663
|
if (!isPreview) {
|
|
1448
1664
|
this.complete();
|
|
@@ -1487,6 +1703,17 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1487
1703
|
requestFinished() {
|
|
1488
1704
|
this.finishRequest();
|
|
1489
1705
|
}
|
|
1706
|
+
performScroll() {
|
|
1707
|
+
if (!this.scrolled) {
|
|
1708
|
+
if (this.action == "restore") {
|
|
1709
|
+
this.scrollToRestoredPosition() || this.scrollToTop();
|
|
1710
|
+
}
|
|
1711
|
+
else {
|
|
1712
|
+
this.scrollToAnchor() || this.scrollToTop();
|
|
1713
|
+
}
|
|
1714
|
+
this.scrolled = true;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1490
1717
|
scrollToRestoredPosition() {
|
|
1491
1718
|
const { scrollPosition } = this.restorationData;
|
|
1492
1719
|
if (scrollPosition) {
|
|
@@ -1495,8 +1722,8 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1495
1722
|
}
|
|
1496
1723
|
}
|
|
1497
1724
|
scrollToAnchor() {
|
|
1498
|
-
if (this.location
|
|
1499
|
-
this.view.scrollToAnchor(this.location
|
|
1725
|
+
if (getAnchor(this.location) != null) {
|
|
1726
|
+
this.view.scrollToAnchor(getAnchor(this.location));
|
|
1500
1727
|
return true;
|
|
1501
1728
|
}
|
|
1502
1729
|
}
|
|
@@ -1530,12 +1757,14 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1530
1757
|
this.snapshotCached = true;
|
|
1531
1758
|
}
|
|
1532
1759
|
}
|
|
1533
|
-
render(callback) {
|
|
1760
|
+
async render(callback) {
|
|
1534
1761
|
this.cancelRender();
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
callback.call(this);
|
|
1762
|
+
await new Promise(resolve => {
|
|
1763
|
+
this.frame = requestAnimationFrame(() => resolve());
|
|
1538
1764
|
});
|
|
1765
|
+
callback();
|
|
1766
|
+
delete this.frame;
|
|
1767
|
+
this.performScroll();
|
|
1539
1768
|
}
|
|
1540
1769
|
cancelRender() {
|
|
1541
1770
|
if (this.frame) {
|
|
@@ -1711,11 +1940,10 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1711
1940
|
if (this.shouldHandlePopState()) {
|
|
1712
1941
|
const { turbo } = event.state || {};
|
|
1713
1942
|
if (turbo) {
|
|
1714
|
-
|
|
1715
|
-
this.location = location;
|
|
1943
|
+
this.location = new URL(window.location.href);
|
|
1716
1944
|
const { restorationIdentifier } = turbo;
|
|
1717
1945
|
this.restorationIdentifier = restorationIdentifier;
|
|
1718
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
|
|
1946
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
|
1719
1947
|
}
|
|
1720
1948
|
}
|
|
1721
1949
|
};
|
|
@@ -1730,7 +1958,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1730
1958
|
addEventListener("popstate", this.onPopState, false);
|
|
1731
1959
|
addEventListener("load", this.onPageLoad, false);
|
|
1732
1960
|
this.started = true;
|
|
1733
|
-
this.replace(
|
|
1961
|
+
this.replace(new URL(window.location.href));
|
|
1734
1962
|
}
|
|
1735
1963
|
}
|
|
1736
1964
|
stop() {
|
|
@@ -1748,7 +1976,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1748
1976
|
}
|
|
1749
1977
|
update(method, location, restorationIdentifier = uuid()) {
|
|
1750
1978
|
const state = { turbo: { restorationIdentifier } };
|
|
1751
|
-
method.call(history, state, "", location.
|
|
1979
|
+
method.call(history, state, "", location.href);
|
|
1752
1980
|
this.location = location;
|
|
1753
1981
|
this.restorationIdentifier = restorationIdentifier;
|
|
1754
1982
|
}
|
|
@@ -1829,7 +2057,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1829
2057
|
}
|
|
1830
2058
|
}
|
|
1831
2059
|
getLocationForLink(link) {
|
|
1832
|
-
return
|
|
2060
|
+
return expandURL(link.getAttribute("href") || "");
|
|
1833
2061
|
}
|
|
1834
2062
|
}
|
|
1835
2063
|
|
|
@@ -1842,15 +2070,20 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1842
2070
|
this.delegate.visitProposedToLocation(location, options);
|
|
1843
2071
|
}
|
|
1844
2072
|
}
|
|
1845
|
-
startVisit(
|
|
2073
|
+
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
1846
2074
|
this.stop();
|
|
1847
|
-
this.currentVisit = new Visit(this,
|
|
2075
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
|
|
1848
2076
|
this.currentVisit.start();
|
|
1849
2077
|
}
|
|
1850
2078
|
submitForm(form, submitter) {
|
|
1851
2079
|
this.stop();
|
|
1852
2080
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
|
1853
|
-
this.formSubmission.
|
|
2081
|
+
if (this.formSubmission.fetchRequest.isIdempotent) {
|
|
2082
|
+
this.proposeVisit(this.formSubmission.fetchRequest.url);
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
this.formSubmission.start();
|
|
2086
|
+
}
|
|
1854
2087
|
}
|
|
1855
2088
|
stop() {
|
|
1856
2089
|
if (this.formSubmission) {
|
|
@@ -1889,8 +2122,8 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1889
2122
|
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
1890
2123
|
const responseHTML = await fetchResponse.responseHTML;
|
|
1891
2124
|
if (responseHTML) {
|
|
1892
|
-
const snapshot =
|
|
1893
|
-
this.view.
|
|
2125
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
2126
|
+
await this.view.renderPage(snapshot);
|
|
1894
2127
|
this.view.clearSnapshotCache();
|
|
1895
2128
|
}
|
|
1896
2129
|
}
|
|
@@ -1998,53 +2231,10 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1998
2231
|
}
|
|
1999
2232
|
}
|
|
2000
2233
|
|
|
2001
|
-
class StreamMessage {
|
|
2002
|
-
constructor(html) {
|
|
2003
|
-
this.templateElement = document.createElement("template");
|
|
2004
|
-
this.templateElement.innerHTML = html;
|
|
2005
|
-
}
|
|
2006
|
-
static wrap(message) {
|
|
2007
|
-
if (typeof message == "string") {
|
|
2008
|
-
return new this(message);
|
|
2009
|
-
}
|
|
2010
|
-
else {
|
|
2011
|
-
return message;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
get fragment() {
|
|
2015
|
-
const fragment = document.createDocumentFragment();
|
|
2016
|
-
for (const element of this.foreignElements) {
|
|
2017
|
-
fragment.appendChild(document.importNode(element, true));
|
|
2018
|
-
}
|
|
2019
|
-
return fragment;
|
|
2020
|
-
}
|
|
2021
|
-
get foreignElements() {
|
|
2022
|
-
return this.templateChildren.reduce((streamElements, child) => {
|
|
2023
|
-
if (child.tagName.toLowerCase() == "turbo-stream") {
|
|
2024
|
-
return [...streamElements, child];
|
|
2025
|
-
}
|
|
2026
|
-
else {
|
|
2027
|
-
return streamElements;
|
|
2028
|
-
}
|
|
2029
|
-
}, []);
|
|
2030
|
-
}
|
|
2031
|
-
get templateChildren() {
|
|
2032
|
-
return Array.from(this.templateElement.content.children);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
2234
|
class StreamObserver {
|
|
2037
2235
|
constructor(delegate) {
|
|
2038
2236
|
this.sources = new Set;
|
|
2039
2237
|
this.started = false;
|
|
2040
|
-
this.prepareFetchRequest = ((event) => {
|
|
2041
|
-
var _a;
|
|
2042
|
-
const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
|
|
2043
|
-
if (fetchOptions) {
|
|
2044
|
-
const { headers } = fetchOptions;
|
|
2045
|
-
headers.Accept = ["text/vnd.turbo-stream.html", headers.Accept].join(", ");
|
|
2046
|
-
}
|
|
2047
|
-
});
|
|
2048
2238
|
this.inspectFetchResponse = ((event) => {
|
|
2049
2239
|
const response = fetchResponseFromEvent(event);
|
|
2050
2240
|
if (response && fetchResponseIsStream(response)) {
|
|
@@ -2062,14 +2252,12 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2062
2252
|
start() {
|
|
2063
2253
|
if (!this.started) {
|
|
2064
2254
|
this.started = true;
|
|
2065
|
-
addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
|
2066
2255
|
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
2067
2256
|
}
|
|
2068
2257
|
}
|
|
2069
2258
|
stop() {
|
|
2070
2259
|
if (this.started) {
|
|
2071
2260
|
this.started = false;
|
|
2072
|
-
removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
|
2073
2261
|
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
2074
2262
|
}
|
|
2075
2263
|
}
|
|
@@ -2108,70 +2296,25 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2108
2296
|
function fetchResponseIsStream(response) {
|
|
2109
2297
|
var _a;
|
|
2110
2298
|
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
|
2111
|
-
return
|
|
2299
|
+
return contentType.startsWith(StreamMessage.contentType);
|
|
2112
2300
|
}
|
|
2113
2301
|
|
|
2114
2302
|
function isAction(action) {
|
|
2115
2303
|
return action == "advance" || action == "replace" || action == "restore";
|
|
2116
2304
|
}
|
|
2117
2305
|
|
|
2118
|
-
class Renderer {
|
|
2119
|
-
renderView(callback) {
|
|
2120
|
-
this.delegate.viewWillRender(this.newBody);
|
|
2121
|
-
callback();
|
|
2122
|
-
this.delegate.viewRendered(this.newBody);
|
|
2123
|
-
}
|
|
2124
|
-
invalidateView() {
|
|
2125
|
-
this.delegate.viewInvalidated();
|
|
2126
|
-
}
|
|
2127
|
-
createScriptElement(element) {
|
|
2128
|
-
if (element.getAttribute("data-turbo-eval") == "false") {
|
|
2129
|
-
return element;
|
|
2130
|
-
}
|
|
2131
|
-
else {
|
|
2132
|
-
const createdScriptElement = document.createElement("script");
|
|
2133
|
-
createdScriptElement.textContent = element.textContent;
|
|
2134
|
-
createdScriptElement.async = false;
|
|
2135
|
-
copyElementAttributes(createdScriptElement, element);
|
|
2136
|
-
return createdScriptElement;
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
function copyElementAttributes(destinationElement, sourceElement) {
|
|
2141
|
-
for (const { name, value } of [...sourceElement.attributes]) {
|
|
2142
|
-
destinationElement.setAttribute(name, value);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
2306
|
class ErrorRenderer extends Renderer {
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
this.
|
|
2150
|
-
this.htmlElement = (() => {
|
|
2151
|
-
const htmlElement = document.createElement("html");
|
|
2152
|
-
htmlElement.innerHTML = html;
|
|
2153
|
-
return htmlElement;
|
|
2154
|
-
})();
|
|
2155
|
-
this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
|
|
2156
|
-
this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
|
|
2157
|
-
}
|
|
2158
|
-
static render(delegate, callback, html) {
|
|
2159
|
-
return new this(delegate, html).render(callback);
|
|
2160
|
-
}
|
|
2161
|
-
render(callback) {
|
|
2162
|
-
this.renderView(() => {
|
|
2163
|
-
this.replaceHeadAndBody();
|
|
2164
|
-
this.activateBodyScriptElements();
|
|
2165
|
-
callback();
|
|
2166
|
-
});
|
|
2307
|
+
async render() {
|
|
2308
|
+
this.replaceHeadAndBody();
|
|
2309
|
+
this.activateScriptElements();
|
|
2167
2310
|
}
|
|
2168
2311
|
replaceHeadAndBody() {
|
|
2169
2312
|
const { documentElement, head, body } = document;
|
|
2170
2313
|
documentElement.replaceChild(this.newHead, head);
|
|
2171
|
-
documentElement.replaceChild(this.
|
|
2314
|
+
documentElement.replaceChild(this.newElement, body);
|
|
2172
2315
|
}
|
|
2173
|
-
|
|
2174
|
-
for (const replaceableElement of this.
|
|
2316
|
+
activateScriptElements() {
|
|
2317
|
+
for (const replaceableElement of this.scriptElements) {
|
|
2175
2318
|
const parentNode = replaceableElement.parentNode;
|
|
2176
2319
|
if (parentNode) {
|
|
2177
2320
|
const element = this.createScriptElement(replaceableElement);
|
|
@@ -2179,84 +2322,38 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2179
2322
|
}
|
|
2180
2323
|
}
|
|
2181
2324
|
}
|
|
2182
|
-
|
|
2325
|
+
get newHead() {
|
|
2326
|
+
return this.newSnapshot.headSnapshot.element;
|
|
2327
|
+
}
|
|
2328
|
+
get scriptElements() {
|
|
2183
2329
|
return [...document.documentElement.querySelectorAll("script")];
|
|
2184
2330
|
}
|
|
2185
2331
|
}
|
|
2186
2332
|
|
|
2187
|
-
class
|
|
2188
|
-
|
|
2189
|
-
this.
|
|
2190
|
-
this.snapshots = {};
|
|
2191
|
-
this.size = size;
|
|
2192
|
-
}
|
|
2193
|
-
has(location) {
|
|
2194
|
-
return location.toCacheKey() in this.snapshots;
|
|
2195
|
-
}
|
|
2196
|
-
get(location) {
|
|
2197
|
-
if (this.has(location)) {
|
|
2198
|
-
const snapshot = this.read(location);
|
|
2199
|
-
this.touch(location);
|
|
2200
|
-
return snapshot;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
put(location, snapshot) {
|
|
2204
|
-
this.write(location, snapshot);
|
|
2205
|
-
this.touch(location);
|
|
2206
|
-
return snapshot;
|
|
2207
|
-
}
|
|
2208
|
-
clear() {
|
|
2209
|
-
this.snapshots = {};
|
|
2210
|
-
}
|
|
2211
|
-
read(location) {
|
|
2212
|
-
return this.snapshots[location.toCacheKey()];
|
|
2333
|
+
class PageRenderer extends Renderer {
|
|
2334
|
+
get shouldRender() {
|
|
2335
|
+
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
|
2213
2336
|
}
|
|
2214
|
-
|
|
2215
|
-
this.
|
|
2337
|
+
prepareToRender() {
|
|
2338
|
+
this.mergeHead();
|
|
2216
2339
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
const index = this.keys.indexOf(key);
|
|
2220
|
-
if (index > -1)
|
|
2221
|
-
this.keys.splice(index, 1);
|
|
2222
|
-
this.keys.unshift(key);
|
|
2223
|
-
this.trim();
|
|
2340
|
+
async render() {
|
|
2341
|
+
this.replaceBody();
|
|
2224
2342
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2343
|
+
finishRendering() {
|
|
2344
|
+
super.finishRendering();
|
|
2345
|
+
if (this.isPreview) {
|
|
2346
|
+
this.focusFirstAutofocusableElement();
|
|
2228
2347
|
}
|
|
2229
2348
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
class SnapshotRenderer extends Renderer {
|
|
2233
|
-
constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
|
|
2234
|
-
super();
|
|
2235
|
-
this.delegate = delegate;
|
|
2236
|
-
this.currentSnapshot = currentSnapshot;
|
|
2237
|
-
this.currentHeadDetails = currentSnapshot.headDetails;
|
|
2238
|
-
this.newSnapshot = newSnapshot;
|
|
2239
|
-
this.newHeadDetails = newSnapshot.headDetails;
|
|
2240
|
-
this.newBody = newSnapshot.bodyElement;
|
|
2241
|
-
this.isPreview = isPreview;
|
|
2349
|
+
get currentHeadSnapshot() {
|
|
2350
|
+
return this.currentSnapshot.headSnapshot;
|
|
2242
2351
|
}
|
|
2243
|
-
|
|
2244
|
-
return
|
|
2352
|
+
get newHeadSnapshot() {
|
|
2353
|
+
return this.newSnapshot.headSnapshot;
|
|
2245
2354
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
this.mergeHead();
|
|
2249
|
-
this.renderView(() => {
|
|
2250
|
-
this.replaceBody();
|
|
2251
|
-
if (!this.isPreview) {
|
|
2252
|
-
this.focusFirstAutofocusableElement();
|
|
2253
|
-
}
|
|
2254
|
-
callback();
|
|
2255
|
-
});
|
|
2256
|
-
}
|
|
2257
|
-
else {
|
|
2258
|
-
this.invalidateView();
|
|
2259
|
-
}
|
|
2355
|
+
get newElement() {
|
|
2356
|
+
return this.newSnapshot.element;
|
|
2260
2357
|
}
|
|
2261
2358
|
mergeHead() {
|
|
2262
2359
|
this.copyNewHeadStylesheetElements();
|
|
@@ -2265,186 +2362,147 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2265
2362
|
this.copyNewHeadProvisionalElements();
|
|
2266
2363
|
}
|
|
2267
2364
|
replaceBody() {
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
}
|
|
2273
|
-
shouldRender() {
|
|
2274
|
-
return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
|
|
2365
|
+
this.preservingPermanentElements(() => {
|
|
2366
|
+
this.activateNewBody();
|
|
2367
|
+
this.assignNewBody();
|
|
2368
|
+
});
|
|
2275
2369
|
}
|
|
2276
|
-
trackedElementsAreIdentical() {
|
|
2277
|
-
return this.
|
|
2370
|
+
get trackedElementsAreIdentical() {
|
|
2371
|
+
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
|
2278
2372
|
}
|
|
2279
2373
|
copyNewHeadStylesheetElements() {
|
|
2280
|
-
for (const element of this.
|
|
2374
|
+
for (const element of this.newHeadStylesheetElements) {
|
|
2281
2375
|
document.head.appendChild(element);
|
|
2282
2376
|
}
|
|
2283
2377
|
}
|
|
2284
2378
|
copyNewHeadScriptElements() {
|
|
2285
|
-
for (const element of this.
|
|
2379
|
+
for (const element of this.newHeadScriptElements) {
|
|
2286
2380
|
document.head.appendChild(this.createScriptElement(element));
|
|
2287
2381
|
}
|
|
2288
2382
|
}
|
|
2289
2383
|
removeCurrentHeadProvisionalElements() {
|
|
2290
|
-
for (const element of this.
|
|
2384
|
+
for (const element of this.currentHeadProvisionalElements) {
|
|
2291
2385
|
document.head.removeChild(element);
|
|
2292
2386
|
}
|
|
2293
2387
|
}
|
|
2294
2388
|
copyNewHeadProvisionalElements() {
|
|
2295
|
-
for (const element of this.
|
|
2389
|
+
for (const element of this.newHeadProvisionalElements) {
|
|
2296
2390
|
document.head.appendChild(element);
|
|
2297
2391
|
}
|
|
2298
2392
|
}
|
|
2299
|
-
relocateCurrentBodyPermanentElements() {
|
|
2300
|
-
return this.getCurrentBodyPermanentElements().reduce((placeholders, permanentElement) => {
|
|
2301
|
-
const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
|
|
2302
|
-
if (newElement) {
|
|
2303
|
-
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
|
2304
|
-
replaceElementWithElement(permanentElement, placeholder.element);
|
|
2305
|
-
replaceElementWithElement(newElement, permanentElement);
|
|
2306
|
-
return [...placeholders, placeholder];
|
|
2307
|
-
}
|
|
2308
|
-
else {
|
|
2309
|
-
return placeholders;
|
|
2310
|
-
}
|
|
2311
|
-
}, []);
|
|
2312
|
-
}
|
|
2313
|
-
replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
|
2314
|
-
for (const { element, permanentElement } of placeholders) {
|
|
2315
|
-
const clonedElement = permanentElement.cloneNode(true);
|
|
2316
|
-
replaceElementWithElement(element, clonedElement);
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
2393
|
activateNewBody() {
|
|
2320
|
-
document.adoptNode(this.
|
|
2394
|
+
document.adoptNode(this.newElement);
|
|
2321
2395
|
this.activateNewBodyScriptElements();
|
|
2322
2396
|
}
|
|
2323
2397
|
activateNewBodyScriptElements() {
|
|
2324
|
-
for (const inertScriptElement of this.
|
|
2398
|
+
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
2325
2399
|
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
|
2326
2400
|
replaceElementWithElement(inertScriptElement, activatedScriptElement);
|
|
2327
2401
|
}
|
|
2328
2402
|
}
|
|
2329
2403
|
assignNewBody() {
|
|
2330
|
-
if (document.body) {
|
|
2331
|
-
replaceElementWithElement(document.body, this.
|
|
2404
|
+
if (document.body && this.newElement instanceof HTMLBodyElement) {
|
|
2405
|
+
replaceElementWithElement(document.body, this.newElement);
|
|
2332
2406
|
}
|
|
2333
2407
|
else {
|
|
2334
|
-
document.documentElement.appendChild(this.
|
|
2408
|
+
document.documentElement.appendChild(this.newElement);
|
|
2335
2409
|
}
|
|
2336
2410
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
if (elementIsFocusable(element)) {
|
|
2340
|
-
element.focus();
|
|
2341
|
-
}
|
|
2411
|
+
get newHeadStylesheetElements() {
|
|
2412
|
+
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
|
2342
2413
|
}
|
|
2343
|
-
|
|
2344
|
-
return this.
|
|
2414
|
+
get newHeadScriptElements() {
|
|
2415
|
+
return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
|
|
2345
2416
|
}
|
|
2346
|
-
|
|
2347
|
-
return this.
|
|
2417
|
+
get currentHeadProvisionalElements() {
|
|
2418
|
+
return this.currentHeadSnapshot.provisionalElements;
|
|
2348
2419
|
}
|
|
2349
|
-
|
|
2350
|
-
return this.
|
|
2420
|
+
get newHeadProvisionalElements() {
|
|
2421
|
+
return this.newHeadSnapshot.provisionalElements;
|
|
2351
2422
|
}
|
|
2352
|
-
|
|
2353
|
-
return this.
|
|
2423
|
+
get newBodyScriptElements() {
|
|
2424
|
+
return [...this.newElement.querySelectorAll("script")];
|
|
2354
2425
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
class SnapshotCache {
|
|
2429
|
+
constructor(size) {
|
|
2430
|
+
this.keys = [];
|
|
2431
|
+
this.snapshots = {};
|
|
2432
|
+
this.size = size;
|
|
2357
2433
|
}
|
|
2358
|
-
|
|
2359
|
-
return
|
|
2434
|
+
has(location) {
|
|
2435
|
+
return toCacheKey(location) in this.snapshots;
|
|
2360
2436
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
return
|
|
2437
|
+
get(location) {
|
|
2438
|
+
if (this.has(location)) {
|
|
2439
|
+
const snapshot = this.read(location);
|
|
2440
|
+
this.touch(location);
|
|
2441
|
+
return snapshot;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
put(location, snapshot) {
|
|
2445
|
+
this.write(location, snapshot);
|
|
2446
|
+
this.touch(location);
|
|
2447
|
+
return snapshot;
|
|
2448
|
+
}
|
|
2449
|
+
clear() {
|
|
2450
|
+
this.snapshots = {};
|
|
2451
|
+
}
|
|
2452
|
+
read(location) {
|
|
2453
|
+
return this.snapshots[toCacheKey(location)];
|
|
2454
|
+
}
|
|
2455
|
+
write(location, snapshot) {
|
|
2456
|
+
this.snapshots[toCacheKey(location)] = snapshot;
|
|
2457
|
+
}
|
|
2458
|
+
touch(location) {
|
|
2459
|
+
const key = toCacheKey(location);
|
|
2460
|
+
const index = this.keys.indexOf(key);
|
|
2461
|
+
if (index > -1)
|
|
2462
|
+
this.keys.splice(index, 1);
|
|
2463
|
+
this.keys.unshift(key);
|
|
2464
|
+
this.trim();
|
|
2465
|
+
}
|
|
2466
|
+
trim() {
|
|
2467
|
+
for (const key of this.keys.splice(this.size)) {
|
|
2468
|
+
delete this.snapshots[key];
|
|
2469
|
+
}
|
|
2372
2470
|
}
|
|
2373
|
-
}
|
|
2374
|
-
function elementIsFocusable(element) {
|
|
2375
|
-
return element && typeof element.focus == "function";
|
|
2376
2471
|
}
|
|
2377
2472
|
|
|
2378
|
-
class View {
|
|
2379
|
-
constructor(
|
|
2380
|
-
|
|
2473
|
+
class PageView extends View {
|
|
2474
|
+
constructor() {
|
|
2475
|
+
super(...arguments);
|
|
2381
2476
|
this.snapshotCache = new SnapshotCache(10);
|
|
2382
|
-
this.
|
|
2477
|
+
this.lastRenderedLocation = new URL(location.href);
|
|
2383
2478
|
}
|
|
2384
|
-
|
|
2385
|
-
|
|
2479
|
+
renderPage(snapshot, isPreview = false) {
|
|
2480
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
|
2481
|
+
return this.render(renderer);
|
|
2386
2482
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
getSnapshot() {
|
|
2391
|
-
return Snapshot.fromHTMLElement(this.htmlElement);
|
|
2483
|
+
renderError(snapshot) {
|
|
2484
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
|
2485
|
+
this.render(renderer);
|
|
2392
2486
|
}
|
|
2393
2487
|
clearSnapshotCache() {
|
|
2394
2488
|
this.snapshotCache.clear();
|
|
2395
2489
|
}
|
|
2396
|
-
shouldCacheSnapshot() {
|
|
2397
|
-
return this.getSnapshot().isCacheable();
|
|
2398
|
-
}
|
|
2399
2490
|
async cacheSnapshot() {
|
|
2400
|
-
if (this.shouldCacheSnapshot
|
|
2491
|
+
if (this.shouldCacheSnapshot) {
|
|
2401
2492
|
this.delegate.viewWillCacheSnapshot();
|
|
2402
|
-
const snapshot = this
|
|
2403
|
-
|
|
2404
|
-
await nextMicrotask();
|
|
2493
|
+
const { snapshot, lastRenderedLocation: location } = this;
|
|
2494
|
+
await nextEventLoopTick();
|
|
2405
2495
|
this.snapshotCache.put(location, snapshot.clone());
|
|
2406
2496
|
}
|
|
2407
2497
|
}
|
|
2408
2498
|
getCachedSnapshotForLocation(location) {
|
|
2409
2499
|
return this.snapshotCache.get(location);
|
|
2410
2500
|
}
|
|
2411
|
-
|
|
2412
|
-
this.
|
|
2413
|
-
if (snapshot) {
|
|
2414
|
-
this.renderSnapshot(snapshot, isPreview, callback);
|
|
2415
|
-
}
|
|
2416
|
-
else {
|
|
2417
|
-
this.renderError(error, callback);
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
scrollToAnchor(anchor) {
|
|
2421
|
-
const element = this.getElementForAnchor(anchor);
|
|
2422
|
-
if (element) {
|
|
2423
|
-
this.scrollToElement(element);
|
|
2424
|
-
}
|
|
2425
|
-
else {
|
|
2426
|
-
this.scrollToPosition({ x: 0, y: 0 });
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
scrollToElement(element) {
|
|
2430
|
-
element.scrollIntoView();
|
|
2431
|
-
}
|
|
2432
|
-
scrollToPosition({ x, y }) {
|
|
2433
|
-
window.scrollTo(x, y);
|
|
2434
|
-
}
|
|
2435
|
-
markAsPreview(isPreview) {
|
|
2436
|
-
if (isPreview) {
|
|
2437
|
-
this.htmlElement.setAttribute("data-turbo-preview", "");
|
|
2438
|
-
}
|
|
2439
|
-
else {
|
|
2440
|
-
this.htmlElement.removeAttribute("data-turbo-preview");
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
renderSnapshot(snapshot, isPreview, callback) {
|
|
2444
|
-
SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
|
|
2501
|
+
get snapshot() {
|
|
2502
|
+
return PageSnapshot.fromElement(this.element);
|
|
2445
2503
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2504
|
+
get shouldCacheSnapshot() {
|
|
2505
|
+
return this.snapshot.isCacheable;
|
|
2448
2506
|
}
|
|
2449
2507
|
}
|
|
2450
2508
|
|
|
@@ -2452,7 +2510,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2452
2510
|
constructor() {
|
|
2453
2511
|
this.navigator = new Navigator(this);
|
|
2454
2512
|
this.history = new History(this);
|
|
2455
|
-
this.view = new
|
|
2513
|
+
this.view = new PageView(this, document.documentElement);
|
|
2456
2514
|
this.adapter = new BrowserAdapter(this);
|
|
2457
2515
|
this.pageObserver = new PageObserver(this);
|
|
2458
2516
|
this.linkClickObserver = new LinkClickObserver(this);
|
|
@@ -2496,7 +2554,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2496
2554
|
this.adapter = adapter;
|
|
2497
2555
|
}
|
|
2498
2556
|
visit(location, options = {}) {
|
|
2499
|
-
this.navigator.proposeVisit(
|
|
2557
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
|
2500
2558
|
}
|
|
2501
2559
|
connectStreamSource(source) {
|
|
2502
2560
|
this.streamObserver.connectStreamSource(source);
|
|
@@ -2537,15 +2595,17 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2537
2595
|
}
|
|
2538
2596
|
followedLinkToLocation(link, location) {
|
|
2539
2597
|
const action = this.getActionForLink(link);
|
|
2540
|
-
this.visit(location, { action });
|
|
2598
|
+
this.visit(location.href, { action });
|
|
2541
2599
|
}
|
|
2542
2600
|
allowsVisitingLocation(location) {
|
|
2543
2601
|
return this.applicationAllowsVisitingLocation(location);
|
|
2544
2602
|
}
|
|
2545
2603
|
visitProposedToLocation(location, options) {
|
|
2604
|
+
extendURLWithDeprecatedProperties(location);
|
|
2546
2605
|
this.adapter.visitProposedToLocation(location, options);
|
|
2547
2606
|
}
|
|
2548
2607
|
visitStarted(visit) {
|
|
2608
|
+
extendURLWithDeprecatedProperties(visit.location);
|
|
2549
2609
|
this.notifyApplicationAfterVisitingLocation(visit.location);
|
|
2550
2610
|
}
|
|
2551
2611
|
visitCompleted(visit) {
|
|
@@ -2570,19 +2630,19 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2570
2630
|
receivedMessageFromStream(message) {
|
|
2571
2631
|
this.renderStreamMessage(message);
|
|
2572
2632
|
}
|
|
2573
|
-
|
|
2574
|
-
this.
|
|
2633
|
+
viewWillCacheSnapshot() {
|
|
2634
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
|
2575
2635
|
}
|
|
2576
|
-
|
|
2636
|
+
viewWillRenderSnapshot({ element }, isPreview) {
|
|
2637
|
+
this.notifyApplicationBeforeRender(element);
|
|
2638
|
+
}
|
|
2639
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
|
2577
2640
|
this.view.lastRenderedLocation = this.history.location;
|
|
2578
2641
|
this.notifyApplicationAfterRender();
|
|
2579
2642
|
}
|
|
2580
2643
|
viewInvalidated() {
|
|
2581
2644
|
this.adapter.pageInvalidated();
|
|
2582
2645
|
}
|
|
2583
|
-
viewWillCacheSnapshot() {
|
|
2584
|
-
this.notifyApplicationBeforeCachingSnapshot();
|
|
2585
|
-
}
|
|
2586
2646
|
applicationAllowsFollowingLinkToLocation(link, location) {
|
|
2587
2647
|
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
|
2588
2648
|
return !event.defaultPrevented;
|
|
@@ -2592,13 +2652,13 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2592
2652
|
return !event.defaultPrevented;
|
|
2593
2653
|
}
|
|
2594
2654
|
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
|
2595
|
-
return dispatch("turbo:click", { target: link, detail: { url: location.
|
|
2655
|
+
return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
|
|
2596
2656
|
}
|
|
2597
2657
|
notifyApplicationBeforeVisitingLocation(location) {
|
|
2598
|
-
return dispatch("turbo:before-visit", { detail: { url: location.
|
|
2658
|
+
return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
|
|
2599
2659
|
}
|
|
2600
2660
|
notifyApplicationAfterVisitingLocation(location) {
|
|
2601
|
-
return dispatch("turbo:visit", { detail: { url: location.
|
|
2661
|
+
return dispatch("turbo:visit", { detail: { url: location.href } });
|
|
2602
2662
|
}
|
|
2603
2663
|
notifyApplicationBeforeCachingSnapshot() {
|
|
2604
2664
|
return dispatch("turbo:before-cache");
|
|
@@ -2610,7 +2670,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2610
2670
|
return dispatch("turbo:render");
|
|
2611
2671
|
}
|
|
2612
2672
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
2613
|
-
return dispatch("turbo:load", { detail: { url: this.location.
|
|
2673
|
+
return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
|
|
2614
2674
|
}
|
|
2615
2675
|
getActionForLink(link) {
|
|
2616
2676
|
const action = link.getAttribute("data-turbo-action");
|
|
@@ -2626,9 +2686,22 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2626
2686
|
}
|
|
2627
2687
|
}
|
|
2628
2688
|
locationIsVisitable(location) {
|
|
2629
|
-
return
|
|
2689
|
+
return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
|
|
2630
2690
|
}
|
|
2691
|
+
get snapshot() {
|
|
2692
|
+
return this.view.snapshot;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
function extendURLWithDeprecatedProperties(url) {
|
|
2696
|
+
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
|
2631
2697
|
}
|
|
2698
|
+
const deprecatedLocationPropertyDescriptors = {
|
|
2699
|
+
absoluteURL: {
|
|
2700
|
+
get() {
|
|
2701
|
+
return this.toString();
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2632
2705
|
|
|
2633
2706
|
const session = new Session;
|
|
2634
2707
|
const { navigator } = session;
|