turbo-rails 0.5.7 → 0.5.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -9
- data/Rakefile +4 -0
- data/app/assets/javascripts/turbo.js +1234 -940
- data/app/channels/turbo/streams/broadcasts.rb +16 -0
- data/app/helpers/turbo/frames_helper.rb +4 -0
- data/app/helpers/turbo/streams/action_helper.rb +2 -7
- data/app/helpers/turbo/streams_helper.rb +5 -3
- data/app/javascript/turbo/cable.js +6 -3
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -0
- data/app/jobs/turbo/streams/broadcast_job.rb +2 -0
- data/app/models/concerns/turbo/broadcastable.rb +41 -7
- data/app/models/turbo/streams/tag_builder.rb +29 -2
- data/lib/install/turbo_with_asset_pipeline.rb +1 -6
- data/lib/install/turbo_with_webpacker.rb +5 -4
- data/lib/tasks/turbo_tasks.rake +3 -3
- data/lib/turbo/engine.rb +5 -1
- data/lib/turbo/test_assertions.rb +2 -3
- data/lib/turbo/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27a0cac6803a0e1e410fb4fce4aaa60a0998aee4dec7d1b4b4cbb674533ce5cf
|
4
|
+
data.tar.gz: a4a67e8cff6989445d669cd6aa3b64b39dfc7de4ca7afdfddfd41c1e9334558e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2de3cde68a13beffda32366c8b209666b1bae8a8dc3423ed9f5645dcae00fe88ba9f8ff08582eb60c9212a209d24683adf14f220a670923eac6806268198583
|
7
|
+
data.tar.gz: be9f2251ca7bf7684d71ecfba9cbaaefbc64423d1b7f86428fc733facc9e1867ff6b54df1c756cc96ce0367ffe2cbb090af2bbf3319d432d88fbae005a3edd7b
|
data/README.md
CHANGED
@@ -40,13 +40,12 @@ The JavaScript for Turbo can either be run through the asset pipeline, which is
|
|
40
40
|
|
41
41
|
Running `turbo:install` will install through NPM if Webpacker is installed in the application. Otherwise the asset pipeline version is used.
|
42
42
|
|
43
|
-
If you're using Webpack and need to use
|
43
|
+
If you're using Webpack and need to use the cable consumer, you can import [`cable`](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable.js) (`import { cable } from "@hotwired/turbo-rails"`), but ensure that your application actually *uses* the members it `import`s when using this style (see [turbo-rails#48](https://github.com/hotwired/turbo-rails/issues/48)).
|
44
44
|
|
45
|
-
|
45
|
+
The `Turbo` instance is automatically assigned to `window.Turbo` upon import:
|
46
46
|
|
47
47
|
```js
|
48
|
-
import
|
49
|
-
window.Turbo = Turbo
|
48
|
+
import "@hotwired/turbo-rails"
|
50
49
|
```
|
51
50
|
|
52
51
|
## Usage
|
@@ -56,11 +55,7 @@ You can watch [the video introduction to Hotwire](https://hotwire.dev/#screencas
|
|
56
55
|
|
57
56
|
## Compatibility with Rails UJS
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
Note that the helpers that turn `link_to` into remote invocations will _not_ currently work with Turbo. Links that have been made remote will not stick within frames nor will they allow you to respond with turbo stream actions. The recommendation is to replace these links with styled `button_to`, so you'll flow through a regular form, and you'll be better off with a11y compliance.
|
62
|
-
|
63
|
-
You can still use the `data-confirm` and `data-disable-with`.
|
58
|
+
Turbo can coexist with Rails UJS, but you need to take a series of ugprade steps to make it happen. See [the upgrading guide](https://github.com/hotwired/turbo-rails/blob/main/UPGRADING.md).
|
64
59
|
|
65
60
|
|
66
61
|
## Development
|
data/Rakefile
CHANGED
@@ -2,6 +2,10 @@ require "bundler/setup"
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
4
|
|
5
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
6
|
+
load "rails/tasks/engine.rake"
|
7
|
+
load "rails/tasks/statistics.rake"
|
8
|
+
|
5
9
|
Rake::TestTask.new do |test|
|
6
10
|
test.libs << "test"
|
7
11
|
test.test_files = FileList["test/**/*_test.rb"]
|
@@ -55,7 +55,7 @@ class FrameElement extends HTMLElement {
|
|
55
55
|
this.delegate = new FrameElement.delegateConstructor(this);
|
56
56
|
}
|
57
57
|
static get observedAttributes() {
|
58
|
-
return [ "loading", "src" ];
|
58
|
+
return [ "disabled", "loading", "src" ];
|
59
59
|
}
|
60
60
|
connectedCallback() {
|
61
61
|
this.delegate.connect();
|
@@ -68,6 +68,8 @@ class FrameElement extends HTMLElement {
|
|
68
68
|
this.delegate.loadingStyleChanged();
|
69
69
|
} else if (name == "src") {
|
70
70
|
this.delegate.sourceURLChanged();
|
71
|
+
} else {
|
72
|
+
this.delegate.disabledChanged();
|
71
73
|
}
|
72
74
|
}
|
73
75
|
get src() {
|
@@ -132,82 +134,61 @@ function frameLoadingStyleFromString(style) {
|
|
132
134
|
}
|
133
135
|
}
|
134
136
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
148
|
-
static get currentLocation() {
|
149
|
-
return this.wrap(window.location.toString());
|
150
|
-
}
|
151
|
-
static wrap(locatable) {
|
152
|
-
if (typeof locatable == "string") {
|
153
|
-
return new this(locatable);
|
154
|
-
} else if (locatable != null) {
|
155
|
-
return locatable;
|
156
|
-
}
|
157
|
-
}
|
158
|
-
getOrigin() {
|
159
|
-
return this.absoluteURL.split("/", 3).join("/");
|
160
|
-
}
|
161
|
-
getPath() {
|
162
|
-
return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
|
163
|
-
}
|
164
|
-
getPathComponents() {
|
165
|
-
return this.getPath().split("/").slice(1);
|
166
|
-
}
|
167
|
-
getLastPathComponent() {
|
168
|
-
return this.getPathComponents().slice(-1)[0];
|
169
|
-
}
|
170
|
-
getExtension() {
|
171
|
-
return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
|
172
|
-
}
|
173
|
-
isHTML() {
|
174
|
-
return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
|
175
|
-
}
|
176
|
-
isPrefixedBy(location) {
|
177
|
-
const prefixURL = getPrefixURL(location);
|
178
|
-
return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
|
179
|
-
}
|
180
|
-
isEqualTo(location) {
|
181
|
-
return location && this.absoluteURL === location.absoluteURL;
|
182
|
-
}
|
183
|
-
toCacheKey() {
|
184
|
-
return this.requestURL;
|
185
|
-
}
|
186
|
-
toJSON() {
|
187
|
-
return this.absoluteURL;
|
188
|
-
}
|
189
|
-
toString() {
|
190
|
-
return this.absoluteURL;
|
137
|
+
function expandURL(locatable) {
|
138
|
+
return new URL(locatable.toString(), document.baseURI);
|
139
|
+
}
|
140
|
+
|
141
|
+
function getAnchor(url) {
|
142
|
+
let anchorMatch;
|
143
|
+
if (url.hash) {
|
144
|
+
return url.hash.slice(1);
|
145
|
+
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
146
|
+
return anchorMatch[1];
|
147
|
+
} else {
|
148
|
+
return "";
|
191
149
|
}
|
192
|
-
|
193
|
-
|
150
|
+
}
|
151
|
+
|
152
|
+
function getExtension(url) {
|
153
|
+
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
154
|
+
}
|
155
|
+
|
156
|
+
function isHTML(url) {
|
157
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
158
|
+
}
|
159
|
+
|
160
|
+
function isPrefixedBy(baseURL, url) {
|
161
|
+
const prefix = getPrefix(url);
|
162
|
+
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
163
|
+
}
|
164
|
+
|
165
|
+
function toCacheKey(url) {
|
166
|
+
const anchorLength = url.hash.length;
|
167
|
+
if (anchorLength < 2) {
|
168
|
+
return url.href;
|
169
|
+
} else {
|
170
|
+
return url.href.slice(0, -anchorLength);
|
194
171
|
}
|
195
172
|
}
|
196
173
|
|
197
|
-
function
|
198
|
-
return
|
174
|
+
function urlsAreEqual(left, right) {
|
175
|
+
return expandURL(left).href == expandURL(right).href;
|
176
|
+
}
|
177
|
+
|
178
|
+
function getPathComponents(url) {
|
179
|
+
return url.pathname.split("/").slice(1);
|
199
180
|
}
|
200
181
|
|
201
|
-
function
|
202
|
-
return
|
182
|
+
function getLastPathComponent(url) {
|
183
|
+
return getPathComponents(url).slice(-1)[0];
|
203
184
|
}
|
204
185
|
|
205
|
-
function
|
206
|
-
return
|
186
|
+
function getPrefix(url) {
|
187
|
+
return addTrailingSlash(url.origin + url.pathname);
|
207
188
|
}
|
208
189
|
|
209
|
-
function
|
210
|
-
return
|
190
|
+
function addTrailingSlash(value) {
|
191
|
+
return value.endsWith("/") ? value : value + "/";
|
211
192
|
}
|
212
193
|
|
213
194
|
class FetchResponse {
|
@@ -230,7 +211,7 @@ class FetchResponse {
|
|
230
211
|
return this.response.redirected;
|
231
212
|
}
|
232
213
|
get location() {
|
233
|
-
return
|
214
|
+
return expandURL(this.response.url);
|
234
215
|
}
|
235
216
|
get isHTML() {
|
236
217
|
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
@@ -270,10 +251,18 @@ function nextAnimationFrame() {
|
|
270
251
|
return new Promise((resolve => requestAnimationFrame((() => resolve()))));
|
271
252
|
}
|
272
253
|
|
254
|
+
function nextEventLoopTick() {
|
255
|
+
return new Promise((resolve => setTimeout((() => resolve()), 0)));
|
256
|
+
}
|
257
|
+
|
273
258
|
function nextMicrotask() {
|
274
259
|
return Promise.resolve();
|
275
260
|
}
|
276
261
|
|
262
|
+
function parseHTMLDocument(html = "") {
|
263
|
+
return (new DOMParser).parseFromString(html, "text/html");
|
264
|
+
}
|
265
|
+
|
277
266
|
function unindent(strings, ...values) {
|
278
267
|
const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
|
279
268
|
const match = lines[0].match(/^\s+/);
|
@@ -334,27 +323,23 @@ function fetchMethodFromString(method) {
|
|
334
323
|
}
|
335
324
|
|
336
325
|
class FetchRequest {
|
337
|
-
constructor(delegate, method, location, body) {
|
326
|
+
constructor(delegate, method, location, body = new URLSearchParams) {
|
338
327
|
this.abortController = new AbortController;
|
339
328
|
this.delegate = delegate;
|
340
329
|
this.method = method;
|
341
|
-
this.
|
342
|
-
this.
|
343
|
-
|
344
|
-
get url() {
|
345
|
-
const url = this.location.absoluteURL;
|
346
|
-
const query = this.params.toString();
|
347
|
-
if (this.isIdempotent && query.length) {
|
348
|
-
return [ url, query ].join(url.includes("?") ? "&" : "?");
|
330
|
+
this.headers = this.defaultHeaders;
|
331
|
+
if (this.isIdempotent) {
|
332
|
+
this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
|
349
333
|
} else {
|
350
|
-
|
334
|
+
this.body = body;
|
335
|
+
this.url = location;
|
351
336
|
}
|
352
337
|
}
|
338
|
+
get location() {
|
339
|
+
return this.url;
|
340
|
+
}
|
353
341
|
get params() {
|
354
|
-
return this.
|
355
|
-
params.append(name, value.toString());
|
356
|
-
return params;
|
357
|
-
}), new URLSearchParams);
|
342
|
+
return this.url.searchParams;
|
358
343
|
}
|
359
344
|
get entries() {
|
360
345
|
return this.body ? Array.from(this.body.entries()) : [];
|
@@ -363,7 +348,9 @@ class FetchRequest {
|
|
363
348
|
this.abortController.abort();
|
364
349
|
}
|
365
350
|
async perform() {
|
351
|
+
var _a, _b;
|
366
352
|
const {fetchOptions: fetchOptions} = this;
|
353
|
+
(_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
|
367
354
|
dispatch("turbo:before-fetch-request", {
|
368
355
|
detail: {
|
369
356
|
fetchOptions: fetchOptions
|
@@ -371,7 +358,7 @@ class FetchRequest {
|
|
371
358
|
});
|
372
359
|
try {
|
373
360
|
this.delegate.requestStarted(this);
|
374
|
-
const response = await fetch(this.url, fetchOptions);
|
361
|
+
const response = await fetch(this.url.href, fetchOptions);
|
375
362
|
return await this.receive(response);
|
376
363
|
} catch (error) {
|
377
364
|
this.delegate.requestErrored(this, error);
|
@@ -403,28 +390,35 @@ class FetchRequest {
|
|
403
390
|
credentials: "same-origin",
|
404
391
|
headers: this.headers,
|
405
392
|
redirect: "follow",
|
406
|
-
body: this.
|
393
|
+
body: this.body,
|
407
394
|
signal: this.abortSignal
|
408
395
|
};
|
409
396
|
}
|
397
|
+
get defaultHeaders() {
|
398
|
+
return {
|
399
|
+
Accept: "text/html, application/xhtml+xml"
|
400
|
+
};
|
401
|
+
}
|
410
402
|
get isIdempotent() {
|
411
403
|
return this.method == FetchMethod.get;
|
412
404
|
}
|
413
|
-
get
|
414
|
-
return
|
415
|
-
Accept: "text/html, application/xhtml+xml"
|
416
|
-
}, this.additionalHeaders);
|
405
|
+
get abortSignal() {
|
406
|
+
return this.abortController.signal;
|
417
407
|
}
|
418
|
-
|
419
|
-
|
420
|
-
|
408
|
+
}
|
409
|
+
|
410
|
+
function mergeFormDataEntries(url, entries) {
|
411
|
+
const currentSearchParams = new URLSearchParams(url.search);
|
412
|
+
for (const [name, value] of entries) {
|
413
|
+
if (value instanceof File) continue;
|
414
|
+
if (currentSearchParams.has(name)) {
|
415
|
+
currentSearchParams.delete(name);
|
416
|
+
url.searchParams.set(name, value);
|
421
417
|
} else {
|
422
|
-
|
418
|
+
url.searchParams.append(name, value);
|
423
419
|
}
|
424
420
|
}
|
425
|
-
|
426
|
-
return this.abortController.signal;
|
427
|
-
}
|
421
|
+
return url;
|
428
422
|
}
|
429
423
|
|
430
424
|
class AppearanceObserver {
|
@@ -454,6 +448,41 @@ class AppearanceObserver {
|
|
454
448
|
}
|
455
449
|
}
|
456
450
|
|
451
|
+
class StreamMessage {
|
452
|
+
constructor(html) {
|
453
|
+
this.templateElement = document.createElement("template");
|
454
|
+
this.templateElement.innerHTML = html;
|
455
|
+
}
|
456
|
+
static wrap(message) {
|
457
|
+
if (typeof message == "string") {
|
458
|
+
return new this(message);
|
459
|
+
} else {
|
460
|
+
return message;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
get fragment() {
|
464
|
+
const fragment = document.createDocumentFragment();
|
465
|
+
for (const element of this.foreignElements) {
|
466
|
+
fragment.appendChild(document.importNode(element, true));
|
467
|
+
}
|
468
|
+
return fragment;
|
469
|
+
}
|
470
|
+
get foreignElements() {
|
471
|
+
return this.templateChildren.reduce(((streamElements, child) => {
|
472
|
+
if (child.tagName.toLowerCase() == "turbo-stream") {
|
473
|
+
return [ ...streamElements, child ];
|
474
|
+
} else {
|
475
|
+
return streamElements;
|
476
|
+
}
|
477
|
+
}), []);
|
478
|
+
}
|
479
|
+
get templateChildren() {
|
480
|
+
return Array.from(this.templateElement.content.children);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
485
|
+
|
457
486
|
var FormSubmissionState;
|
458
487
|
|
459
488
|
(function(FormSubmissionState) {
|
@@ -465,14 +494,35 @@ var FormSubmissionState;
|
|
465
494
|
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
466
495
|
})(FormSubmissionState || (FormSubmissionState = {}));
|
467
496
|
|
497
|
+
var FormEnctype;
|
498
|
+
|
499
|
+
(function(FormEnctype) {
|
500
|
+
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
|
501
|
+
FormEnctype["multipart"] = "multipart/form-data";
|
502
|
+
FormEnctype["plain"] = "text/plain";
|
503
|
+
})(FormEnctype || (FormEnctype = {}));
|
504
|
+
|
505
|
+
function formEnctypeFromString(encoding) {
|
506
|
+
switch (encoding.toLowerCase()) {
|
507
|
+
case FormEnctype.multipart:
|
508
|
+
return FormEnctype.multipart;
|
509
|
+
|
510
|
+
case FormEnctype.plain:
|
511
|
+
return FormEnctype.plain;
|
512
|
+
|
513
|
+
default:
|
514
|
+
return FormEnctype.urlEncoded;
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
468
518
|
class FormSubmission {
|
469
519
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
470
520
|
this.state = FormSubmissionState.initialized;
|
471
521
|
this.delegate = delegate;
|
472
522
|
this.formElement = formElement;
|
473
|
-
this.formData = buildFormData(formElement, submitter);
|
474
523
|
this.submitter = submitter;
|
475
|
-
this.
|
524
|
+
this.formData = buildFormData(formElement, submitter);
|
525
|
+
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
|
476
526
|
this.mustRedirect = mustRedirect;
|
477
527
|
}
|
478
528
|
get method() {
|
@@ -485,7 +535,24 @@ class FormSubmission {
|
|
485
535
|
return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
|
486
536
|
}
|
487
537
|
get location() {
|
488
|
-
return
|
538
|
+
return expandURL(this.action);
|
539
|
+
}
|
540
|
+
get body() {
|
541
|
+
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
542
|
+
return new URLSearchParams(this.stringFormData);
|
543
|
+
} else {
|
544
|
+
return this.formData;
|
545
|
+
}
|
546
|
+
}
|
547
|
+
get enctype() {
|
548
|
+
var _a;
|
549
|
+
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
550
|
+
}
|
551
|
+
get isIdempotent() {
|
552
|
+
return this.fetchRequest.isIdempotent;
|
553
|
+
}
|
554
|
+
get stringFormData() {
|
555
|
+
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
489
556
|
}
|
490
557
|
async start() {
|
491
558
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
@@ -502,15 +569,14 @@ class FormSubmission {
|
|
502
569
|
return true;
|
503
570
|
}
|
504
571
|
}
|
505
|
-
|
506
|
-
|
507
|
-
if (this.method != FetchMethod.get) {
|
572
|
+
prepareHeadersForRequest(headers, request) {
|
573
|
+
if (!request.isIdempotent) {
|
508
574
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
509
575
|
if (token) {
|
510
576
|
headers["X-CSRF-Token"] = token;
|
511
577
|
}
|
578
|
+
headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
|
512
579
|
}
|
513
|
-
return headers;
|
514
580
|
}
|
515
581
|
requestStarted(request) {
|
516
582
|
this.state = FormSubmissionState.waiting;
|
@@ -576,8 +642,8 @@ function buildFormData(formElement, submitter) {
|
|
576
642
|
const formData = new FormData(formElement);
|
577
643
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
578
644
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
579
|
-
if (name && formData.get(name) != value) {
|
580
|
-
formData.append(name, value
|
645
|
+
if (name && value != null && formData.get(name) != value) {
|
646
|
+
formData.append(name, value);
|
581
647
|
}
|
582
648
|
return formData;
|
583
649
|
}
|
@@ -602,6 +668,48 @@ function responseSucceededWithoutRedirect(response) {
|
|
602
668
|
return response.statusCode == 200 && !response.redirected;
|
603
669
|
}
|
604
670
|
|
671
|
+
class Snapshot {
|
672
|
+
constructor(element) {
|
673
|
+
this.element = element;
|
674
|
+
}
|
675
|
+
get children() {
|
676
|
+
return [ ...this.element.children ];
|
677
|
+
}
|
678
|
+
hasAnchor(anchor) {
|
679
|
+
return this.getElementForAnchor(anchor) != null;
|
680
|
+
}
|
681
|
+
getElementForAnchor(anchor) {
|
682
|
+
try {
|
683
|
+
return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
684
|
+
} catch (_a) {
|
685
|
+
return null;
|
686
|
+
}
|
687
|
+
}
|
688
|
+
get isConnected() {
|
689
|
+
return this.element.isConnected;
|
690
|
+
}
|
691
|
+
get firstAutofocusableElement() {
|
692
|
+
return this.element.querySelector("[autofocus]");
|
693
|
+
}
|
694
|
+
get permanentElements() {
|
695
|
+
return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
|
696
|
+
}
|
697
|
+
getPermanentElementById(id) {
|
698
|
+
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
|
699
|
+
}
|
700
|
+
getPermanentElementMapForSnapshot(snapshot) {
|
701
|
+
const permanentElementMap = {};
|
702
|
+
for (const currentPermanentElement of this.permanentElements) {
|
703
|
+
const {id: id} = currentPermanentElement;
|
704
|
+
const newPermanentElement = snapshot.getPermanentElementById(id);
|
705
|
+
if (newPermanentElement) {
|
706
|
+
permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
|
707
|
+
}
|
708
|
+
}
|
709
|
+
return permanentElementMap;
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
605
713
|
class FormInterceptor {
|
606
714
|
constructor(delegate, element) {
|
607
715
|
this.submitBubbled = event => {
|
@@ -626,6 +734,82 @@ class FormInterceptor {
|
|
626
734
|
}
|
627
735
|
}
|
628
736
|
|
737
|
+
class View {
|
738
|
+
constructor(delegate, element) {
|
739
|
+
this.delegate = delegate;
|
740
|
+
this.element = element;
|
741
|
+
}
|
742
|
+
scrollToAnchor(anchor) {
|
743
|
+
const element = this.snapshot.getElementForAnchor(anchor);
|
744
|
+
if (element) {
|
745
|
+
this.scrollToElement(element);
|
746
|
+
} else {
|
747
|
+
this.scrollToPosition({
|
748
|
+
x: 0,
|
749
|
+
y: 0
|
750
|
+
});
|
751
|
+
}
|
752
|
+
}
|
753
|
+
scrollToElement(element) {
|
754
|
+
element.scrollIntoView();
|
755
|
+
}
|
756
|
+
scrollToPosition({x: x, y: y}) {
|
757
|
+
this.scrollRoot.scrollTo(x, y);
|
758
|
+
}
|
759
|
+
get scrollRoot() {
|
760
|
+
return window;
|
761
|
+
}
|
762
|
+
async render(renderer) {
|
763
|
+
if (this.renderer) {
|
764
|
+
throw new Error("rendering is already in progress");
|
765
|
+
}
|
766
|
+
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
767
|
+
if (shouldRender) {
|
768
|
+
try {
|
769
|
+
this.renderer = renderer;
|
770
|
+
this.prepareToRenderSnapshot(renderer);
|
771
|
+
this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
|
772
|
+
await this.renderSnapshot(renderer);
|
773
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
774
|
+
this.finishRenderingSnapshot(renderer);
|
775
|
+
} finally {
|
776
|
+
delete this.renderer;
|
777
|
+
}
|
778
|
+
} else {
|
779
|
+
this.invalidate();
|
780
|
+
}
|
781
|
+
}
|
782
|
+
invalidate() {
|
783
|
+
this.delegate.viewInvalidated();
|
784
|
+
}
|
785
|
+
prepareToRenderSnapshot(renderer) {
|
786
|
+
this.markAsPreview(renderer.isPreview);
|
787
|
+
renderer.prepareToRender();
|
788
|
+
}
|
789
|
+
markAsPreview(isPreview) {
|
790
|
+
if (isPreview) {
|
791
|
+
this.element.setAttribute("data-turbo-preview", "");
|
792
|
+
} else {
|
793
|
+
this.element.removeAttribute("data-turbo-preview");
|
794
|
+
}
|
795
|
+
}
|
796
|
+
async renderSnapshot(renderer) {
|
797
|
+
await renderer.render();
|
798
|
+
}
|
799
|
+
finishRenderingSnapshot(renderer) {
|
800
|
+
renderer.finishRendering();
|
801
|
+
}
|
802
|
+
}
|
803
|
+
|
804
|
+
class FrameView extends View {
|
805
|
+
invalidate() {
|
806
|
+
this.element.innerHTML = "";
|
807
|
+
}
|
808
|
+
get snapshot() {
|
809
|
+
return new Snapshot(this.element);
|
810
|
+
}
|
811
|
+
}
|
812
|
+
|
629
813
|
class LinkInterceptor {
|
630
814
|
constructor(delegate, element) {
|
631
815
|
this.clickBubbled = event => {
|
@@ -640,7 +824,7 @@ class LinkInterceptor {
|
|
640
824
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
|
641
825
|
this.clickEvent.preventDefault();
|
642
826
|
event.preventDefault();
|
643
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
827
|
+
this.convertLinkWithMethodClickToFormSubmission(event.target) || this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
644
828
|
}
|
645
829
|
}
|
646
830
|
delete this.clickEvent;
|
@@ -661,183 +845,181 @@ class LinkInterceptor {
|
|
661
845
|
document.removeEventListener("turbo:click", this.linkClicked);
|
662
846
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
663
847
|
}
|
848
|
+
convertLinkWithMethodClickToFormSubmission(link) {
|
849
|
+
var _a;
|
850
|
+
const linkMethod = link.getAttribute("data-turbo-method") || link.getAttribute("data-method");
|
851
|
+
if (linkMethod) {
|
852
|
+
const form = document.createElement("form");
|
853
|
+
form.method = linkMethod;
|
854
|
+
form.action = link.getAttribute("href") || "undefined";
|
855
|
+
(_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
|
856
|
+
return dispatch("submit", {
|
857
|
+
target: form
|
858
|
+
});
|
859
|
+
} else {
|
860
|
+
return false;
|
861
|
+
}
|
862
|
+
}
|
664
863
|
respondsToEventTarget(target) {
|
665
864
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
666
865
|
return element && element.closest("turbo-frame, html") == this.element;
|
667
866
|
}
|
668
867
|
}
|
669
868
|
|
670
|
-
class
|
671
|
-
constructor(
|
672
|
-
this.
|
673
|
-
this.element = element;
|
674
|
-
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
675
|
-
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
676
|
-
this.formInterceptor = new FormInterceptor(this, this.element);
|
677
|
-
}
|
678
|
-
connect() {
|
679
|
-
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
680
|
-
this.appearanceObserver.start();
|
681
|
-
}
|
682
|
-
this.linkInterceptor.start();
|
683
|
-
this.formInterceptor.start();
|
869
|
+
class Bardo {
|
870
|
+
constructor(permanentElementMap) {
|
871
|
+
this.permanentElementMap = permanentElementMap;
|
684
872
|
}
|
685
|
-
|
686
|
-
this
|
687
|
-
|
688
|
-
|
873
|
+
static preservingPermanentElements(permanentElementMap, callback) {
|
874
|
+
const bardo = new this(permanentElementMap);
|
875
|
+
bardo.enter();
|
876
|
+
callback();
|
877
|
+
bardo.leave();
|
689
878
|
}
|
690
|
-
|
691
|
-
|
692
|
-
this.
|
879
|
+
enter() {
|
880
|
+
for (const id in this.permanentElementMap) {
|
881
|
+
const [, newPermanentElement] = this.permanentElementMap[id];
|
882
|
+
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
693
883
|
}
|
694
884
|
}
|
695
|
-
|
696
|
-
|
697
|
-
this.
|
698
|
-
|
699
|
-
this.
|
700
|
-
this.loadSourceURL();
|
885
|
+
leave() {
|
886
|
+
for (const id in this.permanentElementMap) {
|
887
|
+
const [currentPermanentElement] = this.permanentElementMap[id];
|
888
|
+
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
889
|
+
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
701
890
|
}
|
702
891
|
}
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
this.loadingURL = this.sourceURL;
|
707
|
-
this.element.loaded = this.visit(this.sourceURL);
|
708
|
-
this.appearanceObserver.stop();
|
709
|
-
await this.element.loaded;
|
710
|
-
} finally {
|
711
|
-
delete this.loadingURL;
|
712
|
-
}
|
713
|
-
}
|
892
|
+
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
893
|
+
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
894
|
+
permanentElement.replaceWith(placeholder);
|
714
895
|
}
|
715
|
-
|
716
|
-
const
|
717
|
-
|
718
|
-
const element = await this.extractForeignFrameElement(fragment);
|
719
|
-
await nextAnimationFrame();
|
720
|
-
this.loadFrameElement(element);
|
721
|
-
this.scrollFrameIntoView(element);
|
722
|
-
await nextAnimationFrame();
|
723
|
-
this.focusFirstAutofocusableElement();
|
724
|
-
}
|
896
|
+
replaceCurrentPermanentElementWithClone(permanentElement) {
|
897
|
+
const clone = permanentElement.cloneNode(true);
|
898
|
+
permanentElement.replaceWith(clone);
|
725
899
|
}
|
726
|
-
|
727
|
-
this.
|
900
|
+
replacePlaceholderWithPermanentElement(permanentElement) {
|
901
|
+
const placeholder = this.getPlaceholderById(permanentElement.id);
|
902
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
|
728
903
|
}
|
729
|
-
|
730
|
-
return this.
|
904
|
+
getPlaceholderById(id) {
|
905
|
+
return this.placeholders.find((element => element.content == id));
|
731
906
|
}
|
732
|
-
|
733
|
-
|
907
|
+
get placeholders() {
|
908
|
+
return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
|
734
909
|
}
|
735
|
-
|
736
|
-
|
910
|
+
}
|
911
|
+
|
912
|
+
function createPlaceholderForPermanentElement(permanentElement) {
|
913
|
+
const element = document.createElement("meta");
|
914
|
+
element.setAttribute("name", "turbo-permanent-placeholder");
|
915
|
+
element.setAttribute("content", permanentElement.id);
|
916
|
+
return element;
|
917
|
+
}
|
918
|
+
|
919
|
+
class Renderer {
|
920
|
+
constructor(currentSnapshot, newSnapshot, isPreview) {
|
921
|
+
this.currentSnapshot = currentSnapshot;
|
922
|
+
this.newSnapshot = newSnapshot;
|
923
|
+
this.isPreview = isPreview;
|
924
|
+
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
925
|
+
resolve: resolve,
|
926
|
+
reject: reject
|
927
|
+
}));
|
737
928
|
}
|
738
|
-
|
739
|
-
|
740
|
-
|
929
|
+
get shouldRender() {
|
930
|
+
return true;
|
931
|
+
}
|
932
|
+
prepareToRender() {
|
933
|
+
return;
|
934
|
+
}
|
935
|
+
finishRendering() {
|
936
|
+
if (this.resolvingFunctions) {
|
937
|
+
this.resolvingFunctions.resolve();
|
938
|
+
delete this.resolvingFunctions;
|
741
939
|
}
|
742
|
-
|
743
|
-
|
744
|
-
|
940
|
+
}
|
941
|
+
createScriptElement(element) {
|
942
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
943
|
+
return element;
|
745
944
|
} else {
|
746
|
-
|
945
|
+
const createdScriptElement = document.createElement("script");
|
946
|
+
if (this.cspNonce) {
|
947
|
+
createdScriptElement.nonce = this.cspNonce;
|
948
|
+
}
|
949
|
+
createdScriptElement.textContent = element.textContent;
|
950
|
+
createdScriptElement.async = false;
|
951
|
+
copyElementAttributes(createdScriptElement, element);
|
952
|
+
return createdScriptElement;
|
747
953
|
}
|
748
954
|
}
|
749
|
-
|
750
|
-
|
751
|
-
"Turbo-Frame": this.id
|
752
|
-
};
|
955
|
+
preservingPermanentElements(callback) {
|
956
|
+
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
753
957
|
}
|
754
|
-
|
755
|
-
this.
|
958
|
+
focusFirstAutofocusableElement() {
|
959
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
960
|
+
if (elementIsFocusable(element)) {
|
961
|
+
element.focus();
|
962
|
+
}
|
756
963
|
}
|
757
|
-
|
758
|
-
this.
|
964
|
+
get connectedSnapshot() {
|
965
|
+
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
759
966
|
}
|
760
|
-
|
761
|
-
|
762
|
-
this.resolveVisitPromise();
|
967
|
+
get currentElement() {
|
968
|
+
return this.currentSnapshot.element;
|
763
969
|
}
|
764
|
-
|
765
|
-
|
766
|
-
this.resolveVisitPromise();
|
970
|
+
get newElement() {
|
971
|
+
return this.newSnapshot.element;
|
767
972
|
}
|
768
|
-
|
769
|
-
|
770
|
-
this.resolveVisitPromise();
|
973
|
+
get permanentElementMap() {
|
974
|
+
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
771
975
|
}
|
772
|
-
|
773
|
-
|
976
|
+
get cspNonce() {
|
977
|
+
var _a;
|
978
|
+
return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
|
774
979
|
}
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
980
|
+
}
|
981
|
+
|
982
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
983
|
+
for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
|
984
|
+
destinationElement.setAttribute(name, value);
|
779
985
|
}
|
780
|
-
|
781
|
-
|
986
|
+
}
|
987
|
+
|
988
|
+
function elementIsFocusable(element) {
|
989
|
+
return element && typeof element.focus == "function";
|
990
|
+
}
|
991
|
+
|
992
|
+
class FrameRenderer extends Renderer {
|
993
|
+
get shouldRender() {
|
994
|
+
return true;
|
782
995
|
}
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
const request = new FetchRequest(this, FetchMethod.get, location);
|
788
|
-
return new Promise((resolve => {
|
789
|
-
this.resolveVisitPromise = () => {
|
790
|
-
this.resolveVisitPromise = () => {};
|
791
|
-
resolve();
|
792
|
-
};
|
793
|
-
request.perform();
|
996
|
+
async render() {
|
997
|
+
await nextAnimationFrame();
|
998
|
+
this.preservingPermanentElements((() => {
|
999
|
+
this.loadFrameElement();
|
794
1000
|
}));
|
1001
|
+
this.scrollFrameIntoView();
|
1002
|
+
await nextAnimationFrame();
|
1003
|
+
this.focusFirstAutofocusableElement();
|
1004
|
+
await nextAnimationFrame();
|
1005
|
+
this.activateScriptElements();
|
795
1006
|
}
|
796
|
-
|
797
|
-
const frame = this.findFrameElement(element);
|
798
|
-
frame.src = url;
|
799
|
-
}
|
800
|
-
findFrameElement(element) {
|
801
|
-
var _a;
|
802
|
-
const id = element.getAttribute("data-turbo-frame");
|
803
|
-
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
804
|
-
}
|
805
|
-
async extractForeignFrameElement(container) {
|
806
|
-
let element;
|
807
|
-
const id = CSS.escape(this.id);
|
808
|
-
if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
|
809
|
-
return element;
|
810
|
-
}
|
811
|
-
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
|
812
|
-
await element.loaded;
|
813
|
-
return await this.extractForeignFrameElement(element);
|
814
|
-
}
|
815
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
816
|
-
return new FrameElement;
|
817
|
-
}
|
818
|
-
loadFrameElement(frameElement) {
|
1007
|
+
loadFrameElement() {
|
819
1008
|
var _a;
|
820
1009
|
const destinationRange = document.createRange();
|
821
|
-
destinationRange.selectNodeContents(this.
|
1010
|
+
destinationRange.selectNodeContents(this.currentElement);
|
822
1011
|
destinationRange.deleteContents();
|
1012
|
+
const frameElement = this.newElement;
|
823
1013
|
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
824
1014
|
if (sourceRange) {
|
825
1015
|
sourceRange.selectNodeContents(frameElement);
|
826
|
-
this.
|
1016
|
+
this.currentElement.appendChild(sourceRange.extractContents());
|
827
1017
|
}
|
828
1018
|
}
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
return true;
|
834
|
-
}
|
835
|
-
return false;
|
836
|
-
}
|
837
|
-
scrollFrameIntoView(frame) {
|
838
|
-
if (this.element.autoscroll || frame.autoscroll) {
|
839
|
-
const element = this.element.firstElementChild;
|
840
|
-
const block = readScrollLogicalPosition(this.element.getAttribute("data-autoscroll-block"), "end");
|
1019
|
+
scrollFrameIntoView() {
|
1020
|
+
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
1021
|
+
const element = this.currentElement.firstElementChild;
|
1022
|
+
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
841
1023
|
if (element) {
|
842
1024
|
element.scrollIntoView({
|
843
1025
|
block: block
|
@@ -847,49 +1029,14 @@ class FrameController {
|
|
847
1029
|
}
|
848
1030
|
return false;
|
849
1031
|
}
|
850
|
-
|
851
|
-
const
|
852
|
-
|
853
|
-
|
854
|
-
}
|
855
|
-
if (id) {
|
856
|
-
const frameElement = getFrameElementById(id);
|
857
|
-
if (frameElement) {
|
858
|
-
return !frameElement.disabled;
|
859
|
-
}
|
1032
|
+
activateScriptElements() {
|
1033
|
+
for (const inertScriptElement of this.newScriptElements) {
|
1034
|
+
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
1035
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
860
1036
|
}
|
861
|
-
return true;
|
862
|
-
}
|
863
|
-
get firstAutofocusableElement() {
|
864
|
-
const element = this.element.querySelector("[autofocus]");
|
865
|
-
return element instanceof HTMLElement ? element : null;
|
866
|
-
}
|
867
|
-
get id() {
|
868
|
-
return this.element.id;
|
869
|
-
}
|
870
|
-
get enabled() {
|
871
|
-
return !this.element.disabled;
|
872
|
-
}
|
873
|
-
get sourceURL() {
|
874
|
-
return this.element.src;
|
875
|
-
}
|
876
|
-
get loadingStyle() {
|
877
|
-
return this.element.loading;
|
878
|
-
}
|
879
|
-
get isLoading() {
|
880
|
-
return this.formSubmission !== undefined || this.loadingURL !== undefined;
|
881
|
-
}
|
882
|
-
get isActive() {
|
883
|
-
return this.element.isActive;
|
884
1037
|
}
|
885
|
-
|
886
|
-
|
887
|
-
function getFrameElementById(id) {
|
888
|
-
if (id != null) {
|
889
|
-
const element = document.getElementById(id);
|
890
|
-
if (element instanceof FrameElement) {
|
891
|
-
return element;
|
892
|
-
}
|
1038
|
+
get newScriptElements() {
|
1039
|
+
return this.currentElement.querySelectorAll("script");
|
893
1040
|
}
|
894
1041
|
}
|
895
1042
|
|
@@ -901,144 +1048,6 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
901
1048
|
}
|
902
1049
|
}
|
903
1050
|
|
904
|
-
function fragmentFromHTML(html) {
|
905
|
-
if (html) {
|
906
|
-
const foreignDocument = document.implementation.createHTMLDocument();
|
907
|
-
return foreignDocument.createRange().createContextualFragment(html);
|
908
|
-
}
|
909
|
-
}
|
910
|
-
|
911
|
-
function activateElement(element) {
|
912
|
-
if (element && element.ownerDocument !== document) {
|
913
|
-
element = document.importNode(element, true);
|
914
|
-
}
|
915
|
-
if (element instanceof FrameElement) {
|
916
|
-
return element;
|
917
|
-
}
|
918
|
-
}
|
919
|
-
|
920
|
-
const StreamActions = {
|
921
|
-
append() {
|
922
|
-
var _a;
|
923
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
924
|
-
},
|
925
|
-
prepend() {
|
926
|
-
var _a;
|
927
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
928
|
-
},
|
929
|
-
remove() {
|
930
|
-
var _a;
|
931
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
932
|
-
},
|
933
|
-
replace() {
|
934
|
-
var _a;
|
935
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
936
|
-
},
|
937
|
-
update() {
|
938
|
-
if (this.targetElement) {
|
939
|
-
this.targetElement.innerHTML = "";
|
940
|
-
this.targetElement.append(this.templateContent);
|
941
|
-
}
|
942
|
-
}
|
943
|
-
};
|
944
|
-
|
945
|
-
class StreamElement extends HTMLElement {
|
946
|
-
async connectedCallback() {
|
947
|
-
try {
|
948
|
-
await this.render();
|
949
|
-
} catch (error) {
|
950
|
-
console.error(error);
|
951
|
-
} finally {
|
952
|
-
this.disconnect();
|
953
|
-
}
|
954
|
-
}
|
955
|
-
async render() {
|
956
|
-
var _a;
|
957
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
958
|
-
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
959
|
-
await nextAnimationFrame();
|
960
|
-
this.performAction();
|
961
|
-
}
|
962
|
-
})();
|
963
|
-
}
|
964
|
-
disconnect() {
|
965
|
-
try {
|
966
|
-
this.remove();
|
967
|
-
} catch (_a) {}
|
968
|
-
}
|
969
|
-
get performAction() {
|
970
|
-
if (this.action) {
|
971
|
-
const actionFunction = StreamActions[this.action];
|
972
|
-
if (actionFunction) {
|
973
|
-
return actionFunction;
|
974
|
-
}
|
975
|
-
this.raise("unknown action");
|
976
|
-
}
|
977
|
-
this.raise("action attribute is missing");
|
978
|
-
}
|
979
|
-
get targetElement() {
|
980
|
-
var _a;
|
981
|
-
if (this.target) {
|
982
|
-
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
983
|
-
}
|
984
|
-
this.raise("target attribute is missing");
|
985
|
-
}
|
986
|
-
get templateContent() {
|
987
|
-
return this.templateElement.content;
|
988
|
-
}
|
989
|
-
get templateElement() {
|
990
|
-
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
991
|
-
return this.firstElementChild;
|
992
|
-
}
|
993
|
-
this.raise("first child element must be a <template> element");
|
994
|
-
}
|
995
|
-
get action() {
|
996
|
-
return this.getAttribute("action");
|
997
|
-
}
|
998
|
-
get target() {
|
999
|
-
return this.getAttribute("target");
|
1000
|
-
}
|
1001
|
-
raise(message) {
|
1002
|
-
throw new Error(`${this.description}: ${message}`);
|
1003
|
-
}
|
1004
|
-
get description() {
|
1005
|
-
var _a, _b;
|
1006
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
1007
|
-
}
|
1008
|
-
get beforeRenderEvent() {
|
1009
|
-
return new CustomEvent("turbo:before-stream-render", {
|
1010
|
-
bubbles: true,
|
1011
|
-
cancelable: true
|
1012
|
-
});
|
1013
|
-
}
|
1014
|
-
}
|
1015
|
-
|
1016
|
-
FrameElement.delegateConstructor = FrameController;
|
1017
|
-
|
1018
|
-
customElements.define("turbo-frame", FrameElement);
|
1019
|
-
|
1020
|
-
customElements.define("turbo-stream", StreamElement);
|
1021
|
-
|
1022
|
-
(() => {
|
1023
|
-
let element = document.currentScript;
|
1024
|
-
if (!element) return;
|
1025
|
-
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
1026
|
-
while (element = element.parentElement) {
|
1027
|
-
if (element == document.body) {
|
1028
|
-
return console.warn(unindent`
|
1029
|
-
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
1030
|
-
|
1031
|
-
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
1032
|
-
|
1033
|
-
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
1034
|
-
|
1035
|
-
——
|
1036
|
-
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
1037
|
-
`, element.outerHTML);
|
1038
|
-
}
|
1039
|
-
}
|
1040
|
-
})();
|
1041
|
-
|
1042
1051
|
class ProgressBar {
|
1043
1052
|
constructor() {
|
1044
1053
|
this.hiding = false;
|
@@ -1138,9 +1147,10 @@ class ProgressBar {
|
|
1138
1147
|
|
1139
1148
|
ProgressBar.animationDuration = 300;
|
1140
1149
|
|
1141
|
-
class
|
1142
|
-
constructor(
|
1143
|
-
|
1150
|
+
class HeadSnapshot extends Snapshot {
|
1151
|
+
constructor() {
|
1152
|
+
super(...arguments);
|
1153
|
+
this.detailsByOuterHTML = this.children.reduce(((result, element) => {
|
1144
1154
|
const {outerHTML: outerHTML} = element;
|
1145
1155
|
const details = outerHTML in result ? result[outerHTML] : {
|
1146
1156
|
type: elementType(element),
|
@@ -1154,23 +1164,19 @@ class HeadDetails {
|
|
1154
1164
|
});
|
1155
1165
|
}), {});
|
1156
1166
|
}
|
1157
|
-
|
1158
|
-
const children = headElement ? [ ...headElement.children ] : [];
|
1159
|
-
return new this(children);
|
1160
|
-
}
|
1161
|
-
getTrackedElementSignature() {
|
1167
|
+
get trackedElementSignature() {
|
1162
1168
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1163
1169
|
}
|
1164
|
-
|
1165
|
-
return this.
|
1170
|
+
getScriptElementsNotInSnapshot(snapshot) {
|
1171
|
+
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
1166
1172
|
}
|
1167
|
-
|
1168
|
-
return this.
|
1173
|
+
getStylesheetElementsNotInSnapshot(snapshot) {
|
1174
|
+
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
1169
1175
|
}
|
1170
|
-
|
1171
|
-
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in
|
1176
|
+
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
1177
|
+
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
|
1172
1178
|
}
|
1173
|
-
|
1179
|
+
get provisionalElements() {
|
1174
1180
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
1175
1181
|
const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
|
1176
1182
|
if (type == null && !tracked) {
|
@@ -1221,75 +1227,45 @@ function elementIsMetaElementWithName(element, name) {
|
|
1221
1227
|
return tagName == "meta" && element.getAttribute("name") == name;
|
1222
1228
|
}
|
1223
1229
|
|
1224
|
-
class Snapshot {
|
1225
|
-
constructor(
|
1226
|
-
|
1227
|
-
this.
|
1228
|
-
}
|
1229
|
-
static wrap(value) {
|
1230
|
-
if (value instanceof this) {
|
1231
|
-
return value;
|
1232
|
-
} else if (typeof value == "string") {
|
1233
|
-
return this.fromHTMLString(value);
|
1234
|
-
} else {
|
1235
|
-
return this.fromHTMLElement(value);
|
1236
|
-
}
|
1237
|
-
}
|
1238
|
-
static fromHTMLString(html) {
|
1239
|
-
const {documentElement: documentElement} = (new DOMParser).parseFromString(html, "text/html");
|
1240
|
-
return this.fromHTMLElement(documentElement);
|
1230
|
+
class PageSnapshot extends Snapshot {
|
1231
|
+
constructor(element, headSnapshot) {
|
1232
|
+
super(element);
|
1233
|
+
this.headSnapshot = headSnapshot;
|
1241
1234
|
}
|
1242
|
-
static
|
1243
|
-
|
1244
|
-
const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
|
1245
|
-
const headDetails = HeadDetails.fromHeadElement(headElement);
|
1246
|
-
return new this(headDetails, bodyElement);
|
1235
|
+
static fromHTMLString(html = "") {
|
1236
|
+
return this.fromDocument(parseHTMLDocument(html));
|
1247
1237
|
}
|
1248
|
-
|
1249
|
-
|
1250
|
-
return new Snapshot(this.headDetails, bodyElement);
|
1251
|
-
}
|
1252
|
-
getRootLocation() {
|
1253
|
-
const root = this.getSetting("root", "/");
|
1254
|
-
return new Location(root);
|
1255
|
-
}
|
1256
|
-
getCacheControlValue() {
|
1257
|
-
return this.getSetting("cache-control");
|
1258
|
-
}
|
1259
|
-
getElementForAnchor(anchor) {
|
1260
|
-
try {
|
1261
|
-
return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
1262
|
-
} catch (_a) {
|
1263
|
-
return null;
|
1264
|
-
}
|
1238
|
+
static fromElement(element) {
|
1239
|
+
return this.fromDocument(element.ownerDocument);
|
1265
1240
|
}
|
1266
|
-
|
1267
|
-
return
|
1241
|
+
static fromDocument({head: head, body: body}) {
|
1242
|
+
return new this(body, new HeadSnapshot(head));
|
1268
1243
|
}
|
1269
|
-
|
1270
|
-
return this.
|
1244
|
+
clone() {
|
1245
|
+
return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
|
1271
1246
|
}
|
1272
|
-
|
1273
|
-
return this.
|
1247
|
+
get headElement() {
|
1248
|
+
return this.headSnapshot.element;
|
1274
1249
|
}
|
1275
|
-
|
1276
|
-
|
1250
|
+
get rootLocation() {
|
1251
|
+
var _a;
|
1252
|
+
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
1253
|
+
return expandURL(root);
|
1277
1254
|
}
|
1278
|
-
|
1279
|
-
return this.
|
1255
|
+
get cacheControlValue() {
|
1256
|
+
return this.getSetting("cache-control");
|
1280
1257
|
}
|
1281
|
-
isPreviewable() {
|
1282
|
-
return this.
|
1258
|
+
get isPreviewable() {
|
1259
|
+
return this.cacheControlValue != "no-preview";
|
1283
1260
|
}
|
1284
|
-
isCacheable() {
|
1285
|
-
return this.
|
1261
|
+
get isCacheable() {
|
1262
|
+
return this.cacheControlValue != "no-cache";
|
1286
1263
|
}
|
1287
|
-
isVisitable() {
|
1264
|
+
get isVisitable() {
|
1288
1265
|
return this.getSetting("visit-control") != "reload";
|
1289
1266
|
}
|
1290
|
-
getSetting(name
|
1291
|
-
|
1292
|
-
return value == null ? defaultValue : value;
|
1267
|
+
getSetting(name) {
|
1268
|
+
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
1293
1269
|
}
|
1294
1270
|
}
|
1295
1271
|
|
@@ -1334,16 +1310,6 @@ class Visit {
|
|
1334
1310
|
this.scrolled = false;
|
1335
1311
|
this.snapshotCached = false;
|
1336
1312
|
this.state = VisitState.initialized;
|
1337
|
-
this.performScroll = () => {
|
1338
|
-
if (!this.scrolled) {
|
1339
|
-
if (this.action == "restore") {
|
1340
|
-
this.scrollToRestoredPosition() || this.scrollToTop();
|
1341
|
-
} else {
|
1342
|
-
this.scrollToAnchor() || this.scrollToTop();
|
1343
|
-
}
|
1344
|
-
this.scrolled = true;
|
1345
|
-
}
|
1346
|
-
};
|
1347
1313
|
this.delegate = delegate;
|
1348
1314
|
this.location = location;
|
1349
1315
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
@@ -1398,8 +1364,9 @@ class Visit {
|
|
1398
1364
|
}
|
1399
1365
|
}
|
1400
1366
|
changeHistory() {
|
1367
|
+
var _a;
|
1401
1368
|
if (!this.historyChanged) {
|
1402
|
-
const actionForHistory = this.location.
|
1369
|
+
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
1403
1370
|
const method = this.getHistoryMethodForAction(actionForHistory);
|
1404
1371
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1405
1372
|
this.historyChanged = true;
|
@@ -1442,18 +1409,14 @@ class Visit {
|
|
1442
1409
|
loadResponse() {
|
1443
1410
|
if (this.response) {
|
1444
1411
|
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
1445
|
-
this.render((() => {
|
1412
|
+
this.render((async () => {
|
1446
1413
|
this.cacheSnapshot();
|
1447
1414
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1448
|
-
this.view.
|
1449
|
-
snapshot: Snapshot.fromHTMLString(responseHTML)
|
1450
|
-
}, this.performScroll);
|
1415
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
1451
1416
|
this.adapter.visitRendered(this);
|
1452
1417
|
this.complete();
|
1453
1418
|
} else {
|
1454
|
-
this.view.
|
1455
|
-
error: responseHTML
|
1456
|
-
}, this.performScroll);
|
1419
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
1457
1420
|
this.adapter.visitRendered(this);
|
1458
1421
|
this.fail();
|
1459
1422
|
}
|
@@ -1462,15 +1425,15 @@ class Visit {
|
|
1462
1425
|
}
|
1463
1426
|
getCachedSnapshot() {
|
1464
1427
|
const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
|
1465
|
-
if (snapshot && (!this.location
|
1466
|
-
if (this.action == "restore" || snapshot.isPreviewable
|
1428
|
+
if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
|
1429
|
+
if (this.action == "restore" || snapshot.isPreviewable) {
|
1467
1430
|
return snapshot;
|
1468
1431
|
}
|
1469
1432
|
}
|
1470
1433
|
}
|
1471
1434
|
getPreloadedSnapshot() {
|
1472
1435
|
if (this.snapshotHTML) {
|
1473
|
-
return
|
1436
|
+
return PageSnapshot.fromHTMLString(this.snapshotHTML);
|
1474
1437
|
}
|
1475
1438
|
}
|
1476
1439
|
hasCachedSnapshot() {
|
@@ -1480,12 +1443,9 @@ class Visit {
|
|
1480
1443
|
const snapshot = this.getCachedSnapshot();
|
1481
1444
|
if (snapshot) {
|
1482
1445
|
const isPreview = this.shouldIssueRequest();
|
1483
|
-
this.render((() => {
|
1446
|
+
this.render((async () => {
|
1484
1447
|
this.cacheSnapshot();
|
1485
|
-
this.view.
|
1486
|
-
snapshot: snapshot,
|
1487
|
-
isPreview: isPreview
|
1488
|
-
}, this.performScroll);
|
1448
|
+
await this.view.renderPage(snapshot, isPreview);
|
1489
1449
|
this.adapter.visitRendered(this);
|
1490
1450
|
if (!isPreview) {
|
1491
1451
|
this.complete();
|
@@ -1539,16 +1499,26 @@ class Visit {
|
|
1539
1499
|
requestFinished() {
|
1540
1500
|
this.finishRequest();
|
1541
1501
|
}
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1502
|
+
performScroll() {
|
1503
|
+
if (!this.scrolled) {
|
1504
|
+
if (this.action == "restore") {
|
1505
|
+
this.scrollToRestoredPosition() || this.scrollToTop();
|
1506
|
+
} else {
|
1507
|
+
this.scrollToAnchor() || this.scrollToTop();
|
1508
|
+
}
|
1509
|
+
this.scrolled = true;
|
1510
|
+
}
|
1511
|
+
}
|
1512
|
+
scrollToRestoredPosition() {
|
1513
|
+
const {scrollPosition: scrollPosition} = this.restorationData;
|
1514
|
+
if (scrollPosition) {
|
1545
1515
|
this.view.scrollToPosition(scrollPosition);
|
1546
1516
|
return true;
|
1547
1517
|
}
|
1548
1518
|
}
|
1549
1519
|
scrollToAnchor() {
|
1550
|
-
if (this.location
|
1551
|
-
this.view.scrollToAnchor(this.location
|
1520
|
+
if (getAnchor(this.location)) {
|
1521
|
+
this.view.scrollToAnchor(getAnchor(this.location));
|
1552
1522
|
return true;
|
1553
1523
|
}
|
1554
1524
|
}
|
@@ -1586,12 +1556,14 @@ class Visit {
|
|
1586
1556
|
this.snapshotCached = true;
|
1587
1557
|
}
|
1588
1558
|
}
|
1589
|
-
render(callback) {
|
1559
|
+
async render(callback) {
|
1590
1560
|
this.cancelRender();
|
1591
|
-
|
1592
|
-
|
1593
|
-
callback.call(this);
|
1561
|
+
await new Promise((resolve => {
|
1562
|
+
this.frame = requestAnimationFrame((() => resolve()));
|
1594
1563
|
}));
|
1564
|
+
callback();
|
1565
|
+
delete this.frame;
|
1566
|
+
this.performScroll();
|
1595
1567
|
}
|
1596
1568
|
cancelRender() {
|
1597
1569
|
if (this.frame) {
|
@@ -1673,6 +1645,30 @@ class BrowserAdapter {
|
|
1673
1645
|
}
|
1674
1646
|
}
|
1675
1647
|
|
1648
|
+
class CacheObserver {
|
1649
|
+
constructor() {
|
1650
|
+
this.started = false;
|
1651
|
+
}
|
1652
|
+
start() {
|
1653
|
+
if (!this.started) {
|
1654
|
+
this.started = true;
|
1655
|
+
addEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1656
|
+
}
|
1657
|
+
}
|
1658
|
+
stop() {
|
1659
|
+
if (this.started) {
|
1660
|
+
this.started = false;
|
1661
|
+
removeEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1662
|
+
}
|
1663
|
+
}
|
1664
|
+
removeStaleElements() {
|
1665
|
+
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
1666
|
+
for (const element of staleElements) {
|
1667
|
+
element.remove();
|
1668
|
+
}
|
1669
|
+
}
|
1670
|
+
}
|
1671
|
+
|
1676
1672
|
class FormSubmitObserver {
|
1677
1673
|
constructor(delegate) {
|
1678
1674
|
this.started = false;
|
@@ -1766,11 +1762,10 @@ class History {
|
|
1766
1762
|
if (this.shouldHandlePopState()) {
|
1767
1763
|
const {turbo: turbo} = event.state || {};
|
1768
1764
|
if (turbo) {
|
1769
|
-
|
1770
|
-
this.location = location;
|
1765
|
+
this.location = new URL(window.location.href);
|
1771
1766
|
const {restorationIdentifier: restorationIdentifier} = turbo;
|
1772
1767
|
this.restorationIdentifier = restorationIdentifier;
|
1773
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
|
1768
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
1774
1769
|
}
|
1775
1770
|
}
|
1776
1771
|
};
|
@@ -1785,7 +1780,7 @@ class History {
|
|
1785
1780
|
addEventListener("popstate", this.onPopState, false);
|
1786
1781
|
addEventListener("load", this.onPageLoad, false);
|
1787
1782
|
this.started = true;
|
1788
|
-
this.replace(
|
1783
|
+
this.replace(new URL(window.location.href));
|
1789
1784
|
}
|
1790
1785
|
}
|
1791
1786
|
stop() {
|
@@ -1807,7 +1802,7 @@ class History {
|
|
1807
1802
|
restorationIdentifier: restorationIdentifier
|
1808
1803
|
}
|
1809
1804
|
};
|
1810
|
-
method.call(history, state, "", location.
|
1805
|
+
method.call(history, state, "", location.href);
|
1811
1806
|
this.location = location;
|
1812
1807
|
this.restorationIdentifier = restorationIdentifier;
|
1813
1808
|
}
|
@@ -1882,10 +1877,14 @@ class LinkClickObserver {
|
|
1882
1877
|
}
|
1883
1878
|
}
|
1884
1879
|
getLocationForLink(link) {
|
1885
|
-
return
|
1880
|
+
return expandURL(link.getAttribute("href") || "");
|
1886
1881
|
}
|
1887
1882
|
}
|
1888
1883
|
|
1884
|
+
function isAction(action) {
|
1885
|
+
return action == "advance" || action == "replace" || action == "restore";
|
1886
|
+
}
|
1887
|
+
|
1889
1888
|
class Navigator {
|
1890
1889
|
constructor(delegate) {
|
1891
1890
|
this.delegate = delegate;
|
@@ -1895,9 +1894,9 @@ class Navigator {
|
|
1895
1894
|
this.delegate.visitProposedToLocation(location, options);
|
1896
1895
|
}
|
1897
1896
|
}
|
1898
|
-
startVisit(
|
1897
|
+
startVisit(locatable, restorationIdentifier, options = {}) {
|
1899
1898
|
this.stop();
|
1900
|
-
this.currentVisit = new Visit(this,
|
1899
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
1901
1900
|
referrer: this.location
|
1902
1901
|
}, options));
|
1903
1902
|
this.currentVisit.start();
|
@@ -1905,7 +1904,13 @@ class Navigator {
|
|
1905
1904
|
submitForm(form, submitter) {
|
1906
1905
|
this.stop();
|
1907
1906
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
1908
|
-
this.formSubmission.
|
1907
|
+
if (this.formSubmission.isIdempotent) {
|
1908
|
+
this.proposeVisit(this.formSubmission.fetchRequest.url, {
|
1909
|
+
action: this.getActionForFormSubmission(this.formSubmission)
|
1910
|
+
});
|
1911
|
+
} else {
|
1912
|
+
this.formSubmission.start();
|
1913
|
+
}
|
1909
1914
|
}
|
1910
1915
|
stop() {
|
1911
1916
|
if (this.formSubmission) {
|
@@ -1948,14 +1953,14 @@ class Navigator {
|
|
1948
1953
|
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
1949
1954
|
const responseHTML = await fetchResponse.responseHTML;
|
1950
1955
|
if (responseHTML) {
|
1951
|
-
const snapshot =
|
1952
|
-
this.view.
|
1953
|
-
snapshot: snapshot
|
1954
|
-
}, (() => {}));
|
1956
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
1957
|
+
await this.view.renderPage(snapshot);
|
1955
1958
|
this.view.clearSnapshotCache();
|
1956
1959
|
}
|
1957
1960
|
}
|
1958
|
-
formSubmissionErrored(formSubmission, error) {
|
1961
|
+
formSubmissionErrored(formSubmission, error) {
|
1962
|
+
console.error(error);
|
1963
|
+
}
|
1959
1964
|
formSubmissionFinished(formSubmission) {}
|
1960
1965
|
visitStarted(visit) {
|
1961
1966
|
this.delegate.visitStarted(visit);
|
@@ -1969,6 +1974,11 @@ class Navigator {
|
|
1969
1974
|
get restorationIdentifier() {
|
1970
1975
|
return this.history.restorationIdentifier;
|
1971
1976
|
}
|
1977
|
+
getActionForFormSubmission(formSubmission) {
|
1978
|
+
const {formElement: formElement, submitter: submitter} = formSubmission;
|
1979
|
+
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
|
1980
|
+
return isAction(action) ? action : "advance";
|
1981
|
+
}
|
1972
1982
|
}
|
1973
1983
|
|
1974
1984
|
var PageStage;
|
@@ -2061,51 +2071,10 @@ class ScrollObserver {
|
|
2061
2071
|
}
|
2062
2072
|
}
|
2063
2073
|
|
2064
|
-
class StreamMessage {
|
2065
|
-
constructor(html) {
|
2066
|
-
this.templateElement = document.createElement("template");
|
2067
|
-
this.templateElement.innerHTML = html;
|
2068
|
-
}
|
2069
|
-
static wrap(message) {
|
2070
|
-
if (typeof message == "string") {
|
2071
|
-
return new this(message);
|
2072
|
-
} else {
|
2073
|
-
return message;
|
2074
|
-
}
|
2075
|
-
}
|
2076
|
-
get fragment() {
|
2077
|
-
const fragment = document.createDocumentFragment();
|
2078
|
-
for (const element of this.foreignElements) {
|
2079
|
-
fragment.appendChild(document.importNode(element, true));
|
2080
|
-
}
|
2081
|
-
return fragment;
|
2082
|
-
}
|
2083
|
-
get foreignElements() {
|
2084
|
-
return this.templateChildren.reduce(((streamElements, child) => {
|
2085
|
-
if (child.tagName.toLowerCase() == "turbo-stream") {
|
2086
|
-
return [ ...streamElements, child ];
|
2087
|
-
} else {
|
2088
|
-
return streamElements;
|
2089
|
-
}
|
2090
|
-
}), []);
|
2091
|
-
}
|
2092
|
-
get templateChildren() {
|
2093
|
-
return Array.from(this.templateElement.content.children);
|
2094
|
-
}
|
2095
|
-
}
|
2096
|
-
|
2097
2074
|
class StreamObserver {
|
2098
2075
|
constructor(delegate) {
|
2099
2076
|
this.sources = new Set;
|
2100
2077
|
this.started = false;
|
2101
|
-
this.prepareFetchRequest = event => {
|
2102
|
-
var _a;
|
2103
|
-
const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
|
2104
|
-
if (fetchOptions) {
|
2105
|
-
const {headers: headers} = fetchOptions;
|
2106
|
-
headers.Accept = [ "text/vnd.turbo-stream.html", headers.Accept ].join(", ");
|
2107
|
-
}
|
2108
|
-
};
|
2109
2078
|
this.inspectFetchResponse = event => {
|
2110
2079
|
const response = fetchResponseFromEvent(event);
|
2111
2080
|
if (response && fetchResponseIsStream(response)) {
|
@@ -2123,14 +2092,12 @@ class StreamObserver {
|
|
2123
2092
|
start() {
|
2124
2093
|
if (!this.started) {
|
2125
2094
|
this.started = true;
|
2126
|
-
addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
2127
2095
|
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2128
2096
|
}
|
2129
2097
|
}
|
2130
2098
|
stop() {
|
2131
2099
|
if (this.started) {
|
2132
2100
|
this.started = false;
|
2133
|
-
removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
2134
2101
|
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2135
2102
|
}
|
2136
2103
|
}
|
@@ -2171,70 +2138,21 @@ function fetchResponseFromEvent(event) {
|
|
2171
2138
|
function fetchResponseIsStream(response) {
|
2172
2139
|
var _a;
|
2173
2140
|
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
2174
|
-
return
|
2175
|
-
}
|
2176
|
-
|
2177
|
-
function isAction(action) {
|
2178
|
-
return action == "advance" || action == "replace" || action == "restore";
|
2179
|
-
}
|
2180
|
-
|
2181
|
-
class Renderer {
|
2182
|
-
renderView(callback) {
|
2183
|
-
this.delegate.viewWillRender(this.newBody);
|
2184
|
-
callback();
|
2185
|
-
this.delegate.viewRendered(this.newBody);
|
2186
|
-
}
|
2187
|
-
invalidateView() {
|
2188
|
-
this.delegate.viewInvalidated();
|
2189
|
-
}
|
2190
|
-
createScriptElement(element) {
|
2191
|
-
if (element.getAttribute("data-turbo-eval") == "false") {
|
2192
|
-
return element;
|
2193
|
-
} else {
|
2194
|
-
const createdScriptElement = document.createElement("script");
|
2195
|
-
createdScriptElement.textContent = element.textContent;
|
2196
|
-
createdScriptElement.async = false;
|
2197
|
-
copyElementAttributes(createdScriptElement, element);
|
2198
|
-
return createdScriptElement;
|
2199
|
-
}
|
2200
|
-
}
|
2201
|
-
}
|
2202
|
-
|
2203
|
-
function copyElementAttributes(destinationElement, sourceElement) {
|
2204
|
-
for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
|
2205
|
-
destinationElement.setAttribute(name, value);
|
2206
|
-
}
|
2141
|
+
return contentType.startsWith(StreamMessage.contentType);
|
2207
2142
|
}
|
2208
2143
|
|
2209
2144
|
class ErrorRenderer extends Renderer {
|
2210
|
-
|
2211
|
-
|
2212
|
-
this.
|
2213
|
-
this.htmlElement = (() => {
|
2214
|
-
const htmlElement = document.createElement("html");
|
2215
|
-
htmlElement.innerHTML = html;
|
2216
|
-
return htmlElement;
|
2217
|
-
})();
|
2218
|
-
this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
|
2219
|
-
this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
|
2220
|
-
}
|
2221
|
-
static render(delegate, callback, html) {
|
2222
|
-
return new this(delegate, html).render(callback);
|
2223
|
-
}
|
2224
|
-
render(callback) {
|
2225
|
-
this.renderView((() => {
|
2226
|
-
this.replaceHeadAndBody();
|
2227
|
-
this.activateBodyScriptElements();
|
2228
|
-
callback();
|
2229
|
-
}));
|
2145
|
+
async render() {
|
2146
|
+
this.replaceHeadAndBody();
|
2147
|
+
this.activateScriptElements();
|
2230
2148
|
}
|
2231
2149
|
replaceHeadAndBody() {
|
2232
2150
|
const {documentElement: documentElement, head: head, body: body} = document;
|
2233
2151
|
documentElement.replaceChild(this.newHead, head);
|
2234
|
-
documentElement.replaceChild(this.
|
2152
|
+
documentElement.replaceChild(this.newElement, body);
|
2235
2153
|
}
|
2236
|
-
|
2237
|
-
for (const replaceableElement of this.
|
2154
|
+
activateScriptElements() {
|
2155
|
+
for (const replaceableElement of this.scriptElements) {
|
2238
2156
|
const parentNode = replaceableElement.parentNode;
|
2239
2157
|
if (parentNode) {
|
2240
2158
|
const element = this.createScriptElement(replaceableElement);
|
@@ -2242,82 +2160,38 @@ class ErrorRenderer extends Renderer {
|
|
2242
2160
|
}
|
2243
2161
|
}
|
2244
2162
|
}
|
2245
|
-
|
2163
|
+
get newHead() {
|
2164
|
+
return this.newSnapshot.headSnapshot.element;
|
2165
|
+
}
|
2166
|
+
get scriptElements() {
|
2246
2167
|
return [ ...document.documentElement.querySelectorAll("script") ];
|
2247
2168
|
}
|
2248
2169
|
}
|
2249
2170
|
|
2250
|
-
class
|
2251
|
-
|
2252
|
-
this.
|
2253
|
-
this.snapshots = {};
|
2254
|
-
this.size = size;
|
2255
|
-
}
|
2256
|
-
has(location) {
|
2257
|
-
return location.toCacheKey() in this.snapshots;
|
2258
|
-
}
|
2259
|
-
get(location) {
|
2260
|
-
if (this.has(location)) {
|
2261
|
-
const snapshot = this.read(location);
|
2262
|
-
this.touch(location);
|
2263
|
-
return snapshot;
|
2264
|
-
}
|
2265
|
-
}
|
2266
|
-
put(location, snapshot) {
|
2267
|
-
this.write(location, snapshot);
|
2268
|
-
this.touch(location);
|
2269
|
-
return snapshot;
|
2270
|
-
}
|
2271
|
-
clear() {
|
2272
|
-
this.snapshots = {};
|
2273
|
-
}
|
2274
|
-
read(location) {
|
2275
|
-
return this.snapshots[location.toCacheKey()];
|
2171
|
+
class PageRenderer extends Renderer {
|
2172
|
+
get shouldRender() {
|
2173
|
+
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2276
2174
|
}
|
2277
|
-
|
2278
|
-
this.
|
2175
|
+
prepareToRender() {
|
2176
|
+
this.mergeHead();
|
2279
2177
|
}
|
2280
|
-
|
2281
|
-
|
2282
|
-
const index = this.keys.indexOf(key);
|
2283
|
-
if (index > -1) this.keys.splice(index, 1);
|
2284
|
-
this.keys.unshift(key);
|
2285
|
-
this.trim();
|
2178
|
+
async render() {
|
2179
|
+
this.replaceBody();
|
2286
2180
|
}
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2181
|
+
finishRendering() {
|
2182
|
+
super.finishRendering();
|
2183
|
+
if (!this.isPreview) {
|
2184
|
+
this.focusFirstAutofocusableElement();
|
2290
2185
|
}
|
2291
2186
|
}
|
2292
|
-
|
2293
|
-
|
2294
|
-
class SnapshotRenderer extends Renderer {
|
2295
|
-
constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
|
2296
|
-
super();
|
2297
|
-
this.delegate = delegate;
|
2298
|
-
this.currentSnapshot = currentSnapshot;
|
2299
|
-
this.currentHeadDetails = currentSnapshot.headDetails;
|
2300
|
-
this.newSnapshot = newSnapshot;
|
2301
|
-
this.newHeadDetails = newSnapshot.headDetails;
|
2302
|
-
this.newBody = newSnapshot.bodyElement;
|
2303
|
-
this.isPreview = isPreview;
|
2187
|
+
get currentHeadSnapshot() {
|
2188
|
+
return this.currentSnapshot.headSnapshot;
|
2304
2189
|
}
|
2305
|
-
|
2306
|
-
return
|
2190
|
+
get newHeadSnapshot() {
|
2191
|
+
return this.newSnapshot.headSnapshot;
|
2307
2192
|
}
|
2308
|
-
|
2309
|
-
|
2310
|
-
this.mergeHead();
|
2311
|
-
this.renderView((() => {
|
2312
|
-
this.replaceBody();
|
2313
|
-
if (!this.isPreview) {
|
2314
|
-
this.focusFirstAutofocusableElement();
|
2315
|
-
}
|
2316
|
-
callback();
|
2317
|
-
}));
|
2318
|
-
} else {
|
2319
|
-
this.invalidateView();
|
2320
|
-
}
|
2193
|
+
get newElement() {
|
2194
|
+
return this.newSnapshot.element;
|
2321
2195
|
}
|
2322
2196
|
mergeHead() {
|
2323
2197
|
this.copyNewHeadStylesheetElements();
|
@@ -2326,190 +2200,145 @@ class SnapshotRenderer extends Renderer {
|
|
2326
2200
|
this.copyNewHeadProvisionalElements();
|
2327
2201
|
}
|
2328
2202
|
replaceBody() {
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
}
|
2334
|
-
shouldRender() {
|
2335
|
-
return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
|
2203
|
+
this.preservingPermanentElements((() => {
|
2204
|
+
this.activateNewBody();
|
2205
|
+
this.assignNewBody();
|
2206
|
+
}));
|
2336
2207
|
}
|
2337
|
-
trackedElementsAreIdentical() {
|
2338
|
-
return this.
|
2208
|
+
get trackedElementsAreIdentical() {
|
2209
|
+
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
2339
2210
|
}
|
2340
2211
|
copyNewHeadStylesheetElements() {
|
2341
|
-
for (const element of this.
|
2212
|
+
for (const element of this.newHeadStylesheetElements) {
|
2342
2213
|
document.head.appendChild(element);
|
2343
2214
|
}
|
2344
2215
|
}
|
2345
2216
|
copyNewHeadScriptElements() {
|
2346
|
-
for (const element of this.
|
2217
|
+
for (const element of this.newHeadScriptElements) {
|
2347
2218
|
document.head.appendChild(this.createScriptElement(element));
|
2348
2219
|
}
|
2349
2220
|
}
|
2350
2221
|
removeCurrentHeadProvisionalElements() {
|
2351
|
-
for (const element of this.
|
2222
|
+
for (const element of this.currentHeadProvisionalElements) {
|
2352
2223
|
document.head.removeChild(element);
|
2353
2224
|
}
|
2354
2225
|
}
|
2355
2226
|
copyNewHeadProvisionalElements() {
|
2356
|
-
for (const element of this.
|
2227
|
+
for (const element of this.newHeadProvisionalElements) {
|
2357
2228
|
document.head.appendChild(element);
|
2358
2229
|
}
|
2359
2230
|
}
|
2360
|
-
relocateCurrentBodyPermanentElements() {
|
2361
|
-
return this.getCurrentBodyPermanentElements().reduce(((placeholders, permanentElement) => {
|
2362
|
-
const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
|
2363
|
-
if (newElement) {
|
2364
|
-
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
2365
|
-
replaceElementWithElement(permanentElement, placeholder.element);
|
2366
|
-
replaceElementWithElement(newElement, permanentElement);
|
2367
|
-
return [ ...placeholders, placeholder ];
|
2368
|
-
} else {
|
2369
|
-
return placeholders;
|
2370
|
-
}
|
2371
|
-
}), []);
|
2372
|
-
}
|
2373
|
-
replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
2374
|
-
for (const {element: element, permanentElement: permanentElement} of placeholders) {
|
2375
|
-
const clonedElement = permanentElement.cloneNode(true);
|
2376
|
-
replaceElementWithElement(element, clonedElement);
|
2377
|
-
}
|
2378
|
-
}
|
2379
2231
|
activateNewBody() {
|
2380
|
-
document.adoptNode(this.
|
2232
|
+
document.adoptNode(this.newElement);
|
2381
2233
|
this.activateNewBodyScriptElements();
|
2382
2234
|
}
|
2383
2235
|
activateNewBodyScriptElements() {
|
2384
|
-
for (const inertScriptElement of this.
|
2236
|
+
for (const inertScriptElement of this.newBodyScriptElements) {
|
2385
2237
|
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
2386
|
-
|
2238
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
2387
2239
|
}
|
2388
2240
|
}
|
2389
2241
|
assignNewBody() {
|
2390
|
-
if (document.body) {
|
2391
|
-
|
2242
|
+
if (document.body && this.newElement instanceof HTMLBodyElement) {
|
2243
|
+
document.body.replaceWith(this.newElement);
|
2392
2244
|
} else {
|
2393
|
-
document.documentElement.appendChild(this.
|
2394
|
-
}
|
2395
|
-
}
|
2396
|
-
focusFirstAutofocusableElement() {
|
2397
|
-
const element = this.newSnapshot.findFirstAutofocusableElement();
|
2398
|
-
if (elementIsFocusable(element)) {
|
2399
|
-
element.focus();
|
2245
|
+
document.documentElement.appendChild(this.newElement);
|
2400
2246
|
}
|
2401
2247
|
}
|
2402
|
-
|
2403
|
-
return this.
|
2404
|
-
}
|
2405
|
-
getNewHeadScriptElements() {
|
2406
|
-
return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails);
|
2248
|
+
get newHeadStylesheetElements() {
|
2249
|
+
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
2407
2250
|
}
|
2408
|
-
|
2409
|
-
return this.
|
2251
|
+
get newHeadScriptElements() {
|
2252
|
+
return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
|
2410
2253
|
}
|
2411
|
-
|
2412
|
-
return this.
|
2254
|
+
get currentHeadProvisionalElements() {
|
2255
|
+
return this.currentHeadSnapshot.provisionalElements;
|
2413
2256
|
}
|
2414
|
-
|
2415
|
-
return this.
|
2257
|
+
get newHeadProvisionalElements() {
|
2258
|
+
return this.newHeadSnapshot.provisionalElements;
|
2416
2259
|
}
|
2417
|
-
|
2418
|
-
return
|
2260
|
+
get newBodyScriptElements() {
|
2261
|
+
return this.newElement.querySelectorAll("script");
|
2419
2262
|
}
|
2420
2263
|
}
|
2421
2264
|
|
2422
|
-
|
2423
|
-
|
2424
|
-
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
}
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2265
|
+
class SnapshotCache {
|
2266
|
+
constructor(size) {
|
2267
|
+
this.keys = [];
|
2268
|
+
this.snapshots = {};
|
2269
|
+
this.size = size;
|
2270
|
+
}
|
2271
|
+
has(location) {
|
2272
|
+
return toCacheKey(location) in this.snapshots;
|
2273
|
+
}
|
2274
|
+
get(location) {
|
2275
|
+
if (this.has(location)) {
|
2276
|
+
const snapshot = this.read(location);
|
2277
|
+
this.touch(location);
|
2278
|
+
return snapshot;
|
2279
|
+
}
|
2280
|
+
}
|
2281
|
+
put(location, snapshot) {
|
2282
|
+
this.write(location, snapshot);
|
2283
|
+
this.touch(location);
|
2284
|
+
return snapshot;
|
2285
|
+
}
|
2286
|
+
clear() {
|
2287
|
+
this.snapshots = {};
|
2288
|
+
}
|
2289
|
+
read(location) {
|
2290
|
+
return this.snapshots[toCacheKey(location)];
|
2291
|
+
}
|
2292
|
+
write(location, snapshot) {
|
2293
|
+
this.snapshots[toCacheKey(location)] = snapshot;
|
2294
|
+
}
|
2295
|
+
touch(location) {
|
2296
|
+
const key = toCacheKey(location);
|
2297
|
+
const index = this.keys.indexOf(key);
|
2298
|
+
if (index > -1) this.keys.splice(index, 1);
|
2299
|
+
this.keys.unshift(key);
|
2300
|
+
this.trim();
|
2301
|
+
}
|
2302
|
+
trim() {
|
2303
|
+
for (const key of this.keys.splice(this.size)) {
|
2304
|
+
delete this.snapshots[key];
|
2305
|
+
}
|
2436
2306
|
}
|
2437
2307
|
}
|
2438
2308
|
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2442
|
-
|
2443
|
-
class View {
|
2444
|
-
constructor(delegate) {
|
2445
|
-
this.htmlElement = document.documentElement;
|
2309
|
+
class PageView extends View {
|
2310
|
+
constructor() {
|
2311
|
+
super(...arguments);
|
2446
2312
|
this.snapshotCache = new SnapshotCache(10);
|
2447
|
-
this.
|
2313
|
+
this.lastRenderedLocation = new URL(location.href);
|
2448
2314
|
}
|
2449
|
-
|
2450
|
-
|
2451
|
-
|
2452
|
-
getElementForAnchor(anchor) {
|
2453
|
-
return this.getSnapshot().getElementForAnchor(anchor);
|
2315
|
+
renderPage(snapshot, isPreview = false) {
|
2316
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
2317
|
+
return this.render(renderer);
|
2454
2318
|
}
|
2455
|
-
|
2456
|
-
|
2319
|
+
renderError(snapshot) {
|
2320
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
2321
|
+
this.render(renderer);
|
2457
2322
|
}
|
2458
2323
|
clearSnapshotCache() {
|
2459
2324
|
this.snapshotCache.clear();
|
2460
2325
|
}
|
2461
|
-
shouldCacheSnapshot() {
|
2462
|
-
return this.getSnapshot().isCacheable();
|
2463
|
-
}
|
2464
2326
|
async cacheSnapshot() {
|
2465
|
-
if (this.shouldCacheSnapshot
|
2327
|
+
if (this.shouldCacheSnapshot) {
|
2466
2328
|
this.delegate.viewWillCacheSnapshot();
|
2467
|
-
const snapshot = this
|
2468
|
-
|
2469
|
-
await nextMicrotask();
|
2329
|
+
const {snapshot: snapshot, lastRenderedLocation: location} = this;
|
2330
|
+
await nextEventLoopTick();
|
2470
2331
|
this.snapshotCache.put(location, snapshot.clone());
|
2471
2332
|
}
|
2472
2333
|
}
|
2473
2334
|
getCachedSnapshotForLocation(location) {
|
2474
2335
|
return this.snapshotCache.get(location);
|
2475
2336
|
}
|
2476
|
-
|
2477
|
-
this.
|
2478
|
-
if (snapshot) {
|
2479
|
-
this.renderSnapshot(snapshot, isPreview, callback);
|
2480
|
-
} else {
|
2481
|
-
this.renderError(error, callback);
|
2482
|
-
}
|
2483
|
-
}
|
2484
|
-
scrollToAnchor(anchor) {
|
2485
|
-
const element = this.getElementForAnchor(anchor);
|
2486
|
-
if (element) {
|
2487
|
-
this.scrollToElement(element);
|
2488
|
-
} else {
|
2489
|
-
this.scrollToPosition({
|
2490
|
-
x: 0,
|
2491
|
-
y: 0
|
2492
|
-
});
|
2493
|
-
}
|
2494
|
-
}
|
2495
|
-
scrollToElement(element) {
|
2496
|
-
element.scrollIntoView();
|
2337
|
+
get snapshot() {
|
2338
|
+
return PageSnapshot.fromElement(this.element);
|
2497
2339
|
}
|
2498
|
-
|
2499
|
-
|
2500
|
-
}
|
2501
|
-
markAsPreview(isPreview) {
|
2502
|
-
if (isPreview) {
|
2503
|
-
this.htmlElement.setAttribute("data-turbo-preview", "");
|
2504
|
-
} else {
|
2505
|
-
this.htmlElement.removeAttribute("data-turbo-preview");
|
2506
|
-
}
|
2507
|
-
}
|
2508
|
-
renderSnapshot(snapshot, isPreview, callback) {
|
2509
|
-
SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
|
2510
|
-
}
|
2511
|
-
renderError(error, callback) {
|
2512
|
-
ErrorRenderer.render(this.delegate, callback, error || "");
|
2340
|
+
get shouldCacheSnapshot() {
|
2341
|
+
return this.snapshot.isCacheable;
|
2513
2342
|
}
|
2514
2343
|
}
|
2515
2344
|
|
@@ -2517,9 +2346,10 @@ class Session {
|
|
2517
2346
|
constructor() {
|
2518
2347
|
this.navigator = new Navigator(this);
|
2519
2348
|
this.history = new History(this);
|
2520
|
-
this.view = new
|
2349
|
+
this.view = new PageView(this, document.documentElement);
|
2521
2350
|
this.adapter = new BrowserAdapter(this);
|
2522
2351
|
this.pageObserver = new PageObserver(this);
|
2352
|
+
this.cacheObserver = new CacheObserver;
|
2523
2353
|
this.linkClickObserver = new LinkClickObserver(this);
|
2524
2354
|
this.formSubmitObserver = new FormSubmitObserver(this);
|
2525
2355
|
this.scrollObserver = new ScrollObserver(this);
|
@@ -2532,6 +2362,7 @@ class Session {
|
|
2532
2362
|
start() {
|
2533
2363
|
if (!this.started) {
|
2534
2364
|
this.pageObserver.start();
|
2365
|
+
this.cacheObserver.start();
|
2535
2366
|
this.linkClickObserver.start();
|
2536
2367
|
this.formSubmitObserver.start();
|
2537
2368
|
this.scrollObserver.start();
|
@@ -2548,6 +2379,7 @@ class Session {
|
|
2548
2379
|
stop() {
|
2549
2380
|
if (this.started) {
|
2550
2381
|
this.pageObserver.stop();
|
2382
|
+
this.cacheObserver.stop();
|
2551
2383
|
this.linkClickObserver.stop();
|
2552
2384
|
this.formSubmitObserver.stop();
|
2553
2385
|
this.scrollObserver.stop();
|
@@ -2561,7 +2393,7 @@ class Session {
|
|
2561
2393
|
this.adapter = adapter;
|
2562
2394
|
}
|
2563
2395
|
visit(location, options = {}) {
|
2564
|
-
this.navigator.proposeVisit(
|
2396
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
2565
2397
|
}
|
2566
2398
|
connectStreamSource(source) {
|
2567
2399
|
this.streamObserver.connectStreamSource(source);
|
@@ -2584,9 +2416,9 @@ class Session {
|
|
2584
2416
|
get restorationIdentifier() {
|
2585
2417
|
return this.history.restorationIdentifier;
|
2586
2418
|
}
|
2587
|
-
historyPoppedToLocationWithRestorationIdentifier(location) {
|
2419
|
+
historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
|
2588
2420
|
if (this.enabled) {
|
2589
|
-
this.navigator.
|
2421
|
+
this.navigator.startVisit(location, restorationIdentifier, {
|
2590
2422
|
action: "restore",
|
2591
2423
|
historyChanged: true
|
2592
2424
|
});
|
@@ -2594,134 +2426,577 @@ class Session {
|
|
2594
2426
|
this.adapter.pageInvalidated();
|
2595
2427
|
}
|
2596
2428
|
}
|
2597
|
-
scrollPositionChanged(position) {
|
2598
|
-
this.history.updateRestorationData({
|
2599
|
-
scrollPosition: position
|
2600
|
-
});
|
2429
|
+
scrollPositionChanged(position) {
|
2430
|
+
this.history.updateRestorationData({
|
2431
|
+
scrollPosition: position
|
2432
|
+
});
|
2433
|
+
}
|
2434
|
+
willFollowLinkToLocation(link, location) {
|
2435
|
+
return elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
|
2436
|
+
}
|
2437
|
+
followedLinkToLocation(link, location) {
|
2438
|
+
const action = this.getActionForLink(link);
|
2439
|
+
this.visit(location.href, {
|
2440
|
+
action: action
|
2441
|
+
});
|
2442
|
+
}
|
2443
|
+
allowsVisitingLocation(location) {
|
2444
|
+
return this.applicationAllowsVisitingLocation(location);
|
2445
|
+
}
|
2446
|
+
visitProposedToLocation(location, options) {
|
2447
|
+
extendURLWithDeprecatedProperties(location);
|
2448
|
+
this.adapter.visitProposedToLocation(location, options);
|
2449
|
+
}
|
2450
|
+
visitStarted(visit) {
|
2451
|
+
extendURLWithDeprecatedProperties(visit.location);
|
2452
|
+
this.notifyApplicationAfterVisitingLocation(visit.location);
|
2453
|
+
}
|
2454
|
+
visitCompleted(visit) {
|
2455
|
+
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2456
|
+
}
|
2457
|
+
willSubmitForm(form, submitter) {
|
2458
|
+
return elementIsNavigable(form) && elementIsNavigable(submitter);
|
2459
|
+
}
|
2460
|
+
formSubmitted(form, submitter) {
|
2461
|
+
this.navigator.submitForm(form, submitter);
|
2462
|
+
}
|
2463
|
+
pageBecameInteractive() {
|
2464
|
+
this.view.lastRenderedLocation = this.location;
|
2465
|
+
this.notifyApplicationAfterPageLoad();
|
2466
|
+
}
|
2467
|
+
pageLoaded() {
|
2468
|
+
this.history.assumeControlOfScrollRestoration();
|
2469
|
+
}
|
2470
|
+
pageWillUnload() {
|
2471
|
+
this.history.relinquishControlOfScrollRestoration();
|
2472
|
+
}
|
2473
|
+
receivedMessageFromStream(message) {
|
2474
|
+
this.renderStreamMessage(message);
|
2475
|
+
}
|
2476
|
+
viewWillCacheSnapshot() {
|
2477
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
2478
|
+
}
|
2479
|
+
viewWillRenderSnapshot({element: element}, isPreview) {
|
2480
|
+
this.notifyApplicationBeforeRender(element);
|
2481
|
+
}
|
2482
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
2483
|
+
this.view.lastRenderedLocation = this.history.location;
|
2484
|
+
this.notifyApplicationAfterRender();
|
2485
|
+
}
|
2486
|
+
viewInvalidated() {
|
2487
|
+
this.adapter.pageInvalidated();
|
2488
|
+
}
|
2489
|
+
applicationAllowsFollowingLinkToLocation(link, location) {
|
2490
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
2491
|
+
return !event.defaultPrevented;
|
2492
|
+
}
|
2493
|
+
applicationAllowsVisitingLocation(location) {
|
2494
|
+
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2495
|
+
return !event.defaultPrevented;
|
2496
|
+
}
|
2497
|
+
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
2498
|
+
return dispatch("turbo:click", {
|
2499
|
+
target: link,
|
2500
|
+
detail: {
|
2501
|
+
url: location.href
|
2502
|
+
},
|
2503
|
+
cancelable: true
|
2504
|
+
});
|
2505
|
+
}
|
2506
|
+
notifyApplicationBeforeVisitingLocation(location) {
|
2507
|
+
return dispatch("turbo:before-visit", {
|
2508
|
+
detail: {
|
2509
|
+
url: location.href
|
2510
|
+
},
|
2511
|
+
cancelable: true
|
2512
|
+
});
|
2513
|
+
}
|
2514
|
+
notifyApplicationAfterVisitingLocation(location) {
|
2515
|
+
return dispatch("turbo:visit", {
|
2516
|
+
detail: {
|
2517
|
+
url: location.href
|
2518
|
+
}
|
2519
|
+
});
|
2520
|
+
}
|
2521
|
+
notifyApplicationBeforeCachingSnapshot() {
|
2522
|
+
return dispatch("turbo:before-cache");
|
2523
|
+
}
|
2524
|
+
notifyApplicationBeforeRender(newBody) {
|
2525
|
+
return dispatch("turbo:before-render", {
|
2526
|
+
detail: {
|
2527
|
+
newBody: newBody
|
2528
|
+
}
|
2529
|
+
});
|
2530
|
+
}
|
2531
|
+
notifyApplicationAfterRender() {
|
2532
|
+
return dispatch("turbo:render");
|
2533
|
+
}
|
2534
|
+
notifyApplicationAfterPageLoad(timing = {}) {
|
2535
|
+
return dispatch("turbo:load", {
|
2536
|
+
detail: {
|
2537
|
+
url: this.location.href,
|
2538
|
+
timing: timing
|
2539
|
+
}
|
2540
|
+
});
|
2541
|
+
}
|
2542
|
+
getActionForLink(link) {
|
2543
|
+
const action = link.getAttribute("data-turbo-action");
|
2544
|
+
return isAction(action) ? action : "advance";
|
2545
|
+
}
|
2546
|
+
locationIsVisitable(location) {
|
2547
|
+
return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
|
2548
|
+
}
|
2549
|
+
get snapshot() {
|
2550
|
+
return this.view.snapshot;
|
2551
|
+
}
|
2552
|
+
}
|
2553
|
+
|
2554
|
+
function elementIsNavigable(element) {
|
2555
|
+
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2556
|
+
if (container) {
|
2557
|
+
return container.getAttribute("data-turbo") != "false";
|
2558
|
+
} else {
|
2559
|
+
return true;
|
2560
|
+
}
|
2561
|
+
}
|
2562
|
+
|
2563
|
+
function extendURLWithDeprecatedProperties(url) {
|
2564
|
+
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2565
|
+
}
|
2566
|
+
|
2567
|
+
const deprecatedLocationPropertyDescriptors = {
|
2568
|
+
absoluteURL: {
|
2569
|
+
get() {
|
2570
|
+
return this.toString();
|
2571
|
+
}
|
2572
|
+
}
|
2573
|
+
};
|
2574
|
+
|
2575
|
+
class FrameController {
|
2576
|
+
constructor(element) {
|
2577
|
+
this.resolveVisitPromise = () => {};
|
2578
|
+
this.connected = false;
|
2579
|
+
this.hasBeenLoaded = false;
|
2580
|
+
this.settingSourceURL = false;
|
2581
|
+
this.element = element;
|
2582
|
+
this.view = new FrameView(this, this.element);
|
2583
|
+
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
2584
|
+
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
2585
|
+
this.formInterceptor = new FormInterceptor(this, this.element);
|
2586
|
+
}
|
2587
|
+
connect() {
|
2588
|
+
if (!this.connected) {
|
2589
|
+
this.connected = true;
|
2590
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2591
|
+
this.appearanceObserver.start();
|
2592
|
+
}
|
2593
|
+
this.linkInterceptor.start();
|
2594
|
+
this.formInterceptor.start();
|
2595
|
+
this.sourceURLChanged();
|
2596
|
+
}
|
2597
|
+
}
|
2598
|
+
disconnect() {
|
2599
|
+
if (this.connected) {
|
2600
|
+
this.connected = false;
|
2601
|
+
this.appearanceObserver.stop();
|
2602
|
+
this.linkInterceptor.stop();
|
2603
|
+
this.formInterceptor.stop();
|
2604
|
+
}
|
2605
|
+
}
|
2606
|
+
disabledChanged() {
|
2607
|
+
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
2608
|
+
this.loadSourceURL();
|
2609
|
+
}
|
2610
|
+
}
|
2611
|
+
sourceURLChanged() {
|
2612
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2613
|
+
this.loadSourceURL();
|
2614
|
+
}
|
2615
|
+
}
|
2616
|
+
loadingStyleChanged() {
|
2617
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2618
|
+
this.appearanceObserver.start();
|
2619
|
+
} else {
|
2620
|
+
this.appearanceObserver.stop();
|
2621
|
+
this.loadSourceURL();
|
2622
|
+
}
|
2623
|
+
}
|
2624
|
+
async loadSourceURL() {
|
2625
|
+
if (!this.settingSourceURL && this.enabled && this.isActive && this.sourceURL != this.currentURL) {
|
2626
|
+
const previousURL = this.currentURL;
|
2627
|
+
this.currentURL = this.sourceURL;
|
2628
|
+
if (this.sourceURL) {
|
2629
|
+
try {
|
2630
|
+
this.element.loaded = this.visit(this.sourceURL);
|
2631
|
+
this.appearanceObserver.stop();
|
2632
|
+
await this.element.loaded;
|
2633
|
+
this.hasBeenLoaded = true;
|
2634
|
+
} catch (error) {
|
2635
|
+
this.currentURL = previousURL;
|
2636
|
+
throw error;
|
2637
|
+
}
|
2638
|
+
}
|
2639
|
+
}
|
2640
|
+
}
|
2641
|
+
async loadResponse(fetchResponse) {
|
2642
|
+
if (fetchResponse.redirected) {
|
2643
|
+
this.sourceURL = fetchResponse.response.url;
|
2644
|
+
}
|
2645
|
+
try {
|
2646
|
+
const html = await fetchResponse.responseHTML;
|
2647
|
+
if (html) {
|
2648
|
+
const {body: body} = parseHTMLDocument(html);
|
2649
|
+
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
2650
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
2651
|
+
await this.view.render(renderer);
|
2652
|
+
}
|
2653
|
+
} catch (error) {
|
2654
|
+
console.error(error);
|
2655
|
+
this.view.invalidate();
|
2656
|
+
}
|
2657
|
+
}
|
2658
|
+
elementAppearedInViewport(element) {
|
2659
|
+
this.loadSourceURL();
|
2660
|
+
}
|
2661
|
+
shouldInterceptLinkClick(element, url) {
|
2662
|
+
return this.shouldInterceptNavigation(element);
|
2663
|
+
}
|
2664
|
+
linkClickIntercepted(element, url) {
|
2665
|
+
this.navigateFrame(element, url);
|
2666
|
+
}
|
2667
|
+
shouldInterceptFormSubmission(element, submitter) {
|
2668
|
+
return this.shouldInterceptNavigation(element, submitter);
|
2669
|
+
}
|
2670
|
+
formSubmissionIntercepted(element, submitter) {
|
2671
|
+
if (this.formSubmission) {
|
2672
|
+
this.formSubmission.stop();
|
2673
|
+
}
|
2674
|
+
this.formSubmission = new FormSubmission(this, element, submitter);
|
2675
|
+
if (this.formSubmission.fetchRequest.isIdempotent) {
|
2676
|
+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
|
2677
|
+
} else {
|
2678
|
+
const {fetchRequest: fetchRequest} = this.formSubmission;
|
2679
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
2680
|
+
this.formSubmission.start();
|
2681
|
+
}
|
2682
|
+
}
|
2683
|
+
prepareHeadersForRequest(headers, request) {
|
2684
|
+
headers["Turbo-Frame"] = this.id;
|
2685
|
+
}
|
2686
|
+
requestStarted(request) {
|
2687
|
+
this.element.setAttribute("busy", "");
|
2688
|
+
}
|
2689
|
+
requestPreventedHandlingResponse(request, response) {
|
2690
|
+
this.resolveVisitPromise();
|
2691
|
+
}
|
2692
|
+
async requestSucceededWithResponse(request, response) {
|
2693
|
+
await this.loadResponse(response);
|
2694
|
+
this.resolveVisitPromise();
|
2695
|
+
}
|
2696
|
+
requestFailedWithResponse(request, response) {
|
2697
|
+
console.error(response);
|
2698
|
+
this.resolveVisitPromise();
|
2699
|
+
}
|
2700
|
+
requestErrored(request, error) {
|
2701
|
+
console.error(error);
|
2702
|
+
this.resolveVisitPromise();
|
2703
|
+
}
|
2704
|
+
requestFinished(request) {
|
2705
|
+
this.element.removeAttribute("busy");
|
2706
|
+
}
|
2707
|
+
formSubmissionStarted(formSubmission) {
|
2708
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2709
|
+
frame.setAttribute("busy", "");
|
2710
|
+
}
|
2711
|
+
formSubmissionSucceededWithResponse(formSubmission, response) {
|
2712
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2713
|
+
frame.delegate.loadResponse(response);
|
2714
|
+
}
|
2715
|
+
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
2716
|
+
this.element.delegate.loadResponse(fetchResponse);
|
2717
|
+
}
|
2718
|
+
formSubmissionErrored(formSubmission, error) {
|
2719
|
+
console.error(error);
|
2720
|
+
}
|
2721
|
+
formSubmissionFinished(formSubmission) {
|
2722
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2723
|
+
frame.removeAttribute("busy");
|
2724
|
+
}
|
2725
|
+
viewWillRenderSnapshot(snapshot, isPreview) {}
|
2726
|
+
viewRenderedSnapshot(snapshot, isPreview) {}
|
2727
|
+
viewInvalidated() {}
|
2728
|
+
async visit(url) {
|
2729
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
2730
|
+
return new Promise((resolve => {
|
2731
|
+
this.resolveVisitPromise = () => {
|
2732
|
+
this.resolveVisitPromise = () => {};
|
2733
|
+
resolve();
|
2734
|
+
};
|
2735
|
+
request.perform();
|
2736
|
+
}));
|
2737
|
+
}
|
2738
|
+
navigateFrame(element, url) {
|
2739
|
+
const frame = this.findFrameElement(element);
|
2740
|
+
frame.src = url;
|
2741
|
+
}
|
2742
|
+
findFrameElement(element) {
|
2743
|
+
var _a;
|
2744
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2745
|
+
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
2746
|
+
}
|
2747
|
+
async extractForeignFrameElement(container) {
|
2748
|
+
let element;
|
2749
|
+
const id = CSS.escape(this.id);
|
2750
|
+
try {
|
2751
|
+
if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
|
2752
|
+
return element;
|
2753
|
+
}
|
2754
|
+
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
|
2755
|
+
await element.loaded;
|
2756
|
+
return await this.extractForeignFrameElement(element);
|
2757
|
+
}
|
2758
|
+
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
2759
|
+
} catch (error) {
|
2760
|
+
console.error(error);
|
2761
|
+
}
|
2762
|
+
return new FrameElement;
|
2763
|
+
}
|
2764
|
+
shouldInterceptNavigation(element, submitter) {
|
2765
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2766
|
+
if (!this.enabled || id == "_top") {
|
2767
|
+
return false;
|
2768
|
+
}
|
2769
|
+
if (id) {
|
2770
|
+
const frameElement = getFrameElementById(id);
|
2771
|
+
if (frameElement) {
|
2772
|
+
return !frameElement.disabled;
|
2773
|
+
}
|
2774
|
+
}
|
2775
|
+
if (!elementIsNavigable(element)) {
|
2776
|
+
return false;
|
2777
|
+
}
|
2778
|
+
if (submitter && !elementIsNavigable(submitter)) {
|
2779
|
+
return false;
|
2780
|
+
}
|
2781
|
+
return true;
|
2601
2782
|
}
|
2602
|
-
|
2603
|
-
return this.
|
2783
|
+
get id() {
|
2784
|
+
return this.element.id;
|
2604
2785
|
}
|
2605
|
-
|
2606
|
-
|
2607
|
-
this.visit(location, {
|
2608
|
-
action: action
|
2609
|
-
});
|
2786
|
+
get enabled() {
|
2787
|
+
return !this.element.disabled;
|
2610
2788
|
}
|
2611
|
-
|
2612
|
-
|
2789
|
+
get sourceURL() {
|
2790
|
+
if (this.element.src) {
|
2791
|
+
return this.element.src;
|
2792
|
+
}
|
2613
2793
|
}
|
2614
|
-
|
2615
|
-
this.
|
2794
|
+
set sourceURL(sourceURL) {
|
2795
|
+
this.settingSourceURL = true;
|
2796
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
2797
|
+
this.currentURL = this.element.src;
|
2798
|
+
this.settingSourceURL = false;
|
2616
2799
|
}
|
2617
|
-
|
2618
|
-
this.
|
2800
|
+
get loadingStyle() {
|
2801
|
+
return this.element.loading;
|
2619
2802
|
}
|
2620
|
-
|
2621
|
-
this.
|
2803
|
+
get isLoading() {
|
2804
|
+
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
2622
2805
|
}
|
2623
|
-
|
2624
|
-
return this.
|
2806
|
+
get isActive() {
|
2807
|
+
return this.element.isActive && this.connected;
|
2625
2808
|
}
|
2626
|
-
|
2627
|
-
|
2809
|
+
}
|
2810
|
+
|
2811
|
+
function getFrameElementById(id) {
|
2812
|
+
if (id != null) {
|
2813
|
+
const element = document.getElementById(id);
|
2814
|
+
if (element instanceof FrameElement) {
|
2815
|
+
return element;
|
2816
|
+
}
|
2628
2817
|
}
|
2629
|
-
|
2630
|
-
|
2631
|
-
|
2818
|
+
}
|
2819
|
+
|
2820
|
+
function activateElement(element, currentURL) {
|
2821
|
+
if (element) {
|
2822
|
+
const src = element.getAttribute("src");
|
2823
|
+
if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
|
2824
|
+
throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
|
2825
|
+
}
|
2826
|
+
if (element.ownerDocument !== document) {
|
2827
|
+
element = document.importNode(element, true);
|
2828
|
+
}
|
2829
|
+
if (element instanceof FrameElement) {
|
2830
|
+
element.connectedCallback();
|
2831
|
+
return element;
|
2832
|
+
}
|
2632
2833
|
}
|
2633
|
-
|
2634
|
-
|
2834
|
+
}
|
2835
|
+
|
2836
|
+
const StreamActions = {
|
2837
|
+
after() {
|
2838
|
+
this.targetElements.forEach((e => {
|
2839
|
+
var _a;
|
2840
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
2841
|
+
}));
|
2842
|
+
},
|
2843
|
+
append() {
|
2844
|
+
this.removeDuplicateTargetChildren();
|
2845
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
2846
|
+
},
|
2847
|
+
before() {
|
2848
|
+
this.targetElements.forEach((e => {
|
2849
|
+
var _a;
|
2850
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
2851
|
+
}));
|
2852
|
+
},
|
2853
|
+
prepend() {
|
2854
|
+
this.removeDuplicateTargetChildren();
|
2855
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
2856
|
+
},
|
2857
|
+
remove() {
|
2858
|
+
this.targetElements.forEach((e => e.remove()));
|
2859
|
+
},
|
2860
|
+
replace() {
|
2861
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
2862
|
+
},
|
2863
|
+
update() {
|
2864
|
+
this.targetElements.forEach((e => {
|
2865
|
+
e.innerHTML = "";
|
2866
|
+
e.append(this.templateContent);
|
2867
|
+
}));
|
2635
2868
|
}
|
2636
|
-
|
2637
|
-
|
2869
|
+
};
|
2870
|
+
|
2871
|
+
class StreamElement extends HTMLElement {
|
2872
|
+
async connectedCallback() {
|
2873
|
+
try {
|
2874
|
+
await this.render();
|
2875
|
+
} catch (error) {
|
2876
|
+
console.error(error);
|
2877
|
+
} finally {
|
2878
|
+
this.disconnect();
|
2879
|
+
}
|
2638
2880
|
}
|
2639
|
-
|
2640
|
-
|
2881
|
+
async render() {
|
2882
|
+
var _a;
|
2883
|
+
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
2884
|
+
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
2885
|
+
await nextAnimationFrame();
|
2886
|
+
this.performAction();
|
2887
|
+
}
|
2888
|
+
})();
|
2641
2889
|
}
|
2642
|
-
|
2643
|
-
|
2890
|
+
disconnect() {
|
2891
|
+
try {
|
2892
|
+
this.remove();
|
2893
|
+
} catch (_a) {}
|
2644
2894
|
}
|
2645
|
-
|
2646
|
-
this.
|
2647
|
-
this.notifyApplicationAfterRender();
|
2895
|
+
removeDuplicateTargetChildren() {
|
2896
|
+
this.duplicateChildren.forEach((c => c.remove()));
|
2648
2897
|
}
|
2649
|
-
|
2650
|
-
|
2898
|
+
get duplicateChildren() {
|
2899
|
+
var _a;
|
2900
|
+
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
2901
|
+
const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
|
2902
|
+
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
2651
2903
|
}
|
2652
|
-
|
2653
|
-
this.
|
2904
|
+
get performAction() {
|
2905
|
+
if (this.action) {
|
2906
|
+
const actionFunction = StreamActions[this.action];
|
2907
|
+
if (actionFunction) {
|
2908
|
+
return actionFunction;
|
2909
|
+
}
|
2910
|
+
this.raise("unknown action");
|
2911
|
+
}
|
2912
|
+
this.raise("action attribute is missing");
|
2654
2913
|
}
|
2655
|
-
|
2656
|
-
|
2657
|
-
|
2914
|
+
get targetElements() {
|
2915
|
+
if (this.target) {
|
2916
|
+
return this.targetElementsById;
|
2917
|
+
} else if (this.targets) {
|
2918
|
+
return this.targetElementsByQuery;
|
2919
|
+
} else {
|
2920
|
+
this.raise("target or targets attribute is missing");
|
2921
|
+
}
|
2658
2922
|
}
|
2659
|
-
|
2660
|
-
|
2661
|
-
return !event.defaultPrevented;
|
2923
|
+
get templateContent() {
|
2924
|
+
return this.templateElement.content.cloneNode(true);
|
2662
2925
|
}
|
2663
|
-
|
2664
|
-
|
2665
|
-
|
2666
|
-
|
2667
|
-
|
2668
|
-
},
|
2669
|
-
cancelable: true
|
2670
|
-
});
|
2926
|
+
get templateElement() {
|
2927
|
+
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
2928
|
+
return this.firstElementChild;
|
2929
|
+
}
|
2930
|
+
this.raise("first child element must be a <template> element");
|
2671
2931
|
}
|
2672
|
-
|
2673
|
-
return
|
2674
|
-
detail: {
|
2675
|
-
url: location.absoluteURL
|
2676
|
-
},
|
2677
|
-
cancelable: true
|
2678
|
-
});
|
2932
|
+
get action() {
|
2933
|
+
return this.getAttribute("action");
|
2679
2934
|
}
|
2680
|
-
|
2681
|
-
return
|
2682
|
-
detail: {
|
2683
|
-
url: location.absoluteURL
|
2684
|
-
}
|
2685
|
-
});
|
2935
|
+
get target() {
|
2936
|
+
return this.getAttribute("target");
|
2686
2937
|
}
|
2687
|
-
|
2688
|
-
return
|
2938
|
+
get targets() {
|
2939
|
+
return this.getAttribute("targets");
|
2689
2940
|
}
|
2690
|
-
|
2691
|
-
|
2692
|
-
detail: {
|
2693
|
-
newBody: newBody
|
2694
|
-
}
|
2695
|
-
});
|
2941
|
+
raise(message) {
|
2942
|
+
throw new Error(`${this.description}: ${message}`);
|
2696
2943
|
}
|
2697
|
-
|
2698
|
-
|
2944
|
+
get description() {
|
2945
|
+
var _a, _b;
|
2946
|
+
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
2699
2947
|
}
|
2700
|
-
|
2701
|
-
return
|
2702
|
-
|
2703
|
-
|
2704
|
-
timing: timing
|
2705
|
-
}
|
2948
|
+
get beforeRenderEvent() {
|
2949
|
+
return new CustomEvent("turbo:before-stream-render", {
|
2950
|
+
bubbles: true,
|
2951
|
+
cancelable: true
|
2706
2952
|
});
|
2707
2953
|
}
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
2711
|
-
|
2712
|
-
|
2713
|
-
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2714
|
-
if (container) {
|
2715
|
-
return container.getAttribute("data-turbo") != "false";
|
2954
|
+
get targetElementsById() {
|
2955
|
+
var _a;
|
2956
|
+
const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
2957
|
+
if (element !== null) {
|
2958
|
+
return [ element ];
|
2716
2959
|
} else {
|
2717
|
-
return
|
2960
|
+
return [];
|
2718
2961
|
}
|
2719
2962
|
}
|
2720
|
-
|
2721
|
-
|
2963
|
+
get targetElementsByQuery() {
|
2964
|
+
var _a;
|
2965
|
+
const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
|
2966
|
+
if (elements.length !== 0) {
|
2967
|
+
return Array.prototype.slice.call(elements);
|
2968
|
+
} else {
|
2969
|
+
return [];
|
2970
|
+
}
|
2722
2971
|
}
|
2723
2972
|
}
|
2724
2973
|
|
2974
|
+
FrameElement.delegateConstructor = FrameController;
|
2975
|
+
|
2976
|
+
customElements.define("turbo-frame", FrameElement);
|
2977
|
+
|
2978
|
+
customElements.define("turbo-stream", StreamElement);
|
2979
|
+
|
2980
|
+
(() => {
|
2981
|
+
let element = document.currentScript;
|
2982
|
+
if (!element) return;
|
2983
|
+
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
2984
|
+
while (element = element.parentElement) {
|
2985
|
+
if (element == document.body) {
|
2986
|
+
return console.warn(unindent`
|
2987
|
+
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
2988
|
+
|
2989
|
+
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
2990
|
+
|
2991
|
+
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
2992
|
+
|
2993
|
+
——
|
2994
|
+
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
2995
|
+
`, element.outerHTML);
|
2996
|
+
}
|
2997
|
+
}
|
2998
|
+
})();
|
2999
|
+
|
2725
3000
|
const session = new Session;
|
2726
3001
|
|
2727
3002
|
const {navigator: navigator} = session;
|
@@ -2758,6 +3033,21 @@ function setProgressBarDelay(delay) {
|
|
2758
3033
|
session.setProgressBarDelay(delay);
|
2759
3034
|
}
|
2760
3035
|
|
3036
|
+
var Turbo = Object.freeze({
|
3037
|
+
__proto__: null,
|
3038
|
+
navigator: navigator,
|
3039
|
+
start: start,
|
3040
|
+
registerAdapter: registerAdapter,
|
3041
|
+
visit: visit,
|
3042
|
+
connectStreamSource: connectStreamSource,
|
3043
|
+
disconnectStreamSource: disconnectStreamSource,
|
3044
|
+
renderStreamMessage: renderStreamMessage,
|
3045
|
+
clearCache: clearCache,
|
3046
|
+
setProgressBarDelay: setProgressBarDelay
|
3047
|
+
});
|
3048
|
+
|
3049
|
+
window.Turbo = Turbo;
|
3050
|
+
|
2761
3051
|
start();
|
2762
3052
|
|
2763
3053
|
var turbo_es2017Esm = Object.freeze({
|
@@ -2776,17 +3066,20 @@ var turbo_es2017Esm = Object.freeze({
|
|
2776
3066
|
let consumer;
|
2777
3067
|
|
2778
3068
|
async function getConsumer() {
|
2779
|
-
|
2780
|
-
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
2781
|
-
return index;
|
2782
|
-
}));
|
2783
|
-
return setConsumer(createConsumer());
|
3069
|
+
return consumer || setConsumer(createConsumer().then(setConsumer));
|
2784
3070
|
}
|
2785
3071
|
|
2786
3072
|
function setConsumer(newConsumer) {
|
2787
3073
|
return consumer = newConsumer;
|
2788
3074
|
}
|
2789
3075
|
|
3076
|
+
async function createConsumer() {
|
3077
|
+
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
3078
|
+
return index;
|
3079
|
+
}));
|
3080
|
+
return createConsumer();
|
3081
|
+
}
|
3082
|
+
|
2790
3083
|
async function subscribeTo(channel, mixin) {
|
2791
3084
|
const {subscriptions: subscriptions} = await getConsumer();
|
2792
3085
|
return subscriptions.create(channel, mixin);
|
@@ -2796,6 +3089,7 @@ var cable = Object.freeze({
|
|
2796
3089
|
__proto__: null,
|
2797
3090
|
getConsumer: getConsumer,
|
2798
3091
|
setConsumer: setConsumer,
|
3092
|
+
createConsumer: createConsumer,
|
2799
3093
|
subscribeTo: subscribeTo
|
2800
3094
|
});
|
2801
3095
|
|
@@ -3259,7 +3553,7 @@ function createWebSocketURL(url) {
|
|
3259
3553
|
}
|
3260
3554
|
}
|
3261
3555
|
|
3262
|
-
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3556
|
+
function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3263
3557
|
return new Consumer(url);
|
3264
3558
|
}
|
3265
3559
|
|
@@ -3281,7 +3575,7 @@ var index = Object.freeze({
|
|
3281
3575
|
adapters: adapters,
|
3282
3576
|
createWebSocketURL: createWebSocketURL,
|
3283
3577
|
logger: logger,
|
3284
|
-
createConsumer: createConsumer,
|
3578
|
+
createConsumer: createConsumer$1,
|
3285
3579
|
getConfig: getConfig
|
3286
3580
|
});
|
3287
3581
|
|