1 |
|
2 |
|
3 |
|
4 |
|
5 | (function (global, factory) {
|
6 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
7 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
8 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Turbo = {}));
|
9 | })(this, (function (exports) { 'use strict';
|
10 |
|
11 | |
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | (function (prototype) {
|
36 | if (typeof prototype.requestSubmit == "function") return
|
37 |
|
38 | prototype.requestSubmit = function (submitter) {
|
39 | if (submitter) {
|
40 | validateSubmitter(submitter, this);
|
41 | submitter.click();
|
42 | } else {
|
43 | submitter = document.createElement("input");
|
44 | submitter.type = "submit";
|
45 | submitter.hidden = true;
|
46 | this.appendChild(submitter);
|
47 | submitter.click();
|
48 | this.removeChild(submitter);
|
49 | }
|
50 | };
|
51 |
|
52 | function validateSubmitter(submitter, form) {
|
53 | submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
|
54 | submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
|
55 | submitter.form == form ||
|
56 | raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
|
57 | }
|
58 |
|
59 | function raise(errorConstructor, message, name) {
|
60 | throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
|
61 | }
|
62 | })(HTMLFormElement.prototype);
|
63 |
|
64 | const submittersByForm = new WeakMap();
|
65 |
|
66 | function findSubmitterFromClickTarget(target) {
|
67 | const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
68 | const candidate = element ? element.closest("input, button") : null;
|
69 | return candidate?.type == "submit" ? candidate : null
|
70 | }
|
71 |
|
72 | function clickCaptured(event) {
|
73 | const submitter = findSubmitterFromClickTarget(event.target);
|
74 |
|
75 | if (submitter && submitter.form) {
|
76 | submittersByForm.set(submitter.form, submitter);
|
77 | }
|
78 | }
|
79 |
|
80 | (function () {
|
81 | if ("submitter" in Event.prototype) return
|
82 |
|
83 | let prototype = window.Event.prototype;
|
84 |
|
85 |
|
86 |
|
87 | if ("SubmitEvent" in window) {
|
88 | const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
|
89 |
|
90 | if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
|
91 | prototype = prototypeOfSubmitEvent;
|
92 | } else {
|
93 | return
|
94 | }
|
95 | }
|
96 |
|
97 | addEventListener("click", clickCaptured, true);
|
98 |
|
99 | Object.defineProperty(prototype, "submitter", {
|
100 | get() {
|
101 | if (this.type == "submit" && this.target instanceof HTMLFormElement) {
|
102 | return submittersByForm.get(this.target)
|
103 | }
|
104 | }
|
105 | });
|
106 | })();
|
107 |
|
108 | const FrameLoadingStyle = {
|
109 | eager: "eager",
|
110 | lazy: "lazy"
|
111 | };
|
112 |
|
113 | |
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | class FrameElement extends HTMLElement {
|
130 | static delegateConstructor = undefined
|
131 |
|
132 | loaded = Promise.resolve()
|
133 |
|
134 | static get observedAttributes() {
|
135 | return ["disabled", "loading", "src"]
|
136 | }
|
137 |
|
138 | constructor() {
|
139 | super();
|
140 | this.delegate = new FrameElement.delegateConstructor(this);
|
141 | }
|
142 |
|
143 | connectedCallback() {
|
144 | this.delegate.connect();
|
145 | }
|
146 |
|
147 | disconnectedCallback() {
|
148 | this.delegate.disconnect();
|
149 | }
|
150 |
|
151 | reload() {
|
152 | return this.delegate.sourceURLReloaded()
|
153 | }
|
154 |
|
155 | attributeChangedCallback(name) {
|
156 | if (name == "loading") {
|
157 | this.delegate.loadingStyleChanged();
|
158 | } else if (name == "src") {
|
159 | this.delegate.sourceURLChanged();
|
160 | } else if (name == "disabled") {
|
161 | this.delegate.disabledChanged();
|
162 | }
|
163 | }
|
164 |
|
165 | |
166 |
|
167 |
|
168 | get src() {
|
169 | return this.getAttribute("src")
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 | set src(value) {
|
176 | if (value) {
|
177 | this.setAttribute("src", value);
|
178 | } else {
|
179 | this.removeAttribute("src");
|
180 | }
|
181 | }
|
182 |
|
183 | |
184 |
|
185 |
|
186 | get refresh() {
|
187 | return this.getAttribute("refresh")
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 | set refresh(value) {
|
194 | if (value) {
|
195 | this.setAttribute("refresh", value);
|
196 | } else {
|
197 | this.removeAttribute("refresh");
|
198 | }
|
199 | }
|
200 |
|
201 | get shouldReloadWithMorph() {
|
202 | return this.src && this.refresh === "morph"
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 | get loading() {
|
209 | return frameLoadingStyleFromString(this.getAttribute("loading") || "")
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 | set loading(value) {
|
216 | if (value) {
|
217 | this.setAttribute("loading", value);
|
218 | } else {
|
219 | this.removeAttribute("loading");
|
220 | }
|
221 | }
|
222 |
|
223 | |
224 |
|
225 |
|
226 |
|
227 |
|
228 | get disabled() {
|
229 | return this.hasAttribute("disabled")
|
230 | }
|
231 |
|
232 | |
233 |
|
234 |
|
235 |
|
236 |
|
237 | set disabled(value) {
|
238 | if (value) {
|
239 | this.setAttribute("disabled", "");
|
240 | } else {
|
241 | this.removeAttribute("disabled");
|
242 | }
|
243 | }
|
244 |
|
245 | |
246 |
|
247 |
|
248 |
|
249 |
|
250 | get autoscroll() {
|
251 | return this.hasAttribute("autoscroll")
|
252 | }
|
253 |
|
254 | |
255 |
|
256 |
|
257 |
|
258 |
|
259 | set autoscroll(value) {
|
260 | if (value) {
|
261 | this.setAttribute("autoscroll", "");
|
262 | } else {
|
263 | this.removeAttribute("autoscroll");
|
264 | }
|
265 | }
|
266 |
|
267 | |
268 |
|
269 |
|
270 | get complete() {
|
271 | return !this.delegate.isLoading
|
272 | }
|
273 |
|
274 | |
275 |
|
276 |
|
277 |
|
278 |
|
279 | get isActive() {
|
280 | return this.ownerDocument === document && !this.isPreview
|
281 | }
|
282 |
|
283 | |
284 |
|
285 |
|
286 |
|
287 |
|
288 | get isPreview() {
|
289 | return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview")
|
290 | }
|
291 | }
|
292 |
|
293 | function frameLoadingStyleFromString(style) {
|
294 | switch (style.toLowerCase()) {
|
295 | case "lazy":
|
296 | return FrameLoadingStyle.lazy
|
297 | default:
|
298 | return FrameLoadingStyle.eager
|
299 | }
|
300 | }
|
301 |
|
302 | const drive = {
|
303 | enabled: true,
|
304 | progressBarDelay: 500,
|
305 | unvisitableExtensions: new Set(
|
306 | [
|
307 | ".7z", ".aac", ".apk", ".avi", ".bmp", ".bz2", ".css", ".csv", ".deb", ".dmg", ".doc",
|
308 | ".docx", ".exe", ".gif", ".gz", ".heic", ".heif", ".ico", ".iso", ".jpeg", ".jpg",
|
309 | ".js", ".json", ".m4a", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi",
|
310 | ".ogg", ".ogv", ".pdf", ".pkg", ".png", ".ppt", ".pptx", ".rar", ".rtf",
|
311 | ".svg", ".tar", ".tif", ".tiff", ".txt", ".wav", ".webm", ".webp", ".wma", ".wmv",
|
312 | ".xls", ".xlsx", ".xml", ".zip"
|
313 | ]
|
314 | )
|
315 | };
|
316 |
|
317 | function activateScriptElement(element) {
|
318 | if (element.getAttribute("data-turbo-eval") == "false") {
|
319 | return element
|
320 | } else {
|
321 | const createdScriptElement = document.createElement("script");
|
322 | const cspNonce = getCspNonce();
|
323 | if (cspNonce) {
|
324 | createdScriptElement.nonce = cspNonce;
|
325 | }
|
326 | createdScriptElement.textContent = element.textContent;
|
327 | createdScriptElement.async = false;
|
328 | copyElementAttributes(createdScriptElement, element);
|
329 | return createdScriptElement
|
330 | }
|
331 | }
|
332 |
|
333 | function copyElementAttributes(destinationElement, sourceElement) {
|
334 | for (const { name, value } of sourceElement.attributes) {
|
335 | destinationElement.setAttribute(name, value);
|
336 | }
|
337 | }
|
338 |
|
339 | function createDocumentFragment(html) {
|
340 | const template = document.createElement("template");
|
341 | template.innerHTML = html;
|
342 | return template.content
|
343 | }
|
344 |
|
345 | function dispatch(eventName, { target, cancelable, detail } = {}) {
|
346 | const event = new CustomEvent(eventName, {
|
347 | cancelable,
|
348 | bubbles: true,
|
349 | composed: true,
|
350 | detail
|
351 | });
|
352 |
|
353 | if (target && target.isConnected) {
|
354 | target.dispatchEvent(event);
|
355 | } else {
|
356 | document.documentElement.dispatchEvent(event);
|
357 | }
|
358 |
|
359 | return event
|
360 | }
|
361 |
|
362 | function cancelEvent(event) {
|
363 | event.preventDefault();
|
364 | event.stopImmediatePropagation();
|
365 | }
|
366 |
|
367 | function nextRepaint() {
|
368 | if (document.visibilityState === "hidden") {
|
369 | return nextEventLoopTick()
|
370 | } else {
|
371 | return nextAnimationFrame()
|
372 | }
|
373 | }
|
374 |
|
375 | function nextAnimationFrame() {
|
376 | return new Promise((resolve) => requestAnimationFrame(() => resolve()))
|
377 | }
|
378 |
|
379 | function nextEventLoopTick() {
|
380 | return new Promise((resolve) => setTimeout(() => resolve(), 0))
|
381 | }
|
382 |
|
383 | function nextMicrotask() {
|
384 | return Promise.resolve()
|
385 | }
|
386 |
|
387 | function parseHTMLDocument(html = "") {
|
388 | return new DOMParser().parseFromString(html, "text/html")
|
389 | }
|
390 |
|
391 | function unindent(strings, ...values) {
|
392 | const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
|
393 | const match = lines[0].match(/^\s+/);
|
394 | const indent = match ? match[0].length : 0;
|
395 | return lines.map((line) => line.slice(indent)).join("\n")
|
396 | }
|
397 |
|
398 | function interpolate(strings, values) {
|
399 | return strings.reduce((result, string, i) => {
|
400 | const value = values[i] == undefined ? "" : values[i];
|
401 | return result + string + value
|
402 | }, "")
|
403 | }
|
404 |
|
405 | function uuid() {
|
406 | return Array.from({ length: 36 })
|
407 | .map((_, i) => {
|
408 | if (i == 8 || i == 13 || i == 18 || i == 23) {
|
409 | return "-"
|
410 | } else if (i == 14) {
|
411 | return "4"
|
412 | } else if (i == 19) {
|
413 | return (Math.floor(Math.random() * 4) + 8).toString(16)
|
414 | } else {
|
415 | return Math.floor(Math.random() * 15).toString(16)
|
416 | }
|
417 | })
|
418 | .join("")
|
419 | }
|
420 |
|
421 | function getAttribute(attributeName, ...elements) {
|
422 | for (const value of elements.map((element) => element?.getAttribute(attributeName))) {
|
423 | if (typeof value == "string") return value
|
424 | }
|
425 |
|
426 | return null
|
427 | }
|
428 |
|
429 | function hasAttribute(attributeName, ...elements) {
|
430 | return elements.some((element) => element && element.hasAttribute(attributeName))
|
431 | }
|
432 |
|
433 | function markAsBusy(...elements) {
|
434 | for (const element of elements) {
|
435 | if (element.localName == "turbo-frame") {
|
436 | element.setAttribute("busy", "");
|
437 | }
|
438 | element.setAttribute("aria-busy", "true");
|
439 | }
|
440 | }
|
441 |
|
442 | function clearBusyState(...elements) {
|
443 | for (const element of elements) {
|
444 | if (element.localName == "turbo-frame") {
|
445 | element.removeAttribute("busy");
|
446 | }
|
447 |
|
448 | element.removeAttribute("aria-busy");
|
449 | }
|
450 | }
|
451 |
|
452 | function waitForLoad(element, timeoutInMilliseconds = 2000) {
|
453 | return new Promise((resolve) => {
|
454 | const onComplete = () => {
|
455 | element.removeEventListener("error", onComplete);
|
456 | element.removeEventListener("load", onComplete);
|
457 | resolve();
|
458 | };
|
459 |
|
460 | element.addEventListener("load", onComplete, { once: true });
|
461 | element.addEventListener("error", onComplete, { once: true });
|
462 | setTimeout(resolve, timeoutInMilliseconds);
|
463 | })
|
464 | }
|
465 |
|
466 | function getHistoryMethodForAction(action) {
|
467 | switch (action) {
|
468 | case "replace":
|
469 | return history.replaceState
|
470 | case "advance":
|
471 | case "restore":
|
472 | return history.pushState
|
473 | }
|
474 | }
|
475 |
|
476 | function isAction(action) {
|
477 | return action == "advance" || action == "replace" || action == "restore"
|
478 | }
|
479 |
|
480 | function getVisitAction(...elements) {
|
481 | const action = getAttribute("data-turbo-action", ...elements);
|
482 |
|
483 | return isAction(action) ? action : null
|
484 | }
|
485 |
|
486 | function getMetaElement(name) {
|
487 | return document.querySelector(`meta[name="${name}"]`)
|
488 | }
|
489 |
|
490 | function getMetaContent(name) {
|
491 | const element = getMetaElement(name);
|
492 | return element && element.content
|
493 | }
|
494 |
|
495 | function getCspNonce() {
|
496 | const element = getMetaElement("csp-nonce");
|
497 |
|
498 | if (element) {
|
499 | const { nonce, content } = element;
|
500 | return nonce == "" ? content : nonce
|
501 | }
|
502 | }
|
503 |
|
504 | function setMetaContent(name, content) {
|
505 | let element = getMetaElement(name);
|
506 |
|
507 | if (!element) {
|
508 | element = document.createElement("meta");
|
509 | element.setAttribute("name", name);
|
510 |
|
511 | document.head.appendChild(element);
|
512 | }
|
513 |
|
514 | element.setAttribute("content", content);
|
515 |
|
516 | return element
|
517 | }
|
518 |
|
519 | function findClosestRecursively(element, selector) {
|
520 | if (element instanceof Element) {
|
521 | return (
|
522 | element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector)
|
523 | )
|
524 | }
|
525 | }
|
526 |
|
527 | function elementIsFocusable(element) {
|
528 | const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
529 |
|
530 | return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function"
|
531 | }
|
532 |
|
533 | function queryAutofocusableElement(elementOrDocumentFragment) {
|
534 | return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable)
|
535 | }
|
536 |
|
537 | async function around(callback, reader) {
|
538 | const before = reader();
|
539 |
|
540 | callback();
|
541 |
|
542 | await nextAnimationFrame();
|
543 |
|
544 | const after = reader();
|
545 |
|
546 | return [before, after]
|
547 | }
|
548 |
|
549 | function doesNotTargetIFrame(name) {
|
550 | if (name === "_blank") {
|
551 | return false
|
552 | } else if (name) {
|
553 | for (const element of document.getElementsByName(name)) {
|
554 | if (element instanceof HTMLIFrameElement) return false
|
555 | }
|
556 |
|
557 | return true
|
558 | } else {
|
559 | return true
|
560 | }
|
561 | }
|
562 |
|
563 | function findLinkFromClickTarget(target) {
|
564 | return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
|
565 | }
|
566 |
|
567 | function getLocationForLink(link) {
|
568 | return expandURL(link.getAttribute("href") || "")
|
569 | }
|
570 |
|
571 | function debounce(fn, delay) {
|
572 | let timeoutId = null;
|
573 |
|
574 | return (...args) => {
|
575 | const callback = () => fn.apply(this, args);
|
576 | clearTimeout(timeoutId);
|
577 | timeoutId = setTimeout(callback, delay);
|
578 | }
|
579 | }
|
580 |
|
581 | const submitter = {
|
582 | "aria-disabled": {
|
583 | beforeSubmit: submitter => {
|
584 | submitter.setAttribute("aria-disabled", "true");
|
585 | submitter.addEventListener("click", cancelEvent);
|
586 | },
|
587 |
|
588 | afterSubmit: submitter => {
|
589 | submitter.removeAttribute("aria-disabled");
|
590 | submitter.removeEventListener("click", cancelEvent);
|
591 | }
|
592 | },
|
593 |
|
594 | "disabled": {
|
595 | beforeSubmit: submitter => submitter.disabled = true,
|
596 | afterSubmit: submitter => submitter.disabled = false
|
597 | }
|
598 | };
|
599 |
|
600 | class Config {
|
601 | #submitter = null
|
602 |
|
603 | constructor(config) {
|
604 | Object.assign(this, config);
|
605 | }
|
606 |
|
607 | get submitter() {
|
608 | return this.#submitter
|
609 | }
|
610 |
|
611 | set submitter(value) {
|
612 | this.#submitter = submitter[value] || value;
|
613 | }
|
614 | }
|
615 |
|
616 | const forms = new Config({
|
617 | mode: "on",
|
618 | submitter: "disabled"
|
619 | });
|
620 |
|
621 | const config = {
|
622 | drive,
|
623 | forms
|
624 | };
|
625 |
|
626 | function expandURL(locatable) {
|
627 | return new URL(locatable.toString(), document.baseURI)
|
628 | }
|
629 |
|
630 | function getAnchor(url) {
|
631 | let anchorMatch;
|
632 | if (url.hash) {
|
633 | return url.hash.slice(1)
|
634 |
|
635 | } else if ((anchorMatch = url.href.match(/#(.*)$/))) {
|
636 | return anchorMatch[1]
|
637 | }
|
638 | }
|
639 |
|
640 | function getAction$1(form, submitter) {
|
641 | const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
642 |
|
643 | return expandURL(action)
|
644 | }
|
645 |
|
646 | function getExtension(url) {
|
647 | return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || ""
|
648 | }
|
649 |
|
650 | function isPrefixedBy(baseURL, url) {
|
651 | const prefix = getPrefix(url);
|
652 | return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix)
|
653 | }
|
654 |
|
655 | function locationIsVisitable(location, rootLocation) {
|
656 | return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location))
|
657 | }
|
658 |
|
659 | function getRequestURL(url) {
|
660 | const anchor = getAnchor(url);
|
661 | return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href
|
662 | }
|
663 |
|
664 | function toCacheKey(url) {
|
665 | return getRequestURL(url)
|
666 | }
|
667 |
|
668 | function urlsAreEqual(left, right) {
|
669 | return expandURL(left).href == expandURL(right).href
|
670 | }
|
671 |
|
672 | function getPathComponents(url) {
|
673 | return url.pathname.split("/").slice(1)
|
674 | }
|
675 |
|
676 | function getLastPathComponent(url) {
|
677 | return getPathComponents(url).slice(-1)[0]
|
678 | }
|
679 |
|
680 | function getPrefix(url) {
|
681 | return addTrailingSlash(url.origin + url.pathname)
|
682 | }
|
683 |
|
684 | function addTrailingSlash(value) {
|
685 | return value.endsWith("/") ? value : value + "/"
|
686 | }
|
687 |
|
688 | class FetchResponse {
|
689 | constructor(response) {
|
690 | this.response = response;
|
691 | }
|
692 |
|
693 | get succeeded() {
|
694 | return this.response.ok
|
695 | }
|
696 |
|
697 | get failed() {
|
698 | return !this.succeeded
|
699 | }
|
700 |
|
701 | get clientError() {
|
702 | return this.statusCode >= 400 && this.statusCode <= 499
|
703 | }
|
704 |
|
705 | get serverError() {
|
706 | return this.statusCode >= 500 && this.statusCode <= 599
|
707 | }
|
708 |
|
709 | get redirected() {
|
710 | return this.response.redirected
|
711 | }
|
712 |
|
713 | get location() {
|
714 | return expandURL(this.response.url)
|
715 | }
|
716 |
|
717 | get isHTML() {
|
718 | return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/)
|
719 | }
|
720 |
|
721 | get statusCode() {
|
722 | return this.response.status
|
723 | }
|
724 |
|
725 | get contentType() {
|
726 | return this.header("Content-Type")
|
727 | }
|
728 |
|
729 | get responseText() {
|
730 | return this.response.clone().text()
|
731 | }
|
732 |
|
733 | get responseHTML() {
|
734 | if (this.isHTML) {
|
735 | return this.response.clone().text()
|
736 | } else {
|
737 | return Promise.resolve(undefined)
|
738 | }
|
739 | }
|
740 |
|
741 | header(name) {
|
742 | return this.response.headers.get(name)
|
743 | }
|
744 | }
|
745 |
|
746 | class LimitedSet extends Set {
|
747 | constructor(maxSize) {
|
748 | super();
|
749 | this.maxSize = maxSize;
|
750 | }
|
751 |
|
752 | add(value) {
|
753 | if (this.size >= this.maxSize) {
|
754 | const iterator = this.values();
|
755 | const oldestValue = iterator.next().value;
|
756 | this.delete(oldestValue);
|
757 | }
|
758 | super.add(value);
|
759 | }
|
760 | }
|
761 |
|
762 | const recentRequests = new LimitedSet(20);
|
763 |
|
764 | const nativeFetch = window.fetch;
|
765 |
|
766 | function fetchWithTurboHeaders(url, options = {}) {
|
767 | const modifiedHeaders = new Headers(options.headers || {});
|
768 | const requestUID = uuid();
|
769 | recentRequests.add(requestUID);
|
770 | modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
|
771 |
|
772 | return nativeFetch(url, {
|
773 | ...options,
|
774 | headers: modifiedHeaders
|
775 | })
|
776 | }
|
777 |
|
778 | function fetchMethodFromString(method) {
|
779 | switch (method.toLowerCase()) {
|
780 | case "get":
|
781 | return FetchMethod.get
|
782 | case "post":
|
783 | return FetchMethod.post
|
784 | case "put":
|
785 | return FetchMethod.put
|
786 | case "patch":
|
787 | return FetchMethod.patch
|
788 | case "delete":
|
789 | return FetchMethod.delete
|
790 | }
|
791 | }
|
792 |
|
793 | const FetchMethod = {
|
794 | get: "get",
|
795 | post: "post",
|
796 | put: "put",
|
797 | patch: "patch",
|
798 | delete: "delete"
|
799 | };
|
800 |
|
801 | function fetchEnctypeFromString(encoding) {
|
802 | switch (encoding.toLowerCase()) {
|
803 | case FetchEnctype.multipart:
|
804 | return FetchEnctype.multipart
|
805 | case FetchEnctype.plain:
|
806 | return FetchEnctype.plain
|
807 | default:
|
808 | return FetchEnctype.urlEncoded
|
809 | }
|
810 | }
|
811 |
|
812 | const FetchEnctype = {
|
813 | urlEncoded: "application/x-www-form-urlencoded",
|
814 | multipart: "multipart/form-data",
|
815 | plain: "text/plain"
|
816 | };
|
817 |
|
818 | class FetchRequest {
|
819 | abortController = new AbortController()
|
820 | #resolveRequestPromise = (_value) => {}
|
821 |
|
822 | constructor(delegate, method, location, requestBody = new URLSearchParams(), target = null, enctype = FetchEnctype.urlEncoded) {
|
823 | const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
|
824 |
|
825 | this.delegate = delegate;
|
826 | this.url = url;
|
827 | this.target = target;
|
828 | this.fetchOptions = {
|
829 | credentials: "same-origin",
|
830 | redirect: "follow",
|
831 | method: method.toUpperCase(),
|
832 | headers: { ...this.defaultHeaders },
|
833 | body: body,
|
834 | signal: this.abortSignal,
|
835 | referrer: this.delegate.referrer?.href
|
836 | };
|
837 | this.enctype = enctype;
|
838 | }
|
839 |
|
840 | get method() {
|
841 | return this.fetchOptions.method
|
842 | }
|
843 |
|
844 | set method(value) {
|
845 | const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData();
|
846 | const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
|
847 |
|
848 | this.url.search = "";
|
849 |
|
850 | const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
851 |
|
852 | this.url = url;
|
853 | this.fetchOptions.body = body;
|
854 | this.fetchOptions.method = fetchMethod.toUpperCase();
|
855 | }
|
856 |
|
857 | get headers() {
|
858 | return this.fetchOptions.headers
|
859 | }
|
860 |
|
861 | set headers(value) {
|
862 | this.fetchOptions.headers = value;
|
863 | }
|
864 |
|
865 | get body() {
|
866 | if (this.isSafe) {
|
867 | return this.url.searchParams
|
868 | } else {
|
869 | return this.fetchOptions.body
|
870 | }
|
871 | }
|
872 |
|
873 | set body(value) {
|
874 | this.fetchOptions.body = value;
|
875 | }
|
876 |
|
877 | get location() {
|
878 | return this.url
|
879 | }
|
880 |
|
881 | get params() {
|
882 | return this.url.searchParams
|
883 | }
|
884 |
|
885 | get entries() {
|
886 | return this.body ? Array.from(this.body.entries()) : []
|
887 | }
|
888 |
|
889 | cancel() {
|
890 | this.abortController.abort();
|
891 | }
|
892 |
|
893 | async perform() {
|
894 | const { fetchOptions } = this;
|
895 | this.delegate.prepareRequest(this);
|
896 | const event = await this.#allowRequestToBeIntercepted(fetchOptions);
|
897 | try {
|
898 | this.delegate.requestStarted(this);
|
899 |
|
900 | if (event.detail.fetchRequest) {
|
901 | this.response = event.detail.fetchRequest.response;
|
902 | } else {
|
903 | this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
|
904 | }
|
905 |
|
906 | const response = await this.response;
|
907 | return await this.receive(response)
|
908 | } catch (error) {
|
909 | if (error.name !== "AbortError") {
|
910 | if (this.#willDelegateErrorHandling(error)) {
|
911 | this.delegate.requestErrored(this, error);
|
912 | }
|
913 | throw error
|
914 | }
|
915 | } finally {
|
916 | this.delegate.requestFinished(this);
|
917 | }
|
918 | }
|
919 |
|
920 | async receive(response) {
|
921 | const fetchResponse = new FetchResponse(response);
|
922 | const event = dispatch("turbo:before-fetch-response", {
|
923 | cancelable: true,
|
924 | detail: { fetchResponse },
|
925 | target: this.target
|
926 | });
|
927 | if (event.defaultPrevented) {
|
928 | this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
|
929 | } else if (fetchResponse.succeeded) {
|
930 | this.delegate.requestSucceededWithResponse(this, fetchResponse);
|
931 | } else {
|
932 | this.delegate.requestFailedWithResponse(this, fetchResponse);
|
933 | }
|
934 | return fetchResponse
|
935 | }
|
936 |
|
937 | get defaultHeaders() {
|
938 | return {
|
939 | Accept: "text/html, application/xhtml+xml"
|
940 | }
|
941 | }
|
942 |
|
943 | get isSafe() {
|
944 | return isSafe(this.method)
|
945 | }
|
946 |
|
947 | get abortSignal() {
|
948 | return this.abortController.signal
|
949 | }
|
950 |
|
951 | acceptResponseType(mimeType) {
|
952 | this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
|
953 | }
|
954 |
|
955 | async #allowRequestToBeIntercepted(fetchOptions) {
|
956 | const requestInterception = new Promise((resolve) => (this.#resolveRequestPromise = resolve));
|
957 | const event = dispatch("turbo:before-fetch-request", {
|
958 | cancelable: true,
|
959 | detail: {
|
960 | fetchOptions,
|
961 | url: this.url,
|
962 | resume: this.#resolveRequestPromise
|
963 | },
|
964 | target: this.target
|
965 | });
|
966 | this.url = event.detail.url;
|
967 | if (event.defaultPrevented) await requestInterception;
|
968 |
|
969 | return event
|
970 | }
|
971 |
|
972 | #willDelegateErrorHandling(error) {
|
973 | const event = dispatch("turbo:fetch-request-error", {
|
974 | target: this.target,
|
975 | cancelable: true,
|
976 | detail: { request: this, error: error }
|
977 | });
|
978 |
|
979 | return !event.defaultPrevented
|
980 | }
|
981 | }
|
982 |
|
983 | function isSafe(fetchMethod) {
|
984 | return fetchMethodFromString(fetchMethod) == FetchMethod.get
|
985 | }
|
986 |
|
987 | function buildResourceAndBody(resource, method, requestBody, enctype) {
|
988 | const searchParams =
|
989 | Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
|
990 |
|
991 | if (isSafe(method)) {
|
992 | return [mergeIntoURLSearchParams(resource, searchParams), null]
|
993 | } else if (enctype == FetchEnctype.urlEncoded) {
|
994 | return [resource, searchParams]
|
995 | } else {
|
996 | return [resource, requestBody]
|
997 | }
|
998 | }
|
999 |
|
1000 | function entriesExcludingFiles(requestBody) {
|
1001 | const entries = [];
|
1002 |
|
1003 | for (const [name, value] of requestBody) {
|
1004 | if (value instanceof File) continue
|
1005 | else entries.push([name, value]);
|
1006 | }
|
1007 |
|
1008 | return entries
|
1009 | }
|
1010 |
|
1011 | function mergeIntoURLSearchParams(url, requestBody) {
|
1012 | const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
|
1013 |
|
1014 | url.search = searchParams.toString();
|
1015 |
|
1016 | return url
|
1017 | }
|
1018 |
|
1019 | class AppearanceObserver {
|
1020 | started = false
|
1021 |
|
1022 | constructor(delegate, element) {
|
1023 | this.delegate = delegate;
|
1024 | this.element = element;
|
1025 | this.intersectionObserver = new IntersectionObserver(this.intersect);
|
1026 | }
|
1027 |
|
1028 | start() {
|
1029 | if (!this.started) {
|
1030 | this.started = true;
|
1031 | this.intersectionObserver.observe(this.element);
|
1032 | }
|
1033 | }
|
1034 |
|
1035 | stop() {
|
1036 | if (this.started) {
|
1037 | this.started = false;
|
1038 | this.intersectionObserver.unobserve(this.element);
|
1039 | }
|
1040 | }
|
1041 |
|
1042 | intersect = (entries) => {
|
1043 | const lastEntry = entries.slice(-1)[0];
|
1044 | if (lastEntry?.isIntersecting) {
|
1045 | this.delegate.elementAppearedInViewport(this.element);
|
1046 | }
|
1047 | }
|
1048 | }
|
1049 |
|
1050 | class StreamMessage {
|
1051 | static contentType = "text/vnd.turbo-stream.html"
|
1052 |
|
1053 | static wrap(message) {
|
1054 | if (typeof message == "string") {
|
1055 | return new this(createDocumentFragment(message))
|
1056 | } else {
|
1057 | return message
|
1058 | }
|
1059 | }
|
1060 |
|
1061 | constructor(fragment) {
|
1062 | this.fragment = importStreamElements(fragment);
|
1063 | }
|
1064 | }
|
1065 |
|
1066 | function importStreamElements(fragment) {
|
1067 | for (const element of fragment.querySelectorAll("turbo-stream")) {
|
1068 | const streamElement = document.importNode(element, true);
|
1069 |
|
1070 | for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
|
1071 | inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
|
1072 | }
|
1073 |
|
1074 | element.replaceWith(streamElement);
|
1075 | }
|
1076 |
|
1077 | return fragment
|
1078 | }
|
1079 |
|
1080 | const PREFETCH_DELAY = 100;
|
1081 |
|
1082 | class PrefetchCache {
|
1083 | #prefetchTimeout = null
|
1084 | #prefetched = null
|
1085 |
|
1086 | get(url) {
|
1087 | if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
|
1088 | return this.#prefetched.request
|
1089 | }
|
1090 | }
|
1091 |
|
1092 | setLater(url, request, ttl) {
|
1093 | this.clear();
|
1094 |
|
1095 | this.#prefetchTimeout = setTimeout(() => {
|
1096 | request.perform();
|
1097 | this.set(url, request, ttl);
|
1098 | this.#prefetchTimeout = null;
|
1099 | }, PREFETCH_DELAY);
|
1100 | }
|
1101 |
|
1102 | set(url, request, ttl) {
|
1103 | this.#prefetched = { url, request, expire: new Date(new Date().getTime() + ttl) };
|
1104 | }
|
1105 |
|
1106 | clear() {
|
1107 | if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
|
1108 | this.#prefetched = null;
|
1109 | }
|
1110 | }
|
1111 |
|
1112 | const cacheTtl = 10 * 1000;
|
1113 | const prefetchCache = new PrefetchCache();
|
1114 |
|
1115 | const FormSubmissionState = {
|
1116 | initialized: "initialized",
|
1117 | requesting: "requesting",
|
1118 | waiting: "waiting",
|
1119 | receiving: "receiving",
|
1120 | stopping: "stopping",
|
1121 | stopped: "stopped"
|
1122 | };
|
1123 |
|
1124 | class FormSubmission {
|
1125 | state = FormSubmissionState.initialized
|
1126 |
|
1127 | static confirmMethod(message) {
|
1128 | return Promise.resolve(confirm(message))
|
1129 | }
|
1130 |
|
1131 | constructor(delegate, formElement, submitter, mustRedirect = false) {
|
1132 | const method = getMethod(formElement, submitter);
|
1133 | const action = getAction(getFormAction(formElement, submitter), method);
|
1134 | const body = buildFormData(formElement, submitter);
|
1135 | const enctype = getEnctype(formElement, submitter);
|
1136 |
|
1137 | this.delegate = delegate;
|
1138 | this.formElement = formElement;
|
1139 | this.submitter = submitter;
|
1140 | this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
|
1141 | this.mustRedirect = mustRedirect;
|
1142 | }
|
1143 |
|
1144 | get method() {
|
1145 | return this.fetchRequest.method
|
1146 | }
|
1147 |
|
1148 | set method(value) {
|
1149 | this.fetchRequest.method = value;
|
1150 | }
|
1151 |
|
1152 | get action() {
|
1153 | return this.fetchRequest.url.toString()
|
1154 | }
|
1155 |
|
1156 | set action(value) {
|
1157 | this.fetchRequest.url = expandURL(value);
|
1158 | }
|
1159 |
|
1160 | get body() {
|
1161 | return this.fetchRequest.body
|
1162 | }
|
1163 |
|
1164 | get enctype() {
|
1165 | return this.fetchRequest.enctype
|
1166 | }
|
1167 |
|
1168 | get isSafe() {
|
1169 | return this.fetchRequest.isSafe
|
1170 | }
|
1171 |
|
1172 | get location() {
|
1173 | return this.fetchRequest.url
|
1174 | }
|
1175 |
|
1176 |
|
1177 |
|
1178 | async start() {
|
1179 | const { initialized, requesting } = FormSubmissionState;
|
1180 | const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
1181 |
|
1182 | if (typeof confirmationMessage === "string") {
|
1183 | const confirmMethod = typeof config.forms.confirm === "function" ?
|
1184 | config.forms.confirm :
|
1185 | FormSubmission.confirmMethod;
|
1186 |
|
1187 | const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
1188 | if (!answer) {
|
1189 | return
|
1190 | }
|
1191 | }
|
1192 |
|
1193 | if (this.state == initialized) {
|
1194 | this.state = requesting;
|
1195 | return this.fetchRequest.perform()
|
1196 | }
|
1197 | }
|
1198 |
|
1199 | stop() {
|
1200 | const { stopping, stopped } = FormSubmissionState;
|
1201 | if (this.state != stopping && this.state != stopped) {
|
1202 | this.state = stopping;
|
1203 | this.fetchRequest.cancel();
|
1204 | return true
|
1205 | }
|
1206 | }
|
1207 |
|
1208 |
|
1209 |
|
1210 | prepareRequest(request) {
|
1211 | if (!request.isSafe) {
|
1212 | const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
1213 | if (token) {
|
1214 | request.headers["X-CSRF-Token"] = token;
|
1215 | }
|
1216 | }
|
1217 |
|
1218 | if (this.requestAcceptsTurboStreamResponse(request)) {
|
1219 | request.acceptResponseType(StreamMessage.contentType);
|
1220 | }
|
1221 | }
|
1222 |
|
1223 | requestStarted(_request) {
|
1224 | this.state = FormSubmissionState.waiting;
|
1225 | if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);
|
1226 | this.setSubmitsWith();
|
1227 | markAsBusy(this.formElement);
|
1228 | dispatch("turbo:submit-start", {
|
1229 | target: this.formElement,
|
1230 | detail: { formSubmission: this }
|
1231 | });
|
1232 | this.delegate.formSubmissionStarted(this);
|
1233 | }
|
1234 |
|
1235 | requestPreventedHandlingResponse(request, response) {
|
1236 | prefetchCache.clear();
|
1237 |
|
1238 | this.result = { success: response.succeeded, fetchResponse: response };
|
1239 | }
|
1240 |
|
1241 | requestSucceededWithResponse(request, response) {
|
1242 | if (response.clientError || response.serverError) {
|
1243 | this.delegate.formSubmissionFailedWithResponse(this, response);
|
1244 | return
|
1245 | }
|
1246 |
|
1247 | prefetchCache.clear();
|
1248 |
|
1249 | if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
|
1250 | const error = new Error("Form responses must redirect to another location");
|
1251 | this.delegate.formSubmissionErrored(this, error);
|
1252 | } else {
|
1253 | this.state = FormSubmissionState.receiving;
|
1254 | this.result = { success: true, fetchResponse: response };
|
1255 | this.delegate.formSubmissionSucceededWithResponse(this, response);
|
1256 | }
|
1257 | }
|
1258 |
|
1259 | requestFailedWithResponse(request, response) {
|
1260 | this.result = { success: false, fetchResponse: response };
|
1261 | this.delegate.formSubmissionFailedWithResponse(this, response);
|
1262 | }
|
1263 |
|
1264 | requestErrored(request, error) {
|
1265 | this.result = { success: false, error };
|
1266 | this.delegate.formSubmissionErrored(this, error);
|
1267 | }
|
1268 |
|
1269 | requestFinished(_request) {
|
1270 | this.state = FormSubmissionState.stopped;
|
1271 | if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);
|
1272 | this.resetSubmitterText();
|
1273 | clearBusyState(this.formElement);
|
1274 | dispatch("turbo:submit-end", {
|
1275 | target: this.formElement,
|
1276 | detail: { formSubmission: this, ...this.result }
|
1277 | });
|
1278 | this.delegate.formSubmissionFinished(this);
|
1279 | }
|
1280 |
|
1281 |
|
1282 |
|
1283 | setSubmitsWith() {
|
1284 | if (!this.submitter || !this.submitsWith) return
|
1285 |
|
1286 | if (this.submitter.matches("button")) {
|
1287 | this.originalSubmitText = this.submitter.innerHTML;
|
1288 | this.submitter.innerHTML = this.submitsWith;
|
1289 | } else if (this.submitter.matches("input")) {
|
1290 | const input = this.submitter;
|
1291 | this.originalSubmitText = input.value;
|
1292 | input.value = this.submitsWith;
|
1293 | }
|
1294 | }
|
1295 |
|
1296 | resetSubmitterText() {
|
1297 | if (!this.submitter || !this.originalSubmitText) return
|
1298 |
|
1299 | if (this.submitter.matches("button")) {
|
1300 | this.submitter.innerHTML = this.originalSubmitText;
|
1301 | } else if (this.submitter.matches("input")) {
|
1302 | const input = this.submitter;
|
1303 | input.value = this.originalSubmitText;
|
1304 | }
|
1305 | }
|
1306 |
|
1307 | requestMustRedirect(request) {
|
1308 | return !request.isSafe && this.mustRedirect
|
1309 | }
|
1310 |
|
1311 | requestAcceptsTurboStreamResponse(request) {
|
1312 | return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement)
|
1313 | }
|
1314 |
|
1315 | get submitsWith() {
|
1316 | return this.submitter?.getAttribute("data-turbo-submits-with")
|
1317 | }
|
1318 | }
|
1319 |
|
1320 | function buildFormData(formElement, submitter) {
|
1321 | const formData = new FormData(formElement);
|
1322 | const name = submitter?.getAttribute("name");
|
1323 | const value = submitter?.getAttribute("value");
|
1324 |
|
1325 | if (name) {
|
1326 | formData.append(name, value || "");
|
1327 | }
|
1328 |
|
1329 | return formData
|
1330 | }
|
1331 |
|
1332 | function getCookieValue(cookieName) {
|
1333 | if (cookieName != null) {
|
1334 | const cookies = document.cookie ? document.cookie.split("; ") : [];
|
1335 | const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));
|
1336 | if (cookie) {
|
1337 | const value = cookie.split("=").slice(1).join("=");
|
1338 | return value ? decodeURIComponent(value) : undefined
|
1339 | }
|
1340 | }
|
1341 | }
|
1342 |
|
1343 | function responseSucceededWithoutRedirect(response) {
|
1344 | return response.statusCode == 200 && !response.redirected
|
1345 | }
|
1346 |
|
1347 | function getFormAction(formElement, submitter) {
|
1348 | const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
|
1349 |
|
1350 | if (submitter?.hasAttribute("formaction")) {
|
1351 | return submitter.getAttribute("formaction") || ""
|
1352 | } else {
|
1353 | return formElement.getAttribute("action") || formElementAction || ""
|
1354 | }
|
1355 | }
|
1356 |
|
1357 | function getAction(formAction, fetchMethod) {
|
1358 | const action = expandURL(formAction);
|
1359 |
|
1360 | if (isSafe(fetchMethod)) {
|
1361 | action.search = "";
|
1362 | }
|
1363 |
|
1364 | return action
|
1365 | }
|
1366 |
|
1367 | function getMethod(formElement, submitter) {
|
1368 | const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
|
1369 | return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get
|
1370 | }
|
1371 |
|
1372 | function getEnctype(formElement, submitter) {
|
1373 | return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype)
|
1374 | }
|
1375 |
|
1376 | class Snapshot {
|
1377 | constructor(element) {
|
1378 | this.element = element;
|
1379 | }
|
1380 |
|
1381 | get activeElement() {
|
1382 | return this.element.ownerDocument.activeElement
|
1383 | }
|
1384 |
|
1385 | get children() {
|
1386 | return [...this.element.children]
|
1387 | }
|
1388 |
|
1389 | hasAnchor(anchor) {
|
1390 | return this.getElementForAnchor(anchor) != null
|
1391 | }
|
1392 |
|
1393 | getElementForAnchor(anchor) {
|
1394 | return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null
|
1395 | }
|
1396 |
|
1397 | get isConnected() {
|
1398 | return this.element.isConnected
|
1399 | }
|
1400 |
|
1401 | get firstAutofocusableElement() {
|
1402 | return queryAutofocusableElement(this.element)
|
1403 | }
|
1404 |
|
1405 | get permanentElements() {
|
1406 | return queryPermanentElementsAll(this.element)
|
1407 | }
|
1408 |
|
1409 | getPermanentElementById(id) {
|
1410 | return getPermanentElementById(this.element, id)
|
1411 | }
|
1412 |
|
1413 | getPermanentElementMapForSnapshot(snapshot) {
|
1414 | const permanentElementMap = {};
|
1415 |
|
1416 | for (const currentPermanentElement of this.permanentElements) {
|
1417 | const { id } = currentPermanentElement;
|
1418 | const newPermanentElement = snapshot.getPermanentElementById(id);
|
1419 | if (newPermanentElement) {
|
1420 | permanentElementMap[id] = [currentPermanentElement, newPermanentElement];
|
1421 | }
|
1422 | }
|
1423 |
|
1424 | return permanentElementMap
|
1425 | }
|
1426 | }
|
1427 |
|
1428 | function getPermanentElementById(node, id) {
|
1429 | return node.querySelector(`#${id}[data-turbo-permanent]`)
|
1430 | }
|
1431 |
|
1432 | function queryPermanentElementsAll(node) {
|
1433 | return node.querySelectorAll("[id][data-turbo-permanent]")
|
1434 | }
|
1435 |
|
1436 | class FormSubmitObserver {
|
1437 | started = false
|
1438 |
|
1439 | constructor(delegate, eventTarget) {
|
1440 | this.delegate = delegate;
|
1441 | this.eventTarget = eventTarget;
|
1442 | }
|
1443 |
|
1444 | start() {
|
1445 | if (!this.started) {
|
1446 | this.eventTarget.addEventListener("submit", this.submitCaptured, true);
|
1447 | this.started = true;
|
1448 | }
|
1449 | }
|
1450 |
|
1451 | stop() {
|
1452 | if (this.started) {
|
1453 | this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
|
1454 | this.started = false;
|
1455 | }
|
1456 | }
|
1457 |
|
1458 | submitCaptured = () => {
|
1459 | this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
1460 | this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
1461 | }
|
1462 |
|
1463 | submitBubbled = (event) => {
|
1464 | if (!event.defaultPrevented) {
|
1465 | const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
1466 | const submitter = event.submitter || undefined;
|
1467 |
|
1468 | if (
|
1469 | form &&
|
1470 | submissionDoesNotDismissDialog(form, submitter) &&
|
1471 | submissionDoesNotTargetIFrame(form, submitter) &&
|
1472 | this.delegate.willSubmitForm(form, submitter)
|
1473 | ) {
|
1474 | event.preventDefault();
|
1475 | event.stopImmediatePropagation();
|
1476 | this.delegate.formSubmitted(form, submitter);
|
1477 | }
|
1478 | }
|
1479 | }
|
1480 | }
|
1481 |
|
1482 | function submissionDoesNotDismissDialog(form, submitter) {
|
1483 | const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
|
1484 |
|
1485 | return method != "dialog"
|
1486 | }
|
1487 |
|
1488 | function submissionDoesNotTargetIFrame(form, submitter) {
|
1489 | const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
1490 |
|
1491 | return doesNotTargetIFrame(target)
|
1492 | }
|
1493 |
|
1494 | class View {
|
1495 | #resolveRenderPromise = (_value) => {}
|
1496 | #resolveInterceptionPromise = (_value) => {}
|
1497 |
|
1498 | constructor(delegate, element) {
|
1499 | this.delegate = delegate;
|
1500 | this.element = element;
|
1501 | }
|
1502 |
|
1503 |
|
1504 |
|
1505 | scrollToAnchor(anchor) {
|
1506 | const element = this.snapshot.getElementForAnchor(anchor);
|
1507 | if (element) {
|
1508 | this.scrollToElement(element);
|
1509 | this.focusElement(element);
|
1510 | } else {
|
1511 | this.scrollToPosition({ x: 0, y: 0 });
|
1512 | }
|
1513 | }
|
1514 |
|
1515 | scrollToAnchorFromLocation(location) {
|
1516 | this.scrollToAnchor(getAnchor(location));
|
1517 | }
|
1518 |
|
1519 | scrollToElement(element) {
|
1520 | element.scrollIntoView();
|
1521 | }
|
1522 |
|
1523 | focusElement(element) {
|
1524 | if (element instanceof HTMLElement) {
|
1525 | if (element.hasAttribute("tabindex")) {
|
1526 | element.focus();
|
1527 | } else {
|
1528 | element.setAttribute("tabindex", "-1");
|
1529 | element.focus();
|
1530 | element.removeAttribute("tabindex");
|
1531 | }
|
1532 | }
|
1533 | }
|
1534 |
|
1535 | scrollToPosition({ x, y }) {
|
1536 | this.scrollRoot.scrollTo(x, y);
|
1537 | }
|
1538 |
|
1539 | scrollToTop() {
|
1540 | this.scrollToPosition({ x: 0, y: 0 });
|
1541 | }
|
1542 |
|
1543 | get scrollRoot() {
|
1544 | return window
|
1545 | }
|
1546 |
|
1547 |
|
1548 |
|
1549 | async render(renderer) {
|
1550 | const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer;
|
1551 |
|
1552 |
|
1553 |
|
1554 | const shouldInvalidate = willRender;
|
1555 |
|
1556 | if (shouldRender) {
|
1557 | try {
|
1558 | this.renderPromise = new Promise((resolve) => (this.#resolveRenderPromise = resolve));
|
1559 | this.renderer = renderer;
|
1560 | await this.prepareToRenderSnapshot(renderer);
|
1561 |
|
1562 | const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve));
|
1563 | const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod };
|
1564 | const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
1565 | if (!immediateRender) await renderInterception;
|
1566 |
|
1567 | await this.renderSnapshot(renderer);
|
1568 | this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
|
1569 | this.delegate.preloadOnLoadLinksForView(this.element);
|
1570 | this.finishRenderingSnapshot(renderer);
|
1571 | } finally {
|
1572 | delete this.renderer;
|
1573 | this.#resolveRenderPromise(undefined);
|
1574 | delete this.renderPromise;
|
1575 | }
|
1576 | } else if (shouldInvalidate) {
|
1577 | this.invalidate(renderer.reloadReason);
|
1578 | }
|
1579 | }
|
1580 |
|
1581 | invalidate(reason) {
|
1582 | this.delegate.viewInvalidated(reason);
|
1583 | }
|
1584 |
|
1585 | async prepareToRenderSnapshot(renderer) {
|
1586 | this.markAsPreview(renderer.isPreview);
|
1587 | await renderer.prepareToRender();
|
1588 | }
|
1589 |
|
1590 | markAsPreview(isPreview) {
|
1591 | if (isPreview) {
|
1592 | this.element.setAttribute("data-turbo-preview", "");
|
1593 | } else {
|
1594 | this.element.removeAttribute("data-turbo-preview");
|
1595 | }
|
1596 | }
|
1597 |
|
1598 | markVisitDirection(direction) {
|
1599 | this.element.setAttribute("data-turbo-visit-direction", direction);
|
1600 | }
|
1601 |
|
1602 | unmarkVisitDirection() {
|
1603 | this.element.removeAttribute("data-turbo-visit-direction");
|
1604 | }
|
1605 |
|
1606 | async renderSnapshot(renderer) {
|
1607 | await renderer.render();
|
1608 | }
|
1609 |
|
1610 | finishRenderingSnapshot(renderer) {
|
1611 | renderer.finishRendering();
|
1612 | }
|
1613 | }
|
1614 |
|
1615 | class FrameView extends View {
|
1616 | missing() {
|
1617 | this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
|
1618 | }
|
1619 |
|
1620 | get snapshot() {
|
1621 | return new Snapshot(this.element)
|
1622 | }
|
1623 | }
|
1624 |
|
1625 | class LinkInterceptor {
|
1626 | constructor(delegate, element) {
|
1627 | this.delegate = delegate;
|
1628 | this.element = element;
|
1629 | }
|
1630 |
|
1631 | start() {
|
1632 | this.element.addEventListener("click", this.clickBubbled);
|
1633 | document.addEventListener("turbo:click", this.linkClicked);
|
1634 | document.addEventListener("turbo:before-visit", this.willVisit);
|
1635 | }
|
1636 |
|
1637 | stop() {
|
1638 | this.element.removeEventListener("click", this.clickBubbled);
|
1639 | document.removeEventListener("turbo:click", this.linkClicked);
|
1640 | document.removeEventListener("turbo:before-visit", this.willVisit);
|
1641 | }
|
1642 |
|
1643 | clickBubbled = (event) => {
|
1644 | if (this.clickEventIsSignificant(event)) {
|
1645 | this.clickEvent = event;
|
1646 | } else {
|
1647 | delete this.clickEvent;
|
1648 | }
|
1649 | }
|
1650 |
|
1651 | linkClicked = (event) => {
|
1652 | if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
1653 | if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1654 | this.clickEvent.preventDefault();
|
1655 | event.preventDefault();
|
1656 | this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1657 | }
|
1658 | }
|
1659 | delete this.clickEvent;
|
1660 | }
|
1661 |
|
1662 | willVisit = (_event) => {
|
1663 | delete this.clickEvent;
|
1664 | }
|
1665 |
|
1666 | clickEventIsSignificant(event) {
|
1667 | const target = event.composed ? event.target?.parentElement : event.target;
|
1668 | const element = findLinkFromClickTarget(target) || target;
|
1669 |
|
1670 | return element instanceof Element && element.closest("turbo-frame, html") == this.element
|
1671 | }
|
1672 | }
|
1673 |
|
1674 | class LinkClickObserver {
|
1675 | started = false
|
1676 |
|
1677 | constructor(delegate, eventTarget) {
|
1678 | this.delegate = delegate;
|
1679 | this.eventTarget = eventTarget;
|
1680 | }
|
1681 |
|
1682 | start() {
|
1683 | if (!this.started) {
|
1684 | this.eventTarget.addEventListener("click", this.clickCaptured, true);
|
1685 | this.started = true;
|
1686 | }
|
1687 | }
|
1688 |
|
1689 | stop() {
|
1690 | if (this.started) {
|
1691 | this.eventTarget.removeEventListener("click", this.clickCaptured, true);
|
1692 | this.started = false;
|
1693 | }
|
1694 | }
|
1695 |
|
1696 | clickCaptured = () => {
|
1697 | this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1698 | this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
1699 | }
|
1700 |
|
1701 | clickBubbled = (event) => {
|
1702 | if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1703 | const target = (event.composedPath && event.composedPath()[0]) || event.target;
|
1704 | const link = findLinkFromClickTarget(target);
|
1705 | if (link && doesNotTargetIFrame(link.target)) {
|
1706 | const location = getLocationForLink(link);
|
1707 | if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1708 | event.preventDefault();
|
1709 | this.delegate.followedLinkToLocation(link, location);
|
1710 | }
|
1711 | }
|
1712 | }
|
1713 | }
|
1714 |
|
1715 | clickEventIsSignificant(event) {
|
1716 | return !(
|
1717 | (event.target && event.target.isContentEditable) ||
|
1718 | event.defaultPrevented ||
|
1719 | event.which > 1 ||
|
1720 | event.altKey ||
|
1721 | event.ctrlKey ||
|
1722 | event.metaKey ||
|
1723 | event.shiftKey
|
1724 | )
|
1725 | }
|
1726 | }
|
1727 |
|
1728 | class FormLinkClickObserver {
|
1729 | constructor(delegate, element) {
|
1730 | this.delegate = delegate;
|
1731 | this.linkInterceptor = new LinkClickObserver(this, element);
|
1732 | }
|
1733 |
|
1734 | start() {
|
1735 | this.linkInterceptor.start();
|
1736 | }
|
1737 |
|
1738 | stop() {
|
1739 | this.linkInterceptor.stop();
|
1740 | }
|
1741 |
|
1742 |
|
1743 |
|
1744 | canPrefetchRequestToLocation(link, location) {
|
1745 | return false
|
1746 | }
|
1747 |
|
1748 | prefetchAndCacheRequestToLocation(link, location) {
|
1749 | return
|
1750 | }
|
1751 |
|
1752 |
|
1753 |
|
1754 | willFollowLinkToLocation(link, location, originalEvent) {
|
1755 | return (
|
1756 | this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
|
1757 | (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"))
|
1758 | )
|
1759 | }
|
1760 |
|
1761 | followedLinkToLocation(link, location) {
|
1762 | const form = document.createElement("form");
|
1763 |
|
1764 | const type = "hidden";
|
1765 | for (const [name, value] of location.searchParams) {
|
1766 | form.append(Object.assign(document.createElement("input"), { type, name, value }));
|
1767 | }
|
1768 |
|
1769 | const action = Object.assign(location, { search: "" });
|
1770 | form.setAttribute("data-turbo", "true");
|
1771 | form.setAttribute("action", action.href);
|
1772 | form.setAttribute("hidden", "");
|
1773 |
|
1774 | const method = link.getAttribute("data-turbo-method");
|
1775 | if (method) form.setAttribute("method", method);
|
1776 |
|
1777 | const turboFrame = link.getAttribute("data-turbo-frame");
|
1778 | if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
|
1779 |
|
1780 | const turboAction = getVisitAction(link);
|
1781 | if (turboAction) form.setAttribute("data-turbo-action", turboAction);
|
1782 |
|
1783 | const turboConfirm = link.getAttribute("data-turbo-confirm");
|
1784 | if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
|
1785 |
|
1786 | const turboStream = link.hasAttribute("data-turbo-stream");
|
1787 | if (turboStream) form.setAttribute("data-turbo-stream", "");
|
1788 |
|
1789 | this.delegate.submittedFormLinkToLocation(link, location, form);
|
1790 |
|
1791 | document.body.appendChild(form);
|
1792 | form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
|
1793 | requestAnimationFrame(() => form.requestSubmit());
|
1794 | }
|
1795 | }
|
1796 |
|
1797 | class Bardo {
|
1798 | static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
1799 | const bardo = new this(delegate, permanentElementMap);
|
1800 | bardo.enter();
|
1801 | await callback();
|
1802 | bardo.leave();
|
1803 | }
|
1804 |
|
1805 | constructor(delegate, permanentElementMap) {
|
1806 | this.delegate = delegate;
|
1807 | this.permanentElementMap = permanentElementMap;
|
1808 | }
|
1809 |
|
1810 | enter() {
|
1811 | for (const id in this.permanentElementMap) {
|
1812 | const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
1813 | this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
|
1814 | this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
1815 | }
|
1816 | }
|
1817 |
|
1818 | leave() {
|
1819 | for (const id in this.permanentElementMap) {
|
1820 | const [currentPermanentElement] = this.permanentElementMap[id];
|
1821 | this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
1822 | this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
1823 | this.delegate.leavingBardo(currentPermanentElement);
|
1824 | }
|
1825 | }
|
1826 |
|
1827 | replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
1828 | const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
1829 | permanentElement.replaceWith(placeholder);
|
1830 | }
|
1831 |
|
1832 | replaceCurrentPermanentElementWithClone(permanentElement) {
|
1833 | const clone = permanentElement.cloneNode(true);
|
1834 | permanentElement.replaceWith(clone);
|
1835 | }
|
1836 |
|
1837 | replacePlaceholderWithPermanentElement(permanentElement) {
|
1838 | const placeholder = this.getPlaceholderById(permanentElement.id);
|
1839 | placeholder?.replaceWith(permanentElement);
|
1840 | }
|
1841 |
|
1842 | getPlaceholderById(id) {
|
1843 | return this.placeholders.find((element) => element.content == id)
|
1844 | }
|
1845 |
|
1846 | get placeholders() {
|
1847 | return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]
|
1848 | }
|
1849 | }
|
1850 |
|
1851 | function createPlaceholderForPermanentElement(permanentElement) {
|
1852 | const element = document.createElement("meta");
|
1853 | element.setAttribute("name", "turbo-permanent-placeholder");
|
1854 | element.setAttribute("content", permanentElement.id);
|
1855 | return element
|
1856 | }
|
1857 |
|
1858 | class Renderer {
|
1859 | #activeElement = null
|
1860 |
|
1861 | static renderElement(currentElement, newElement) {
|
1862 |
|
1863 | }
|
1864 |
|
1865 | constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
1866 | this.currentSnapshot = currentSnapshot;
|
1867 | this.newSnapshot = newSnapshot;
|
1868 | this.isPreview = isPreview;
|
1869 | this.willRender = willRender;
|
1870 | this.renderElement = this.constructor.renderElement;
|
1871 | this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
|
1872 | }
|
1873 |
|
1874 | get shouldRender() {
|
1875 | return true
|
1876 | }
|
1877 |
|
1878 | get shouldAutofocus() {
|
1879 | return true
|
1880 | }
|
1881 |
|
1882 | get reloadReason() {
|
1883 | return
|
1884 | }
|
1885 |
|
1886 | prepareToRender() {
|
1887 | return
|
1888 | }
|
1889 |
|
1890 | render() {
|
1891 |
|
1892 | }
|
1893 |
|
1894 | finishRendering() {
|
1895 | if (this.resolvingFunctions) {
|
1896 | this.resolvingFunctions.resolve();
|
1897 | delete this.resolvingFunctions;
|
1898 | }
|
1899 | }
|
1900 |
|
1901 | async preservingPermanentElements(callback) {
|
1902 | await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1903 | }
|
1904 |
|
1905 | focusFirstAutofocusableElement() {
|
1906 | if (this.shouldAutofocus) {
|
1907 | const element = this.connectedSnapshot.firstAutofocusableElement;
|
1908 | if (element) {
|
1909 | element.focus();
|
1910 | }
|
1911 | }
|
1912 | }
|
1913 |
|
1914 |
|
1915 |
|
1916 | enteringBardo(currentPermanentElement) {
|
1917 | if (this.#activeElement) return
|
1918 |
|
1919 | if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
1920 | this.#activeElement = this.currentSnapshot.activeElement;
|
1921 | }
|
1922 | }
|
1923 |
|
1924 | leavingBardo(currentPermanentElement) {
|
1925 | if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
|
1926 | this.#activeElement.focus();
|
1927 |
|
1928 | this.#activeElement = null;
|
1929 | }
|
1930 | }
|
1931 |
|
1932 | get connectedSnapshot() {
|
1933 | return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot
|
1934 | }
|
1935 |
|
1936 | get currentElement() {
|
1937 | return this.currentSnapshot.element
|
1938 | }
|
1939 |
|
1940 | get newElement() {
|
1941 | return this.newSnapshot.element
|
1942 | }
|
1943 |
|
1944 | get permanentElementMap() {
|
1945 | return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot)
|
1946 | }
|
1947 |
|
1948 | get renderMethod() {
|
1949 | return "replace"
|
1950 | }
|
1951 | }
|
1952 |
|
1953 | class FrameRenderer extends Renderer {
|
1954 | static renderElement(currentElement, newElement) {
|
1955 | const destinationRange = document.createRange();
|
1956 | destinationRange.selectNodeContents(currentElement);
|
1957 | destinationRange.deleteContents();
|
1958 |
|
1959 | const frameElement = newElement;
|
1960 | const sourceRange = frameElement.ownerDocument?.createRange();
|
1961 | if (sourceRange) {
|
1962 | sourceRange.selectNodeContents(frameElement);
|
1963 | currentElement.appendChild(sourceRange.extractContents());
|
1964 | }
|
1965 | }
|
1966 |
|
1967 | constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1968 | super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
1969 | this.delegate = delegate;
|
1970 | }
|
1971 |
|
1972 | get shouldRender() {
|
1973 | return true
|
1974 | }
|
1975 |
|
1976 | async render() {
|
1977 | await nextRepaint();
|
1978 | this.preservingPermanentElements(() => {
|
1979 | this.loadFrameElement();
|
1980 | });
|
1981 | this.scrollFrameIntoView();
|
1982 | await nextRepaint();
|
1983 | this.focusFirstAutofocusableElement();
|
1984 | await nextRepaint();
|
1985 | this.activateScriptElements();
|
1986 | }
|
1987 |
|
1988 | loadFrameElement() {
|
1989 | this.delegate.willRenderFrame(this.currentElement, this.newElement);
|
1990 | this.renderElement(this.currentElement, this.newElement);
|
1991 | }
|
1992 |
|
1993 | scrollFrameIntoView() {
|
1994 | if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
1995 | const element = this.currentElement.firstElementChild;
|
1996 | const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
1997 | const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
|
1998 |
|
1999 | if (element) {
|
2000 | element.scrollIntoView({ block, behavior });
|
2001 | return true
|
2002 | }
|
2003 | }
|
2004 | return false
|
2005 | }
|
2006 |
|
2007 | activateScriptElements() {
|
2008 | for (const inertScriptElement of this.newScriptElements) {
|
2009 | const activatedScriptElement = activateScriptElement(inertScriptElement);
|
2010 | inertScriptElement.replaceWith(activatedScriptElement);
|
2011 | }
|
2012 | }
|
2013 |
|
2014 | get newScriptElements() {
|
2015 | return this.currentElement.querySelectorAll("script")
|
2016 | }
|
2017 | }
|
2018 |
|
2019 | function readScrollLogicalPosition(value, defaultValue) {
|
2020 | if (value == "end" || value == "start" || value == "center" || value == "nearest") {
|
2021 | return value
|
2022 | } else {
|
2023 | return defaultValue
|
2024 | }
|
2025 | }
|
2026 |
|
2027 | function readScrollBehavior(value, defaultValue) {
|
2028 | if (value == "auto" || value == "smooth") {
|
2029 | return value
|
2030 | } else {
|
2031 | return defaultValue
|
2032 | }
|
2033 | }
|
2034 |
|
2035 |
|
2036 | var Idiomorph = (function () {
|
2037 |
|
2038 |
|
2039 |
|
2040 |
|
2041 | let EMPTY_SET = new Set();
|
2042 |
|
2043 |
|
2044 | let defaults = {
|
2045 | morphStyle: "outerHTML",
|
2046 | callbacks : {
|
2047 | beforeNodeAdded: noOp,
|
2048 | afterNodeAdded: noOp,
|
2049 | beforeNodeMorphed: noOp,
|
2050 | afterNodeMorphed: noOp,
|
2051 | beforeNodeRemoved: noOp,
|
2052 | afterNodeRemoved: noOp,
|
2053 | beforeAttributeUpdated: noOp,
|
2054 |
|
2055 | },
|
2056 | head: {
|
2057 | style: 'merge',
|
2058 | shouldPreserve: function (elt) {
|
2059 | return elt.getAttribute("im-preserve") === "true";
|
2060 | },
|
2061 | shouldReAppend: function (elt) {
|
2062 | return elt.getAttribute("im-re-append") === "true";
|
2063 | },
|
2064 | shouldRemove: noOp,
|
2065 | afterHeadMorphed: noOp,
|
2066 | }
|
2067 | };
|
2068 |
|
2069 |
|
2070 |
|
2071 |
|
2072 | function morph(oldNode, newContent, config = {}) {
|
2073 |
|
2074 | if (oldNode instanceof Document) {
|
2075 | oldNode = oldNode.documentElement;
|
2076 | }
|
2077 |
|
2078 | if (typeof newContent === 'string') {
|
2079 | newContent = parseContent(newContent);
|
2080 | }
|
2081 |
|
2082 | let normalizedContent = normalizeContent(newContent);
|
2083 |
|
2084 | let ctx = createMorphContext(oldNode, normalizedContent, config);
|
2085 |
|
2086 | return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
2087 | }
|
2088 |
|
2089 | function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
|
2090 | if (ctx.head.block) {
|
2091 | let oldHead = oldNode.querySelector('head');
|
2092 | let newHead = normalizedNewContent.querySelector('head');
|
2093 | if (oldHead && newHead) {
|
2094 | let promises = handleHeadElement(newHead, oldHead, ctx);
|
2095 |
|
2096 | Promise.all(promises).then(function () {
|
2097 | morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
2098 | head: {
|
2099 | block: false,
|
2100 | ignore: true
|
2101 | }
|
2102 | }));
|
2103 | });
|
2104 | return;
|
2105 | }
|
2106 | }
|
2107 |
|
2108 | if (ctx.morphStyle === "innerHTML") {
|
2109 |
|
2110 |
|
2111 | morphChildren(normalizedNewContent, oldNode, ctx);
|
2112 | return oldNode.children;
|
2113 |
|
2114 | } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
2115 |
|
2116 |
|
2117 | let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
2118 |
|
2119 |
|
2120 | let previousSibling = bestMatch?.previousSibling;
|
2121 | let nextSibling = bestMatch?.nextSibling;
|
2122 |
|
2123 |
|
2124 | let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
2125 |
|
2126 | if (bestMatch) {
|
2127 |
|
2128 |
|
2129 | return insertSiblings(previousSibling, morphedNode, nextSibling);
|
2130 | } else {
|
2131 |
|
2132 | return []
|
2133 | }
|
2134 | } else {
|
2135 | throw "Do not understand how to morph style " + ctx.morphStyle;
|
2136 | }
|
2137 | }
|
2138 |
|
2139 |
|
2140 | |
2141 |
|
2142 |
|
2143 |
|
2144 |
|
2145 | function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
2146 | return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
2147 | }
|
2148 |
|
2149 | |
2150 |
|
2151 |
|
2152 |
|
2153 |
|
2154 |
|
2155 | function morphOldNodeTo(oldNode, newContent, ctx) {
|
2156 | if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
|
2157 | if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
2158 |
|
2159 | oldNode.remove();
|
2160 | ctx.callbacks.afterNodeRemoved(oldNode);
|
2161 | return null;
|
2162 | } else if (!isSoftMatch(oldNode, newContent)) {
|
2163 | if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
2164 | if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
|
2165 |
|
2166 | oldNode.parentElement.replaceChild(newContent, oldNode);
|
2167 | ctx.callbacks.afterNodeAdded(newContent);
|
2168 | ctx.callbacks.afterNodeRemoved(oldNode);
|
2169 | return newContent;
|
2170 | } else {
|
2171 | if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
|
2172 |
|
2173 | if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
2174 | handleHeadElement(newContent, oldNode, ctx);
|
2175 | } else {
|
2176 | syncNodeFrom(newContent, oldNode, ctx);
|
2177 | if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
2178 | morphChildren(newContent, oldNode, ctx);
|
2179 | }
|
2180 | }
|
2181 | ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
2182 | return oldNode;
|
2183 | }
|
2184 | }
|
2185 |
|
2186 | |
2187 |
|
2188 |
|
2189 |
|
2190 |
|
2191 |
|
2192 |
|
2193 |
|
2194 |
|
2195 |
|
2196 |
|
2197 |
|
2198 |
|
2199 |
|
2200 |
|
2201 |
|
2202 |
|
2203 |
|
2204 |
|
2205 |
|
2206 |
|
2207 |
|
2208 | function morphChildren(newParent, oldParent, ctx) {
|
2209 |
|
2210 | let nextNewChild = newParent.firstChild;
|
2211 | let insertionPoint = oldParent.firstChild;
|
2212 | let newChild;
|
2213 |
|
2214 |
|
2215 | while (nextNewChild) {
|
2216 |
|
2217 | newChild = nextNewChild;
|
2218 | nextNewChild = newChild.nextSibling;
|
2219 |
|
2220 |
|
2221 | if (insertionPoint == null) {
|
2222 | if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
2223 |
|
2224 | oldParent.appendChild(newChild);
|
2225 | ctx.callbacks.afterNodeAdded(newChild);
|
2226 | removeIdsFromConsideration(ctx, newChild);
|
2227 | continue;
|
2228 | }
|
2229 |
|
2230 |
|
2231 | if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
2232 | morphOldNodeTo(insertionPoint, newChild, ctx);
|
2233 | insertionPoint = insertionPoint.nextSibling;
|
2234 | removeIdsFromConsideration(ctx, newChild);
|
2235 | continue;
|
2236 | }
|
2237 |
|
2238 |
|
2239 | let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
2240 |
|
2241 |
|
2242 | if (idSetMatch) {
|
2243 | insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
2244 | morphOldNodeTo(idSetMatch, newChild, ctx);
|
2245 | removeIdsFromConsideration(ctx, newChild);
|
2246 | continue;
|
2247 | }
|
2248 |
|
2249 |
|
2250 | let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
2251 |
|
2252 |
|
2253 | if (softMatch) {
|
2254 | insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
2255 | morphOldNodeTo(softMatch, newChild, ctx);
|
2256 | removeIdsFromConsideration(ctx, newChild);
|
2257 | continue;
|
2258 | }
|
2259 |
|
2260 |
|
2261 |
|
2262 | if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
2263 |
|
2264 | oldParent.insertBefore(newChild, insertionPoint);
|
2265 | ctx.callbacks.afterNodeAdded(newChild);
|
2266 | removeIdsFromConsideration(ctx, newChild);
|
2267 | }
|
2268 |
|
2269 |
|
2270 | while (insertionPoint !== null) {
|
2271 |
|
2272 | let tempNode = insertionPoint;
|
2273 | insertionPoint = insertionPoint.nextSibling;
|
2274 | removeNode(tempNode, ctx);
|
2275 | }
|
2276 | }
|
2277 |
|
2278 |
|
2279 |
|
2280 |
|
2281 |
|
2282 | |
2283 |
|
2284 |
|
2285 |
|
2286 |
|
2287 |
|
2288 |
|
2289 | function ignoreAttribute(attr, to, updateType, ctx) {
|
2290 | if(attr === 'value' && ctx.ignoreActiveValue && to === document.activeElement){
|
2291 | return true;
|
2292 | }
|
2293 | return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
|
2294 | }
|
2295 |
|
2296 | |
2297 |
|
2298 |
|
2299 |
|
2300 |
|
2301 |
|
2302 |
|
2303 |
|
2304 | function syncNodeFrom(from, to, ctx) {
|
2305 | let type = from.nodeType;
|
2306 |
|
2307 |
|
2308 |
|
2309 | if (type === 1 ) {
|
2310 | const fromAttributes = from.attributes;
|
2311 | const toAttributes = to.attributes;
|
2312 | for (const fromAttribute of fromAttributes) {
|
2313 | if (ignoreAttribute(fromAttribute.name, to, 'update', ctx)) {
|
2314 | continue;
|
2315 | }
|
2316 | if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
|
2317 | to.setAttribute(fromAttribute.name, fromAttribute.value);
|
2318 | }
|
2319 | }
|
2320 |
|
2321 | for (let i = toAttributes.length - 1; 0 <= i; i--) {
|
2322 | const toAttribute = toAttributes[i];
|
2323 | if (ignoreAttribute(toAttribute.name, to, 'remove', ctx)) {
|
2324 | continue;
|
2325 | }
|
2326 | if (!from.hasAttribute(toAttribute.name)) {
|
2327 | to.removeAttribute(toAttribute.name);
|
2328 | }
|
2329 | }
|
2330 | }
|
2331 |
|
2332 |
|
2333 | if (type === 8 || type === 3 ) {
|
2334 | if (to.nodeValue !== from.nodeValue) {
|
2335 | to.nodeValue = from.nodeValue;
|
2336 | }
|
2337 | }
|
2338 |
|
2339 | if (!ignoreValueOfActiveElement(to, ctx)) {
|
2340 |
|
2341 | syncInputValue(from, to, ctx);
|
2342 | }
|
2343 | }
|
2344 |
|
2345 | |
2346 |
|
2347 |
|
2348 |
|
2349 |
|
2350 |
|
2351 | function syncBooleanAttribute(from, to, attributeName, ctx) {
|
2352 | if (from[attributeName] !== to[attributeName]) {
|
2353 | let ignoreUpdate = ignoreAttribute(attributeName, to, 'update', ctx);
|
2354 | if (!ignoreUpdate) {
|
2355 | to[attributeName] = from[attributeName];
|
2356 | }
|
2357 | if (from[attributeName]) {
|
2358 | if (!ignoreUpdate) {
|
2359 | to.setAttribute(attributeName, from[attributeName]);
|
2360 | }
|
2361 | } else {
|
2362 | if (!ignoreAttribute(attributeName, to, 'remove', ctx)) {
|
2363 | to.removeAttribute(attributeName);
|
2364 | }
|
2365 | }
|
2366 | }
|
2367 | }
|
2368 |
|
2369 | |
2370 |
|
2371 |
|
2372 |
|
2373 |
|
2374 |
|
2375 |
|
2376 |
|
2377 |
|
2378 |
|
2379 | function syncInputValue(from, to, ctx) {
|
2380 | if (from instanceof HTMLInputElement &&
|
2381 | to instanceof HTMLInputElement &&
|
2382 | from.type !== 'file') {
|
2383 |
|
2384 | let fromValue = from.value;
|
2385 | let toValue = to.value;
|
2386 |
|
2387 |
|
2388 | syncBooleanAttribute(from, to, 'checked', ctx);
|
2389 | syncBooleanAttribute(from, to, 'disabled', ctx);
|
2390 |
|
2391 | if (!from.hasAttribute('value')) {
|
2392 | if (!ignoreAttribute('value', to, 'remove', ctx)) {
|
2393 | to.value = '';
|
2394 | to.removeAttribute('value');
|
2395 | }
|
2396 | } else if (fromValue !== toValue) {
|
2397 | if (!ignoreAttribute('value', to, 'update', ctx)) {
|
2398 | to.setAttribute('value', fromValue);
|
2399 | to.value = fromValue;
|
2400 | }
|
2401 | }
|
2402 | } else if (from instanceof HTMLOptionElement) {
|
2403 | syncBooleanAttribute(from, to, 'selected', ctx);
|
2404 | } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
2405 | let fromValue = from.value;
|
2406 | let toValue = to.value;
|
2407 | if (ignoreAttribute('value', to, 'update', ctx)) {
|
2408 | return;
|
2409 | }
|
2410 | if (fromValue !== toValue) {
|
2411 | to.value = fromValue;
|
2412 | }
|
2413 | if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
2414 | to.firstChild.nodeValue = fromValue;
|
2415 | }
|
2416 | }
|
2417 | }
|
2418 |
|
2419 |
|
2420 |
|
2421 |
|
2422 | function handleHeadElement(newHeadTag, currentHead, ctx) {
|
2423 |
|
2424 | let added = [];
|
2425 | let removed = [];
|
2426 | let preserved = [];
|
2427 | let nodesToAppend = [];
|
2428 |
|
2429 | let headMergeStyle = ctx.head.style;
|
2430 |
|
2431 |
|
2432 | let srcToNewHeadNodes = new Map();
|
2433 | for (const newHeadChild of newHeadTag.children) {
|
2434 | srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
2435 | }
|
2436 |
|
2437 |
|
2438 | for (const currentHeadElt of currentHead.children) {
|
2439 |
|
2440 |
|
2441 | let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
2442 | let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
2443 | let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
2444 | if (inNewContent || isPreserved) {
|
2445 | if (isReAppended) {
|
2446 |
|
2447 | removed.push(currentHeadElt);
|
2448 | } else {
|
2449 |
|
2450 |
|
2451 | srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
2452 | preserved.push(currentHeadElt);
|
2453 | }
|
2454 | } else {
|
2455 | if (headMergeStyle === "append") {
|
2456 |
|
2457 |
|
2458 | if (isReAppended) {
|
2459 | removed.push(currentHeadElt);
|
2460 | nodesToAppend.push(currentHeadElt);
|
2461 | }
|
2462 | } else {
|
2463 |
|
2464 | if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
2465 | removed.push(currentHeadElt);
|
2466 | }
|
2467 | }
|
2468 | }
|
2469 | }
|
2470 |
|
2471 |
|
2472 |
|
2473 | nodesToAppend.push(...srcToNewHeadNodes.values());
|
2474 |
|
2475 | let promises = [];
|
2476 | for (const newNode of nodesToAppend) {
|
2477 | let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
2478 | if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
2479 | if (newElt.href || newElt.src) {
|
2480 | let resolve = null;
|
2481 | let promise = new Promise(function (_resolve) {
|
2482 | resolve = _resolve;
|
2483 | });
|
2484 | newElt.addEventListener('load', function () {
|
2485 | resolve();
|
2486 | });
|
2487 | promises.push(promise);
|
2488 | }
|
2489 | currentHead.appendChild(newElt);
|
2490 | ctx.callbacks.afterNodeAdded(newElt);
|
2491 | added.push(newElt);
|
2492 | }
|
2493 | }
|
2494 |
|
2495 |
|
2496 |
|
2497 | for (const removedElement of removed) {
|
2498 | if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
2499 | currentHead.removeChild(removedElement);
|
2500 | ctx.callbacks.afterNodeRemoved(removedElement);
|
2501 | }
|
2502 | }
|
2503 |
|
2504 | ctx.head.afterHeadMorphed(currentHead, {added: added, kept: preserved, removed: removed});
|
2505 | return promises;
|
2506 | }
|
2507 |
|
2508 | function noOp() {
|
2509 | }
|
2510 |
|
2511 | |
2512 |
|
2513 |
|
2514 |
|
2515 | function mergeDefaults(config) {
|
2516 | let finalConfig = {};
|
2517 |
|
2518 | Object.assign(finalConfig, defaults);
|
2519 | Object.assign(finalConfig, config);
|
2520 |
|
2521 |
|
2522 | finalConfig.callbacks = {};
|
2523 | Object.assign(finalConfig.callbacks, defaults.callbacks);
|
2524 | Object.assign(finalConfig.callbacks, config.callbacks);
|
2525 |
|
2526 |
|
2527 | finalConfig.head = {};
|
2528 | Object.assign(finalConfig.head, defaults.head);
|
2529 | Object.assign(finalConfig.head, config.head);
|
2530 | return finalConfig;
|
2531 | }
|
2532 |
|
2533 | function createMorphContext(oldNode, newContent, config) {
|
2534 | config = mergeDefaults(config);
|
2535 | return {
|
2536 | target: oldNode,
|
2537 | newContent: newContent,
|
2538 | config: config,
|
2539 | morphStyle: config.morphStyle,
|
2540 | ignoreActive: config.ignoreActive,
|
2541 | ignoreActiveValue: config.ignoreActiveValue,
|
2542 | idMap: createIdMap(oldNode, newContent),
|
2543 | deadIds: new Set(),
|
2544 | callbacks: config.callbacks,
|
2545 | head: config.head
|
2546 | }
|
2547 | }
|
2548 |
|
2549 | function isIdSetMatch(node1, node2, ctx) {
|
2550 | if (node1 == null || node2 == null) {
|
2551 | return false;
|
2552 | }
|
2553 | if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
|
2554 | if (node1.id !== "" && node1.id === node2.id) {
|
2555 | return true;
|
2556 | } else {
|
2557 | return getIdIntersectionCount(ctx, node1, node2) > 0;
|
2558 | }
|
2559 | }
|
2560 | return false;
|
2561 | }
|
2562 |
|
2563 | function isSoftMatch(node1, node2) {
|
2564 | if (node1 == null || node2 == null) {
|
2565 | return false;
|
2566 | }
|
2567 | return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName
|
2568 | }
|
2569 |
|
2570 | function removeNodesBetween(startInclusive, endExclusive, ctx) {
|
2571 | while (startInclusive !== endExclusive) {
|
2572 | let tempNode = startInclusive;
|
2573 | startInclusive = startInclusive.nextSibling;
|
2574 | removeNode(tempNode, ctx);
|
2575 | }
|
2576 | removeIdsFromConsideration(ctx, endExclusive);
|
2577 | return endExclusive.nextSibling;
|
2578 | }
|
2579 |
|
2580 |
|
2581 |
|
2582 |
|
2583 |
|
2584 |
|
2585 |
|
2586 | function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
2587 |
|
2588 |
|
2589 | let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
|
2590 |
|
2591 | let potentialMatch = null;
|
2592 |
|
2593 |
|
2594 | if (newChildPotentialIdCount > 0) {
|
2595 | let potentialMatch = insertionPoint;
|
2596 |
|
2597 |
|
2598 |
|
2599 |
|
2600 | let otherMatchCount = 0;
|
2601 | while (potentialMatch != null) {
|
2602 |
|
2603 |
|
2604 | if (isIdSetMatch(newChild, potentialMatch, ctx)) {
|
2605 | return potentialMatch;
|
2606 | }
|
2607 |
|
2608 |
|
2609 | otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
|
2610 | if (otherMatchCount > newChildPotentialIdCount) {
|
2611 |
|
2612 |
|
2613 | return null;
|
2614 | }
|
2615 |
|
2616 |
|
2617 | potentialMatch = potentialMatch.nextSibling;
|
2618 | }
|
2619 | }
|
2620 | return potentialMatch;
|
2621 | }
|
2622 |
|
2623 |
|
2624 |
|
2625 |
|
2626 |
|
2627 |
|
2628 |
|
2629 | function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
2630 |
|
2631 | let potentialSoftMatch = insertionPoint;
|
2632 | let nextSibling = newChild.nextSibling;
|
2633 | let siblingSoftMatchCount = 0;
|
2634 |
|
2635 | while (potentialSoftMatch != null) {
|
2636 |
|
2637 | if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
2638 |
|
2639 |
|
2640 | return null;
|
2641 | }
|
2642 |
|
2643 |
|
2644 | if (isSoftMatch(newChild, potentialSoftMatch)) {
|
2645 | return potentialSoftMatch;
|
2646 | }
|
2647 |
|
2648 | if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
2649 |
|
2650 |
|
2651 | siblingSoftMatchCount++;
|
2652 | nextSibling = nextSibling.nextSibling;
|
2653 |
|
2654 |
|
2655 |
|
2656 | if (siblingSoftMatchCount >= 2) {
|
2657 | return null;
|
2658 | }
|
2659 | }
|
2660 |
|
2661 |
|
2662 | potentialSoftMatch = potentialSoftMatch.nextSibling;
|
2663 | }
|
2664 |
|
2665 | return potentialSoftMatch;
|
2666 | }
|
2667 |
|
2668 | function parseContent(newContent) {
|
2669 | let parser = new DOMParser();
|
2670 |
|
2671 |
|
2672 | let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
2673 |
|
2674 |
|
2675 | if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
2676 | let content = parser.parseFromString(newContent, "text/html");
|
2677 |
|
2678 | if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
2679 | content.generatedByIdiomorph = true;
|
2680 | return content;
|
2681 | } else {
|
2682 |
|
2683 | let htmlElement = content.firstChild;
|
2684 | if (htmlElement) {
|
2685 | htmlElement.generatedByIdiomorph = true;
|
2686 | return htmlElement;
|
2687 | } else {
|
2688 | return null;
|
2689 | }
|
2690 | }
|
2691 | } else {
|
2692 |
|
2693 |
|
2694 | let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
2695 | let content = responseDoc.body.querySelector('template').content;
|
2696 | content.generatedByIdiomorph = true;
|
2697 | return content
|
2698 | }
|
2699 | }
|
2700 |
|
2701 | function normalizeContent(newContent) {
|
2702 | if (newContent == null) {
|
2703 |
|
2704 | const dummyParent = document.createElement('div');
|
2705 | return dummyParent;
|
2706 | } else if (newContent.generatedByIdiomorph) {
|
2707 |
|
2708 | return newContent;
|
2709 | } else if (newContent instanceof Node) {
|
2710 |
|
2711 | const dummyParent = document.createElement('div');
|
2712 | dummyParent.append(newContent);
|
2713 | return dummyParent;
|
2714 | } else {
|
2715 |
|
2716 |
|
2717 | const dummyParent = document.createElement('div');
|
2718 | for (const elt of [...newContent]) {
|
2719 | dummyParent.append(elt);
|
2720 | }
|
2721 | return dummyParent;
|
2722 | }
|
2723 | }
|
2724 |
|
2725 | function insertSiblings(previousSibling, morphedNode, nextSibling) {
|
2726 | let stack = [];
|
2727 | let added = [];
|
2728 | while (previousSibling != null) {
|
2729 | stack.push(previousSibling);
|
2730 | previousSibling = previousSibling.previousSibling;
|
2731 | }
|
2732 | while (stack.length > 0) {
|
2733 | let node = stack.pop();
|
2734 | added.push(node);
|
2735 | morphedNode.parentElement.insertBefore(node, morphedNode);
|
2736 | }
|
2737 | added.push(morphedNode);
|
2738 | while (nextSibling != null) {
|
2739 | stack.push(nextSibling);
|
2740 | added.push(nextSibling);
|
2741 | nextSibling = nextSibling.nextSibling;
|
2742 | }
|
2743 | while (stack.length > 0) {
|
2744 | morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
|
2745 | }
|
2746 | return added;
|
2747 | }
|
2748 |
|
2749 | function findBestNodeMatch(newContent, oldNode, ctx) {
|
2750 | let currentElement;
|
2751 | currentElement = newContent.firstChild;
|
2752 | let bestElement = currentElement;
|
2753 | let score = 0;
|
2754 | while (currentElement) {
|
2755 | let newScore = scoreElement(currentElement, oldNode, ctx);
|
2756 | if (newScore > score) {
|
2757 | bestElement = currentElement;
|
2758 | score = newScore;
|
2759 | }
|
2760 | currentElement = currentElement.nextSibling;
|
2761 | }
|
2762 | return bestElement;
|
2763 | }
|
2764 |
|
2765 | function scoreElement(node1, node2, ctx) {
|
2766 | if (isSoftMatch(node1, node2)) {
|
2767 | return .5 + getIdIntersectionCount(ctx, node1, node2);
|
2768 | }
|
2769 | return 0;
|
2770 | }
|
2771 |
|
2772 | function removeNode(tempNode, ctx) {
|
2773 | removeIdsFromConsideration(ctx, tempNode);
|
2774 | if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
|
2775 |
|
2776 | tempNode.remove();
|
2777 | ctx.callbacks.afterNodeRemoved(tempNode);
|
2778 | }
|
2779 |
|
2780 |
|
2781 |
|
2782 |
|
2783 |
|
2784 | function isIdInConsideration(ctx, id) {
|
2785 | return !ctx.deadIds.has(id);
|
2786 | }
|
2787 |
|
2788 | function idIsWithinNode(ctx, id, targetNode) {
|
2789 | let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
|
2790 | return idSet.has(id);
|
2791 | }
|
2792 |
|
2793 | function removeIdsFromConsideration(ctx, node) {
|
2794 | let idSet = ctx.idMap.get(node) || EMPTY_SET;
|
2795 | for (const id of idSet) {
|
2796 | ctx.deadIds.add(id);
|
2797 | }
|
2798 | }
|
2799 |
|
2800 | function getIdIntersectionCount(ctx, node1, node2) {
|
2801 | let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
|
2802 | let matchCount = 0;
|
2803 | for (const id of sourceSet) {
|
2804 |
|
2805 |
|
2806 | if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
|
2807 | ++matchCount;
|
2808 | }
|
2809 | }
|
2810 | return matchCount;
|
2811 | }
|
2812 |
|
2813 | |
2814 |
|
2815 |
|
2816 |
|
2817 |
|
2818 |
|
2819 |
|
2820 |
|
2821 | function populateIdMapForNode(node, idMap) {
|
2822 | let nodeParent = node.parentElement;
|
2823 |
|
2824 | let idElements = node.querySelectorAll('[id]');
|
2825 | for (const elt of idElements) {
|
2826 | let current = elt;
|
2827 |
|
2828 |
|
2829 | while (current !== nodeParent && current != null) {
|
2830 | let idSet = idMap.get(current);
|
2831 |
|
2832 | if (idSet == null) {
|
2833 | idSet = new Set();
|
2834 | idMap.set(current, idSet);
|
2835 | }
|
2836 | idSet.add(elt.id);
|
2837 | current = current.parentElement;
|
2838 | }
|
2839 | }
|
2840 | }
|
2841 |
|
2842 | |
2843 |
|
2844 |
|
2845 |
|
2846 |
|
2847 |
|
2848 |
|
2849 |
|
2850 |
|
2851 |
|
2852 | function createIdMap(oldContent, newContent) {
|
2853 | let idMap = new Map();
|
2854 | populateIdMapForNode(oldContent, idMap);
|
2855 | populateIdMapForNode(newContent, idMap);
|
2856 | return idMap;
|
2857 | }
|
2858 |
|
2859 |
|
2860 |
|
2861 |
|
2862 | return {
|
2863 | morph,
|
2864 | defaults
|
2865 | }
|
2866 | })();
|
2867 |
|
2868 | function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
|
2869 | Idiomorph.morph(currentElement, newElement, {
|
2870 | ...options,
|
2871 | callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
2872 | });
|
2873 | }
|
2874 |
|
2875 | function morphChildren(currentElement, newElement) {
|
2876 | morphElements(currentElement, newElement.children, {
|
2877 | morphStyle: "innerHTML"
|
2878 | });
|
2879 | }
|
2880 |
|
2881 | class DefaultIdiomorphCallbacks {
|
2882 | #beforeNodeMorphed
|
2883 |
|
2884 | constructor({ beforeNodeMorphed } = {}) {
|
2885 | this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
2886 | }
|
2887 |
|
2888 | beforeNodeAdded = (node) => {
|
2889 | return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
2890 | }
|
2891 |
|
2892 | beforeNodeMorphed = (currentElement, newElement) => {
|
2893 | if (currentElement instanceof Element) {
|
2894 | if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
2895 | const event = dispatch("turbo:before-morph-element", {
|
2896 | cancelable: true,
|
2897 | target: currentElement,
|
2898 | detail: { currentElement, newElement }
|
2899 | });
|
2900 |
|
2901 | return !event.defaultPrevented
|
2902 | } else {
|
2903 | return false
|
2904 | }
|
2905 | }
|
2906 | }
|
2907 |
|
2908 | beforeAttributeUpdated = (attributeName, target, mutationType) => {
|
2909 | const event = dispatch("turbo:before-morph-attribute", {
|
2910 | cancelable: true,
|
2911 | target,
|
2912 | detail: { attributeName, mutationType }
|
2913 | });
|
2914 |
|
2915 | return !event.defaultPrevented
|
2916 | }
|
2917 |
|
2918 | beforeNodeRemoved = (node) => {
|
2919 | return this.beforeNodeMorphed(node)
|
2920 | }
|
2921 |
|
2922 | afterNodeMorphed = (currentElement, newElement) => {
|
2923 | if (currentElement instanceof Element) {
|
2924 | dispatch("turbo:morph-element", {
|
2925 | target: currentElement,
|
2926 | detail: { currentElement, newElement }
|
2927 | });
|
2928 | }
|
2929 | }
|
2930 | }
|
2931 |
|
2932 | class MorphingFrameRenderer extends FrameRenderer {
|
2933 | static renderElement(currentElement, newElement) {
|
2934 | dispatch("turbo:before-frame-morph", {
|
2935 | target: currentElement,
|
2936 | detail: { currentElement, newElement }
|
2937 | });
|
2938 |
|
2939 | morphChildren(currentElement, newElement);
|
2940 | }
|
2941 |
|
2942 | async preservingPermanentElements(callback) {
|
2943 | return await callback()
|
2944 | }
|
2945 | }
|
2946 |
|
2947 | class ProgressBar {
|
2948 | static animationDuration = 300
|
2949 |
|
2950 | static get defaultCSS() {
|
2951 | return unindent`
|
2952 | .turbo-progress-bar {
|
2953 | position: fixed;
|
2954 | display: block;
|
2955 | top: 0;
|
2956 | left: 0;
|
2957 | height: 3px;
|
2958 | background: #0076ff;
|
2959 | z-index: 2147483647;
|
2960 | transition:
|
2961 | width ${ProgressBar.animationDuration}ms ease-out,
|
2962 | opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
2963 | transform: translate3d(0, 0, 0);
|
2964 | }
|
2965 | `
|
2966 | }
|
2967 |
|
2968 | hiding = false
|
2969 | value = 0
|
2970 | visible = false
|
2971 |
|
2972 | constructor() {
|
2973 | this.stylesheetElement = this.createStylesheetElement();
|
2974 | this.progressElement = this.createProgressElement();
|
2975 | this.installStylesheetElement();
|
2976 | this.setValue(0);
|
2977 | }
|
2978 |
|
2979 | show() {
|
2980 | if (!this.visible) {
|
2981 | this.visible = true;
|
2982 | this.installProgressElement();
|
2983 | this.startTrickling();
|
2984 | }
|
2985 | }
|
2986 |
|
2987 | hide() {
|
2988 | if (this.visible && !this.hiding) {
|
2989 | this.hiding = true;
|
2990 | this.fadeProgressElement(() => {
|
2991 | this.uninstallProgressElement();
|
2992 | this.stopTrickling();
|
2993 | this.visible = false;
|
2994 | this.hiding = false;
|
2995 | });
|
2996 | }
|
2997 | }
|
2998 |
|
2999 | setValue(value) {
|
3000 | this.value = value;
|
3001 | this.refresh();
|
3002 | }
|
3003 |
|
3004 |
|
3005 |
|
3006 | installStylesheetElement() {
|
3007 | document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
3008 | }
|
3009 |
|
3010 | installProgressElement() {
|
3011 | this.progressElement.style.width = "0";
|
3012 | this.progressElement.style.opacity = "1";
|
3013 | document.documentElement.insertBefore(this.progressElement, document.body);
|
3014 | this.refresh();
|
3015 | }
|
3016 |
|
3017 | fadeProgressElement(callback) {
|
3018 | this.progressElement.style.opacity = "0";
|
3019 | setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
3020 | }
|
3021 |
|
3022 | uninstallProgressElement() {
|
3023 | if (this.progressElement.parentNode) {
|
3024 | document.documentElement.removeChild(this.progressElement);
|
3025 | }
|
3026 | }
|
3027 |
|
3028 | startTrickling() {
|
3029 | if (!this.trickleInterval) {
|
3030 | this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
3031 | }
|
3032 | }
|
3033 |
|
3034 | stopTrickling() {
|
3035 | window.clearInterval(this.trickleInterval);
|
3036 | delete this.trickleInterval;
|
3037 | }
|
3038 |
|
3039 | trickle = () => {
|
3040 | this.setValue(this.value + Math.random() / 100);
|
3041 | }
|
3042 |
|
3043 | refresh() {
|
3044 | requestAnimationFrame(() => {
|
3045 | this.progressElement.style.width = `${10 + this.value * 90}%`;
|
3046 | });
|
3047 | }
|
3048 |
|
3049 | createStylesheetElement() {
|
3050 | const element = document.createElement("style");
|
3051 | element.type = "text/css";
|
3052 | element.textContent = ProgressBar.defaultCSS;
|
3053 | const cspNonce = getCspNonce();
|
3054 | if (cspNonce) {
|
3055 | element.nonce = cspNonce;
|
3056 | }
|
3057 | return element
|
3058 | }
|
3059 |
|
3060 | createProgressElement() {
|
3061 | const element = document.createElement("div");
|
3062 | element.className = "turbo-progress-bar";
|
3063 | return element
|
3064 | }
|
3065 | }
|
3066 |
|
3067 | class HeadSnapshot extends Snapshot {
|
3068 | detailsByOuterHTML = this.children
|
3069 | .filter((element) => !elementIsNoscript(element))
|
3070 | .map((element) => elementWithoutNonce(element))
|
3071 | .reduce((result, element) => {
|
3072 | const { outerHTML } = element;
|
3073 | const details =
|
3074 | outerHTML in result
|
3075 | ? result[outerHTML]
|
3076 | : {
|
3077 | type: elementType(element),
|
3078 | tracked: elementIsTracked(element),
|
3079 | elements: []
|
3080 | };
|
3081 | return {
|
3082 | ...result,
|
3083 | [outerHTML]: {
|
3084 | ...details,
|
3085 | elements: [...details.elements, element]
|
3086 | }
|
3087 | }
|
3088 | }, {})
|
3089 |
|
3090 | get trackedElementSignature() {
|
3091 | return Object.keys(this.detailsByOuterHTML)
|
3092 | .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
|
3093 | .join("")
|
3094 | }
|
3095 |
|
3096 | getScriptElementsNotInSnapshot(snapshot) {
|
3097 | return this.getElementsMatchingTypeNotInSnapshot("script", snapshot)
|
3098 | }
|
3099 |
|
3100 | getStylesheetElementsNotInSnapshot(snapshot) {
|
3101 | return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot)
|
3102 | }
|
3103 |
|
3104 | getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
3105 | return Object.keys(this.detailsByOuterHTML)
|
3106 | .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
|
3107 | .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
|
3108 | .filter(({ type }) => type == matchedType)
|
3109 | .map(({ elements: [element] }) => element)
|
3110 | }
|
3111 |
|
3112 | get provisionalElements() {
|
3113 | return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
|
3114 | const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];
|
3115 | if (type == null && !tracked) {
|
3116 | return [...result, ...elements]
|
3117 | } else if (elements.length > 1) {
|
3118 | return [...result, ...elements.slice(1)]
|
3119 | } else {
|
3120 | return result
|
3121 | }
|
3122 | }, [])
|
3123 | }
|
3124 |
|
3125 | getMetaValue(name) {
|
3126 | const element = this.findMetaElementByName(name);
|
3127 | return element ? element.getAttribute("content") : null
|
3128 | }
|
3129 |
|
3130 | findMetaElementByName(name) {
|
3131 | return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
|
3132 | const {
|
3133 | elements: [element]
|
3134 | } = this.detailsByOuterHTML[outerHTML];
|
3135 | return elementIsMetaElementWithName(element, name) ? element : result
|
3136 | }, undefined | undefined)
|
3137 | }
|
3138 | }
|
3139 |
|
3140 | function elementType(element) {
|
3141 | if (elementIsScript(element)) {
|
3142 | return "script"
|
3143 | } else if (elementIsStylesheet(element)) {
|
3144 | return "stylesheet"
|
3145 | }
|
3146 | }
|
3147 |
|
3148 | function elementIsTracked(element) {
|
3149 | return element.getAttribute("data-turbo-track") == "reload"
|
3150 | }
|
3151 |
|
3152 | function elementIsScript(element) {
|
3153 | const tagName = element.localName;
|
3154 | return tagName == "script"
|
3155 | }
|
3156 |
|
3157 | function elementIsNoscript(element) {
|
3158 | const tagName = element.localName;
|
3159 | return tagName == "noscript"
|
3160 | }
|
3161 |
|
3162 | function elementIsStylesheet(element) {
|
3163 | const tagName = element.localName;
|
3164 | return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet")
|
3165 | }
|
3166 |
|
3167 | function elementIsMetaElementWithName(element, name) {
|
3168 | const tagName = element.localName;
|
3169 | return tagName == "meta" && element.getAttribute("name") == name
|
3170 | }
|
3171 |
|
3172 | function elementWithoutNonce(element) {
|
3173 | if (element.hasAttribute("nonce")) {
|
3174 | element.setAttribute("nonce", "");
|
3175 | }
|
3176 |
|
3177 | return element
|
3178 | }
|
3179 |
|
3180 | class PageSnapshot extends Snapshot {
|
3181 | static fromHTMLString(html = "") {
|
3182 | return this.fromDocument(parseHTMLDocument(html))
|
3183 | }
|
3184 |
|
3185 | static fromElement(element) {
|
3186 | return this.fromDocument(element.ownerDocument)
|
3187 | }
|
3188 |
|
3189 | static fromDocument({ documentElement, body, head }) {
|
3190 | return new this(documentElement, body, new HeadSnapshot(head))
|
3191 | }
|
3192 |
|
3193 | constructor(documentElement, body, headSnapshot) {
|
3194 | super(body);
|
3195 | this.documentElement = documentElement;
|
3196 | this.headSnapshot = headSnapshot;
|
3197 | }
|
3198 |
|
3199 | clone() {
|
3200 | const clonedElement = this.element.cloneNode(true);
|
3201 |
|
3202 | const selectElements = this.element.querySelectorAll("select");
|
3203 | const clonedSelectElements = clonedElement.querySelectorAll("select");
|
3204 |
|
3205 | for (const [index, source] of selectElements.entries()) {
|
3206 | const clone = clonedSelectElements[index];
|
3207 | for (const option of clone.selectedOptions) option.selected = false;
|
3208 | for (const option of source.selectedOptions) clone.options[option.index].selected = true;
|
3209 | }
|
3210 |
|
3211 | for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
3212 | clonedPasswordInput.value = "";
|
3213 | }
|
3214 |
|
3215 | return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot)
|
3216 | }
|
3217 |
|
3218 | get lang() {
|
3219 | return this.documentElement.getAttribute("lang")
|
3220 | }
|
3221 |
|
3222 | get headElement() {
|
3223 | return this.headSnapshot.element
|
3224 | }
|
3225 |
|
3226 | get rootLocation() {
|
3227 | const root = this.getSetting("root") ?? "/";
|
3228 | return expandURL(root)
|
3229 | }
|
3230 |
|
3231 | get cacheControlValue() {
|
3232 | return this.getSetting("cache-control")
|
3233 | }
|
3234 |
|
3235 | get isPreviewable() {
|
3236 | return this.cacheControlValue != "no-preview"
|
3237 | }
|
3238 |
|
3239 | get isCacheable() {
|
3240 | return this.cacheControlValue != "no-cache"
|
3241 | }
|
3242 |
|
3243 | get isVisitable() {
|
3244 | return this.getSetting("visit-control") != "reload"
|
3245 | }
|
3246 |
|
3247 | get prefersViewTransitions() {
|
3248 | return this.headSnapshot.getMetaValue("view-transition") === "same-origin"
|
3249 | }
|
3250 |
|
3251 | get shouldMorphPage() {
|
3252 | return this.getSetting("refresh-method") === "morph"
|
3253 | }
|
3254 |
|
3255 | get shouldPreserveScrollPosition() {
|
3256 | return this.getSetting("refresh-scroll") === "preserve"
|
3257 | }
|
3258 |
|
3259 |
|
3260 |
|
3261 | getSetting(name) {
|
3262 | return this.headSnapshot.getMetaValue(`turbo-${name}`)
|
3263 | }
|
3264 | }
|
3265 |
|
3266 | class ViewTransitioner {
|
3267 | #viewTransitionStarted = false
|
3268 | #lastOperation = Promise.resolve()
|
3269 |
|
3270 | renderChange(useViewTransition, render) {
|
3271 | if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
|
3272 | this.#viewTransitionStarted = true;
|
3273 | this.#lastOperation = this.#lastOperation.then(async () => {
|
3274 | await document.startViewTransition(render).finished;
|
3275 | });
|
3276 | } else {
|
3277 | this.#lastOperation = this.#lastOperation.then(render);
|
3278 | }
|
3279 |
|
3280 | return this.#lastOperation
|
3281 | }
|
3282 |
|
3283 | get viewTransitionsAvailable() {
|
3284 | return document.startViewTransition
|
3285 | }
|
3286 | }
|
3287 |
|
3288 | const defaultOptions = {
|
3289 | action: "advance",
|
3290 | historyChanged: false,
|
3291 | visitCachedSnapshot: () => {},
|
3292 | willRender: true,
|
3293 | updateHistory: true,
|
3294 | shouldCacheSnapshot: true,
|
3295 | acceptsStreamResponse: false
|
3296 | };
|
3297 |
|
3298 | const TimingMetric = {
|
3299 | visitStart: "visitStart",
|
3300 | requestStart: "requestStart",
|
3301 | requestEnd: "requestEnd",
|
3302 | visitEnd: "visitEnd"
|
3303 | };
|
3304 |
|
3305 | const VisitState = {
|
3306 | initialized: "initialized",
|
3307 | started: "started",
|
3308 | canceled: "canceled",
|
3309 | failed: "failed",
|
3310 | completed: "completed"
|
3311 | };
|
3312 |
|
3313 | const SystemStatusCode = {
|
3314 | networkFailure: 0,
|
3315 | timeoutFailure: -1,
|
3316 | contentTypeMismatch: -2
|
3317 | };
|
3318 |
|
3319 | const Direction = {
|
3320 | advance: "forward",
|
3321 | restore: "back",
|
3322 | replace: "none"
|
3323 | };
|
3324 |
|
3325 | class Visit {
|
3326 | identifier = uuid()
|
3327 | timingMetrics = {}
|
3328 |
|
3329 | followedRedirect = false
|
3330 | historyChanged = false
|
3331 | scrolled = false
|
3332 | shouldCacheSnapshot = true
|
3333 | acceptsStreamResponse = false
|
3334 | snapshotCached = false
|
3335 | state = VisitState.initialized
|
3336 | viewTransitioner = new ViewTransitioner()
|
3337 |
|
3338 | constructor(delegate, location, restorationIdentifier, options = {}) {
|
3339 | this.delegate = delegate;
|
3340 | this.location = location;
|
3341 | this.restorationIdentifier = restorationIdentifier || uuid();
|
3342 |
|
3343 | const {
|
3344 | action,
|
3345 | historyChanged,
|
3346 | referrer,
|
3347 | snapshot,
|
3348 | snapshotHTML,
|
3349 | response,
|
3350 | visitCachedSnapshot,
|
3351 | willRender,
|
3352 | updateHistory,
|
3353 | shouldCacheSnapshot,
|
3354 | acceptsStreamResponse,
|
3355 | direction
|
3356 | } = {
|
3357 | ...defaultOptions,
|
3358 | ...options
|
3359 | };
|
3360 | this.action = action;
|
3361 | this.historyChanged = historyChanged;
|
3362 | this.referrer = referrer;
|
3363 | this.snapshot = snapshot;
|
3364 | this.snapshotHTML = snapshotHTML;
|
3365 | this.response = response;
|
3366 | this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
3367 | this.isPageRefresh = this.view.isPageRefresh(this);
|
3368 | this.visitCachedSnapshot = visitCachedSnapshot;
|
3369 | this.willRender = willRender;
|
3370 | this.updateHistory = updateHistory;
|
3371 | this.scrolled = !willRender;
|
3372 | this.shouldCacheSnapshot = shouldCacheSnapshot;
|
3373 | this.acceptsStreamResponse = acceptsStreamResponse;
|
3374 | this.direction = direction || Direction[action];
|
3375 | }
|
3376 |
|
3377 | get adapter() {
|
3378 | return this.delegate.adapter
|
3379 | }
|
3380 |
|
3381 | get view() {
|
3382 | return this.delegate.view
|
3383 | }
|
3384 |
|
3385 | get history() {
|
3386 | return this.delegate.history
|
3387 | }
|
3388 |
|
3389 | get restorationData() {
|
3390 | return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)
|
3391 | }
|
3392 |
|
3393 | get silent() {
|
3394 | return this.isSamePage
|
3395 | }
|
3396 |
|
3397 | start() {
|
3398 | if (this.state == VisitState.initialized) {
|
3399 | this.recordTimingMetric(TimingMetric.visitStart);
|
3400 | this.state = VisitState.started;
|
3401 | this.adapter.visitStarted(this);
|
3402 | this.delegate.visitStarted(this);
|
3403 | }
|
3404 | }
|
3405 |
|
3406 | cancel() {
|
3407 | if (this.state == VisitState.started) {
|
3408 | if (this.request) {
|
3409 | this.request.cancel();
|
3410 | }
|
3411 | this.cancelRender();
|
3412 | this.state = VisitState.canceled;
|
3413 | }
|
3414 | }
|
3415 |
|
3416 | complete() {
|
3417 | if (this.state == VisitState.started) {
|
3418 | this.recordTimingMetric(TimingMetric.visitEnd);
|
3419 | this.adapter.visitCompleted(this);
|
3420 | this.state = VisitState.completed;
|
3421 | this.followRedirect();
|
3422 |
|
3423 | if (!this.followedRedirect) {
|
3424 | this.delegate.visitCompleted(this);
|
3425 | }
|
3426 | }
|
3427 | }
|
3428 |
|
3429 | fail() {
|
3430 | if (this.state == VisitState.started) {
|
3431 | this.state = VisitState.failed;
|
3432 | this.adapter.visitFailed(this);
|
3433 | this.delegate.visitCompleted(this);
|
3434 | }
|
3435 | }
|
3436 |
|
3437 | changeHistory() {
|
3438 | if (!this.historyChanged && this.updateHistory) {
|
3439 | const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
|
3440 | const method = getHistoryMethodForAction(actionForHistory);
|
3441 | this.history.update(method, this.location, this.restorationIdentifier);
|
3442 | this.historyChanged = true;
|
3443 | }
|
3444 | }
|
3445 |
|
3446 | issueRequest() {
|
3447 | if (this.hasPreloadedResponse()) {
|
3448 | this.simulateRequest();
|
3449 | } else if (this.shouldIssueRequest() && !this.request) {
|
3450 | this.request = new FetchRequest(this, FetchMethod.get, this.location);
|
3451 | this.request.perform();
|
3452 | }
|
3453 | }
|
3454 |
|
3455 | simulateRequest() {
|
3456 | if (this.response) {
|
3457 | this.startRequest();
|
3458 | this.recordResponse();
|
3459 | this.finishRequest();
|
3460 | }
|
3461 | }
|
3462 |
|
3463 | startRequest() {
|
3464 | this.recordTimingMetric(TimingMetric.requestStart);
|
3465 | this.adapter.visitRequestStarted(this);
|
3466 | }
|
3467 |
|
3468 | recordResponse(response = this.response) {
|
3469 | this.response = response;
|
3470 | if (response) {
|
3471 | const { statusCode } = response;
|
3472 | if (isSuccessful(statusCode)) {
|
3473 | this.adapter.visitRequestCompleted(this);
|
3474 | } else {
|
3475 | this.adapter.visitRequestFailedWithStatusCode(this, statusCode);
|
3476 | }
|
3477 | }
|
3478 | }
|
3479 |
|
3480 | finishRequest() {
|
3481 | this.recordTimingMetric(TimingMetric.requestEnd);
|
3482 | this.adapter.visitRequestFinished(this);
|
3483 | }
|
3484 |
|
3485 | loadResponse() {
|
3486 | if (this.response) {
|
3487 | const { statusCode, responseHTML } = this.response;
|
3488 | this.render(async () => {
|
3489 | if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
3490 | if (this.view.renderPromise) await this.view.renderPromise;
|
3491 |
|
3492 | if (isSuccessful(statusCode) && responseHTML != null) {
|
3493 | const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
3494 | await this.renderPageSnapshot(snapshot, false);
|
3495 |
|
3496 | this.adapter.visitRendered(this);
|
3497 | this.complete();
|
3498 | } else {
|
3499 | await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
3500 | this.adapter.visitRendered(this);
|
3501 | this.fail();
|
3502 | }
|
3503 | });
|
3504 | }
|
3505 | }
|
3506 |
|
3507 | getCachedSnapshot() {
|
3508 | const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
|
3509 |
|
3510 | if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
|
3511 | if (this.action == "restore" || snapshot.isPreviewable) {
|
3512 | return snapshot
|
3513 | }
|
3514 | }
|
3515 | }
|
3516 |
|
3517 | getPreloadedSnapshot() {
|
3518 | if (this.snapshotHTML) {
|
3519 | return PageSnapshot.fromHTMLString(this.snapshotHTML)
|
3520 | }
|
3521 | }
|
3522 |
|
3523 | hasCachedSnapshot() {
|
3524 | return this.getCachedSnapshot() != null
|
3525 | }
|
3526 |
|
3527 | loadCachedSnapshot() {
|
3528 | const snapshot = this.getCachedSnapshot();
|
3529 | if (snapshot) {
|
3530 | const isPreview = this.shouldIssueRequest();
|
3531 | this.render(async () => {
|
3532 | this.cacheSnapshot();
|
3533 | if (this.isSamePage || this.isPageRefresh) {
|
3534 | this.adapter.visitRendered(this);
|
3535 | } else {
|
3536 | if (this.view.renderPromise) await this.view.renderPromise;
|
3537 |
|
3538 | await this.renderPageSnapshot(snapshot, isPreview);
|
3539 |
|
3540 | this.adapter.visitRendered(this);
|
3541 | if (!isPreview) {
|
3542 | this.complete();
|
3543 | }
|
3544 | }
|
3545 | });
|
3546 | }
|
3547 | }
|
3548 |
|
3549 | followRedirect() {
|
3550 | if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
|
3551 | this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
3552 | action: "replace",
|
3553 | response: this.response,
|
3554 | shouldCacheSnapshot: false,
|
3555 | willRender: false
|
3556 | });
|
3557 | this.followedRedirect = true;
|
3558 | }
|
3559 | }
|
3560 |
|
3561 | goToSamePageAnchor() {
|
3562 | if (this.isSamePage) {
|
3563 | this.render(async () => {
|
3564 | this.cacheSnapshot();
|
3565 | this.performScroll();
|
3566 | this.changeHistory();
|
3567 | this.adapter.visitRendered(this);
|
3568 | });
|
3569 | }
|
3570 | }
|
3571 |
|
3572 |
|
3573 |
|
3574 | prepareRequest(request) {
|
3575 | if (this.acceptsStreamResponse) {
|
3576 | request.acceptResponseType(StreamMessage.contentType);
|
3577 | }
|
3578 | }
|
3579 |
|
3580 | requestStarted() {
|
3581 | this.startRequest();
|
3582 | }
|
3583 |
|
3584 | requestPreventedHandlingResponse(_request, _response) {}
|
3585 |
|
3586 | async requestSucceededWithResponse(request, response) {
|
3587 | const responseHTML = await response.responseHTML;
|
3588 | const { redirected, statusCode } = response;
|
3589 | if (responseHTML == undefined) {
|
3590 | this.recordResponse({
|
3591 | statusCode: SystemStatusCode.contentTypeMismatch,
|
3592 | redirected
|
3593 | });
|
3594 | } else {
|
3595 | this.redirectedToLocation = response.redirected ? response.location : undefined;
|
3596 | this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
3597 | }
|
3598 | }
|
3599 |
|
3600 | async requestFailedWithResponse(request, response) {
|
3601 | const responseHTML = await response.responseHTML;
|
3602 | const { redirected, statusCode } = response;
|
3603 | if (responseHTML == undefined) {
|
3604 | this.recordResponse({
|
3605 | statusCode: SystemStatusCode.contentTypeMismatch,
|
3606 | redirected
|
3607 | });
|
3608 | } else {
|
3609 | this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
3610 | }
|
3611 | }
|
3612 |
|
3613 | requestErrored(_request, _error) {
|
3614 | this.recordResponse({
|
3615 | statusCode: SystemStatusCode.networkFailure,
|
3616 | redirected: false
|
3617 | });
|
3618 | }
|
3619 |
|
3620 | requestFinished() {
|
3621 | this.finishRequest();
|
3622 | }
|
3623 |
|
3624 |
|
3625 |
|
3626 | performScroll() {
|
3627 | if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
|
3628 | if (this.action == "restore") {
|
3629 | this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
3630 | } else {
|
3631 | this.scrollToAnchor() || this.view.scrollToTop();
|
3632 | }
|
3633 | if (this.isSamePage) {
|
3634 | this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
3635 | }
|
3636 |
|
3637 | this.scrolled = true;
|
3638 | }
|
3639 | }
|
3640 |
|
3641 | scrollToRestoredPosition() {
|
3642 | const { scrollPosition } = this.restorationData;
|
3643 | if (scrollPosition) {
|
3644 | this.view.scrollToPosition(scrollPosition);
|
3645 | return true
|
3646 | }
|
3647 | }
|
3648 |
|
3649 | scrollToAnchor() {
|
3650 | const anchor = getAnchor(this.location);
|
3651 | if (anchor != null) {
|
3652 | this.view.scrollToAnchor(anchor);
|
3653 | return true
|
3654 | }
|
3655 | }
|
3656 |
|
3657 |
|
3658 |
|
3659 | recordTimingMetric(metric) {
|
3660 | this.timingMetrics[metric] = new Date().getTime();
|
3661 | }
|
3662 |
|
3663 | getTimingMetrics() {
|
3664 | return { ...this.timingMetrics }
|
3665 | }
|
3666 |
|
3667 |
|
3668 |
|
3669 | getHistoryMethodForAction(action) {
|
3670 | switch (action) {
|
3671 | case "replace":
|
3672 | return history.replaceState
|
3673 | case "advance":
|
3674 | case "restore":
|
3675 | return history.pushState
|
3676 | }
|
3677 | }
|
3678 |
|
3679 | hasPreloadedResponse() {
|
3680 | return typeof this.response == "object"
|
3681 | }
|
3682 |
|
3683 | shouldIssueRequest() {
|
3684 | if (this.isSamePage) {
|
3685 | return false
|
3686 | } else if (this.action == "restore") {
|
3687 | return !this.hasCachedSnapshot()
|
3688 | } else {
|
3689 | return this.willRender
|
3690 | }
|
3691 | }
|
3692 |
|
3693 | cacheSnapshot() {
|
3694 | if (!this.snapshotCached) {
|
3695 | this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
|
3696 | this.snapshotCached = true;
|
3697 | }
|
3698 | }
|
3699 |
|
3700 | async render(callback) {
|
3701 | this.cancelRender();
|
3702 | await new Promise((resolve) => {
|
3703 | this.frame =
|
3704 | document.visibilityState === "hidden" ? setTimeout(() => resolve(), 0) : requestAnimationFrame(() => resolve());
|
3705 | });
|
3706 | await callback();
|
3707 | delete this.frame;
|
3708 | }
|
3709 |
|
3710 | async renderPageSnapshot(snapshot, isPreview) {
|
3711 | await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), async () => {
|
3712 | await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
3713 | this.performScroll();
|
3714 | });
|
3715 | }
|
3716 |
|
3717 | cancelRender() {
|
3718 | if (this.frame) {
|
3719 | cancelAnimationFrame(this.frame);
|
3720 | delete this.frame;
|
3721 | }
|
3722 | }
|
3723 | }
|
3724 |
|
3725 | function isSuccessful(statusCode) {
|
3726 | return statusCode >= 200 && statusCode < 300
|
3727 | }
|
3728 |
|
3729 | class BrowserAdapter {
|
3730 | progressBar = new ProgressBar()
|
3731 |
|
3732 | constructor(session) {
|
3733 | this.session = session;
|
3734 | }
|
3735 |
|
3736 | visitProposedToLocation(location, options) {
|
3737 | if (locationIsVisitable(location, this.navigator.rootLocation)) {
|
3738 | this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
|
3739 | } else {
|
3740 | window.location.href = location.toString();
|
3741 | }
|
3742 | }
|
3743 |
|
3744 | visitStarted(visit) {
|
3745 | this.location = visit.location;
|
3746 | visit.loadCachedSnapshot();
|
3747 | visit.issueRequest();
|
3748 | visit.goToSamePageAnchor();
|
3749 | }
|
3750 |
|
3751 | visitRequestStarted(visit) {
|
3752 | this.progressBar.setValue(0);
|
3753 | if (visit.hasCachedSnapshot() || visit.action != "restore") {
|
3754 | this.showVisitProgressBarAfterDelay();
|
3755 | } else {
|
3756 | this.showProgressBar();
|
3757 | }
|
3758 | }
|
3759 |
|
3760 | visitRequestCompleted(visit) {
|
3761 | visit.loadResponse();
|
3762 | }
|
3763 |
|
3764 | visitRequestFailedWithStatusCode(visit, statusCode) {
|
3765 | switch (statusCode) {
|
3766 | case SystemStatusCode.networkFailure:
|
3767 | case SystemStatusCode.timeoutFailure:
|
3768 | case SystemStatusCode.contentTypeMismatch:
|
3769 | return this.reload({
|
3770 | reason: "request_failed",
|
3771 | context: {
|
3772 | statusCode
|
3773 | }
|
3774 | })
|
3775 | default:
|
3776 | return visit.loadResponse()
|
3777 | }
|
3778 | }
|
3779 |
|
3780 | visitRequestFinished(_visit) {}
|
3781 |
|
3782 | visitCompleted(_visit) {
|
3783 | this.progressBar.setValue(1);
|
3784 | this.hideVisitProgressBar();
|
3785 | }
|
3786 |
|
3787 | pageInvalidated(reason) {
|
3788 | this.reload(reason);
|
3789 | }
|
3790 |
|
3791 | visitFailed(_visit) {
|
3792 | this.progressBar.setValue(1);
|
3793 | this.hideVisitProgressBar();
|
3794 | }
|
3795 |
|
3796 | visitRendered(_visit) {}
|
3797 |
|
3798 |
|
3799 |
|
3800 | formSubmissionStarted(_formSubmission) {
|
3801 | this.progressBar.setValue(0);
|
3802 | this.showFormProgressBarAfterDelay();
|
3803 | }
|
3804 |
|
3805 | formSubmissionFinished(_formSubmission) {
|
3806 | this.progressBar.setValue(1);
|
3807 | this.hideFormProgressBar();
|
3808 | }
|
3809 |
|
3810 |
|
3811 |
|
3812 | showVisitProgressBarAfterDelay() {
|
3813 | this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
|
3814 | }
|
3815 |
|
3816 | hideVisitProgressBar() {
|
3817 | this.progressBar.hide();
|
3818 | if (this.visitProgressBarTimeout != null) {
|
3819 | window.clearTimeout(this.visitProgressBarTimeout);
|
3820 | delete this.visitProgressBarTimeout;
|
3821 | }
|
3822 | }
|
3823 |
|
3824 | showFormProgressBarAfterDelay() {
|
3825 | if (this.formProgressBarTimeout == null) {
|
3826 | this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
|
3827 | }
|
3828 | }
|
3829 |
|
3830 | hideFormProgressBar() {
|
3831 | this.progressBar.hide();
|
3832 | if (this.formProgressBarTimeout != null) {
|
3833 | window.clearTimeout(this.formProgressBarTimeout);
|
3834 | delete this.formProgressBarTimeout;
|
3835 | }
|
3836 | }
|
3837 |
|
3838 | showProgressBar = () => {
|
3839 | this.progressBar.show();
|
3840 | }
|
3841 |
|
3842 | reload(reason) {
|
3843 | dispatch("turbo:reload", { detail: reason });
|
3844 |
|
3845 | window.location.href = this.location?.toString() || window.location.href;
|
3846 | }
|
3847 |
|
3848 | get navigator() {
|
3849 | return this.session.navigator
|
3850 | }
|
3851 | }
|
3852 |
|
3853 | class CacheObserver {
|
3854 | selector = "[data-turbo-temporary]"
|
3855 | deprecatedSelector = "[data-turbo-cache=false]"
|
3856 |
|
3857 | started = false
|
3858 |
|
3859 | start() {
|
3860 | if (!this.started) {
|
3861 | this.started = true;
|
3862 | addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
3863 | }
|
3864 | }
|
3865 |
|
3866 | stop() {
|
3867 | if (this.started) {
|
3868 | this.started = false;
|
3869 | removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
3870 | }
|
3871 | }
|
3872 |
|
3873 | removeTemporaryElements = (_event) => {
|
3874 | for (const element of this.temporaryElements) {
|
3875 | element.remove();
|
3876 | }
|
3877 | }
|
3878 |
|
3879 | get temporaryElements() {
|
3880 | return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation]
|
3881 | }
|
3882 |
|
3883 | get temporaryElementsWithDeprecation() {
|
3884 | const elements = document.querySelectorAll(this.deprecatedSelector);
|
3885 |
|
3886 | if (elements.length) {
|
3887 | console.warn(
|
3888 | `The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`
|
3889 | );
|
3890 | }
|
3891 |
|
3892 | return [...elements]
|
3893 | }
|
3894 | }
|
3895 |
|
3896 | class FrameRedirector {
|
3897 | constructor(session, element) {
|
3898 | this.session = session;
|
3899 | this.element = element;
|
3900 | this.linkInterceptor = new LinkInterceptor(this, element);
|
3901 | this.formSubmitObserver = new FormSubmitObserver(this, element);
|
3902 | }
|
3903 |
|
3904 | start() {
|
3905 | this.linkInterceptor.start();
|
3906 | this.formSubmitObserver.start();
|
3907 | }
|
3908 |
|
3909 | stop() {
|
3910 | this.linkInterceptor.stop();
|
3911 | this.formSubmitObserver.stop();
|
3912 | }
|
3913 |
|
3914 |
|
3915 |
|
3916 | shouldInterceptLinkClick(element, _location, _event) {
|
3917 | return this.#shouldRedirect(element)
|
3918 | }
|
3919 |
|
3920 | linkClickIntercepted(element, url, event) {
|
3921 | const frame = this.#findFrameElement(element);
|
3922 | if (frame) {
|
3923 | frame.delegate.linkClickIntercepted(element, url, event);
|
3924 | }
|
3925 | }
|
3926 |
|
3927 |
|
3928 |
|
3929 | willSubmitForm(element, submitter) {
|
3930 | return (
|
3931 | element.closest("turbo-frame") == null &&
|
3932 | this.#shouldSubmit(element, submitter) &&
|
3933 | this.#shouldRedirect(element, submitter)
|
3934 | )
|
3935 | }
|
3936 |
|
3937 | formSubmitted(element, submitter) {
|
3938 | const frame = this.#findFrameElement(element, submitter);
|
3939 | if (frame) {
|
3940 | frame.delegate.formSubmitted(element, submitter);
|
3941 | }
|
3942 | }
|
3943 |
|
3944 | #shouldSubmit(form, submitter) {
|
3945 | const action = getAction$1(form, submitter);
|
3946 | const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
3947 | const rootLocation = expandURL(meta?.content ?? "/");
|
3948 |
|
3949 | return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation)
|
3950 | }
|
3951 |
|
3952 | #shouldRedirect(element, submitter) {
|
3953 | const isNavigatable =
|
3954 | element instanceof HTMLFormElement
|
3955 | ? this.session.submissionIsNavigatable(element, submitter)
|
3956 | : this.session.elementIsNavigatable(element);
|
3957 |
|
3958 | if (isNavigatable) {
|
3959 | const frame = this.#findFrameElement(element, submitter);
|
3960 | return frame ? frame != element.closest("turbo-frame") : false
|
3961 | } else {
|
3962 | return false
|
3963 | }
|
3964 | }
|
3965 |
|
3966 | #findFrameElement(element, submitter) {
|
3967 | const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
|
3968 | if (id && id != "_top") {
|
3969 | const frame = this.element.querySelector(`#${id}:not([disabled])`);
|
3970 | if (frame instanceof FrameElement) {
|
3971 | return frame
|
3972 | }
|
3973 | }
|
3974 | }
|
3975 | }
|
3976 |
|
3977 | class History {
|
3978 | location
|
3979 | restorationIdentifier = uuid()
|
3980 | restorationData = {}
|
3981 | started = false
|
3982 | pageLoaded = false
|
3983 | currentIndex = 0
|
3984 |
|
3985 | constructor(delegate) {
|
3986 | this.delegate = delegate;
|
3987 | }
|
3988 |
|
3989 | start() {
|
3990 | if (!this.started) {
|
3991 | addEventListener("popstate", this.onPopState, false);
|
3992 | addEventListener("load", this.onPageLoad, false);
|
3993 | this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
3994 | this.started = true;
|
3995 | this.replace(new URL(window.location.href));
|
3996 | }
|
3997 | }
|
3998 |
|
3999 | stop() {
|
4000 | if (this.started) {
|
4001 | removeEventListener("popstate", this.onPopState, false);
|
4002 | removeEventListener("load", this.onPageLoad, false);
|
4003 | this.started = false;
|
4004 | }
|
4005 | }
|
4006 |
|
4007 | push(location, restorationIdentifier) {
|
4008 | this.update(history.pushState, location, restorationIdentifier);
|
4009 | }
|
4010 |
|
4011 | replace(location, restorationIdentifier) {
|
4012 | this.update(history.replaceState, location, restorationIdentifier);
|
4013 | }
|
4014 |
|
4015 | update(method, location, restorationIdentifier = uuid()) {
|
4016 | if (method === history.pushState) ++this.currentIndex;
|
4017 |
|
4018 | const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } };
|
4019 | method.call(history, state, "", location.href);
|
4020 | this.location = location;
|
4021 | this.restorationIdentifier = restorationIdentifier;
|
4022 | }
|
4023 |
|
4024 |
|
4025 |
|
4026 | getRestorationDataForIdentifier(restorationIdentifier) {
|
4027 | return this.restorationData[restorationIdentifier] || {}
|
4028 | }
|
4029 |
|
4030 | updateRestorationData(additionalData) {
|
4031 | const { restorationIdentifier } = this;
|
4032 | const restorationData = this.restorationData[restorationIdentifier];
|
4033 | this.restorationData[restorationIdentifier] = {
|
4034 | ...restorationData,
|
4035 | ...additionalData
|
4036 | };
|
4037 | }
|
4038 |
|
4039 |
|
4040 |
|
4041 | assumeControlOfScrollRestoration() {
|
4042 | if (!this.previousScrollRestoration) {
|
4043 | this.previousScrollRestoration = history.scrollRestoration ?? "auto";
|
4044 | history.scrollRestoration = "manual";
|
4045 | }
|
4046 | }
|
4047 |
|
4048 | relinquishControlOfScrollRestoration() {
|
4049 | if (this.previousScrollRestoration) {
|
4050 | history.scrollRestoration = this.previousScrollRestoration;
|
4051 | delete this.previousScrollRestoration;
|
4052 | }
|
4053 | }
|
4054 |
|
4055 |
|
4056 |
|
4057 | onPopState = (event) => {
|
4058 | if (this.shouldHandlePopState()) {
|
4059 | const { turbo } = event.state || {};
|
4060 | if (turbo) {
|
4061 | this.location = new URL(window.location.href);
|
4062 | const { restorationIdentifier, restorationIndex } = turbo;
|
4063 | this.restorationIdentifier = restorationIdentifier;
|
4064 | const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
4065 | this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
4066 | this.currentIndex = restorationIndex;
|
4067 | }
|
4068 | }
|
4069 | }
|
4070 |
|
4071 | onPageLoad = async (_event) => {
|
4072 | await nextMicrotask();
|
4073 | this.pageLoaded = true;
|
4074 | }
|
4075 |
|
4076 |
|
4077 |
|
4078 | shouldHandlePopState() {
|
4079 |
|
4080 | return this.pageIsLoaded()
|
4081 | }
|
4082 |
|
4083 | pageIsLoaded() {
|
4084 | return this.pageLoaded || document.readyState == "complete"
|
4085 | }
|
4086 | }
|
4087 |
|
4088 | class LinkPrefetchObserver {
|
4089 | started = false
|
4090 | #prefetchedLink = null
|
4091 |
|
4092 | constructor(delegate, eventTarget) {
|
4093 | this.delegate = delegate;
|
4094 | this.eventTarget = eventTarget;
|
4095 | }
|
4096 |
|
4097 | start() {
|
4098 | if (this.started) return
|
4099 |
|
4100 | if (this.eventTarget.readyState === "loading") {
|
4101 | this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, { once: true });
|
4102 | } else {
|
4103 | this.#enable();
|
4104 | }
|
4105 | }
|
4106 |
|
4107 | stop() {
|
4108 | if (!this.started) return
|
4109 |
|
4110 | this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
4111 | capture: true,
|
4112 | passive: true
|
4113 | });
|
4114 | this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
4115 | capture: true,
|
4116 | passive: true
|
4117 | });
|
4118 |
|
4119 | this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
4120 | this.started = false;
|
4121 | }
|
4122 |
|
4123 | #enable = () => {
|
4124 | this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
4125 | capture: true,
|
4126 | passive: true
|
4127 | });
|
4128 | this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
4129 | capture: true,
|
4130 | passive: true
|
4131 | });
|
4132 |
|
4133 | this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
4134 | this.started = true;
|
4135 | }
|
4136 |
|
4137 | #tryToPrefetchRequest = (event) => {
|
4138 | if (getMetaContent("turbo-prefetch") === "false") return
|
4139 |
|
4140 | const target = event.target;
|
4141 | const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
|
4142 |
|
4143 | if (isLink && this.#isPrefetchable(target)) {
|
4144 | const link = target;
|
4145 | const location = getLocationForLink(link);
|
4146 |
|
4147 | if (this.delegate.canPrefetchRequestToLocation(link, location)) {
|
4148 | this.#prefetchedLink = link;
|
4149 |
|
4150 | const fetchRequest = new FetchRequest(
|
4151 | this,
|
4152 | FetchMethod.get,
|
4153 | location,
|
4154 | new URLSearchParams(),
|
4155 | target
|
4156 | );
|
4157 |
|
4158 | prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
|
4159 | }
|
4160 | }
|
4161 | }
|
4162 |
|
4163 | #cancelRequestIfObsolete = (event) => {
|
4164 | if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
|
4165 | }
|
4166 |
|
4167 | #cancelPrefetchRequest = () => {
|
4168 | prefetchCache.clear();
|
4169 | this.#prefetchedLink = null;
|
4170 | }
|
4171 |
|
4172 | #tryToUsePrefetchedRequest = (event) => {
|
4173 | if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
4174 | const cached = prefetchCache.get(event.detail.url.toString());
|
4175 |
|
4176 | if (cached) {
|
4177 |
|
4178 | event.detail.fetchRequest = cached;
|
4179 | }
|
4180 |
|
4181 | prefetchCache.clear();
|
4182 | }
|
4183 | }
|
4184 |
|
4185 | prepareRequest(request) {
|
4186 | const link = request.target;
|
4187 |
|
4188 | request.headers["X-Sec-Purpose"] = "prefetch";
|
4189 |
|
4190 | const turboFrame = link.closest("turbo-frame");
|
4191 | const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
4192 |
|
4193 | if (turboFrameTarget && turboFrameTarget !== "_top") {
|
4194 | request.headers["Turbo-Frame"] = turboFrameTarget;
|
4195 | }
|
4196 | }
|
4197 |
|
4198 |
|
4199 |
|
4200 | requestSucceededWithResponse() {}
|
4201 |
|
4202 | requestStarted(fetchRequest) {}
|
4203 |
|
4204 | requestErrored(fetchRequest) {}
|
4205 |
|
4206 | requestFinished(fetchRequest) {}
|
4207 |
|
4208 | requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
4209 |
|
4210 | requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
4211 |
|
4212 | get #cacheTtl() {
|
4213 | return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl
|
4214 | }
|
4215 |
|
4216 | #isPrefetchable(link) {
|
4217 | const href = link.getAttribute("href");
|
4218 |
|
4219 | if (!href) return false
|
4220 |
|
4221 | if (unfetchableLink(link)) return false
|
4222 | if (linkToTheSamePage(link)) return false
|
4223 | if (linkOptsOut(link)) return false
|
4224 | if (nonSafeLink(link)) return false
|
4225 | if (eventPrevented(link)) return false
|
4226 |
|
4227 | return true
|
4228 | }
|
4229 | }
|
4230 |
|
4231 | const unfetchableLink = (link) => {
|
4232 | return link.origin !== document.location.origin || !["http:", "https:"].includes(link.protocol) || link.hasAttribute("target")
|
4233 | };
|
4234 |
|
4235 | const linkToTheSamePage = (link) => {
|
4236 | return (link.pathname + link.search === document.location.pathname + document.location.search) || link.href.startsWith("#")
|
4237 | };
|
4238 |
|
4239 | const linkOptsOut = (link) => {
|
4240 | if (link.getAttribute("data-turbo-prefetch") === "false") return true
|
4241 | if (link.getAttribute("data-turbo") === "false") return true
|
4242 |
|
4243 | const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
4244 | if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true
|
4245 |
|
4246 | return false
|
4247 | };
|
4248 |
|
4249 | const nonSafeLink = (link) => {
|
4250 | const turboMethod = link.getAttribute("data-turbo-method");
|
4251 | if (turboMethod && turboMethod.toLowerCase() !== "get") return true
|
4252 |
|
4253 | if (isUJS(link)) return true
|
4254 | if (link.hasAttribute("data-turbo-confirm")) return true
|
4255 | if (link.hasAttribute("data-turbo-stream")) return true
|
4256 |
|
4257 | return false
|
4258 | };
|
4259 |
|
4260 | const isUJS = (link) => {
|
4261 | return link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method")
|
4262 | };
|
4263 |
|
4264 | const eventPrevented = (link) => {
|
4265 | const event = dispatch("turbo:before-prefetch", { target: link, cancelable: true });
|
4266 | return event.defaultPrevented
|
4267 | };
|
4268 |
|
4269 | class Navigator {
|
4270 | constructor(delegate) {
|
4271 | this.delegate = delegate;
|
4272 | }
|
4273 |
|
4274 | proposeVisit(location, options = {}) {
|
4275 | if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
4276 | this.delegate.visitProposedToLocation(location, options);
|
4277 | }
|
4278 | }
|
4279 |
|
4280 | startVisit(locatable, restorationIdentifier, options = {}) {
|
4281 | this.stop();
|
4282 | this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
4283 | referrer: this.location,
|
4284 | ...options
|
4285 | });
|
4286 | this.currentVisit.start();
|
4287 | }
|
4288 |
|
4289 | submitForm(form, submitter) {
|
4290 | this.stop();
|
4291 | this.formSubmission = new FormSubmission(this, form, submitter, true);
|
4292 |
|
4293 | this.formSubmission.start();
|
4294 | }
|
4295 |
|
4296 | stop() {
|
4297 | if (this.formSubmission) {
|
4298 | this.formSubmission.stop();
|
4299 | delete this.formSubmission;
|
4300 | }
|
4301 |
|
4302 | if (this.currentVisit) {
|
4303 | this.currentVisit.cancel();
|
4304 | delete this.currentVisit;
|
4305 | }
|
4306 | }
|
4307 |
|
4308 | get adapter() {
|
4309 | return this.delegate.adapter
|
4310 | }
|
4311 |
|
4312 | get view() {
|
4313 | return this.delegate.view
|
4314 | }
|
4315 |
|
4316 | get rootLocation() {
|
4317 | return this.view.snapshot.rootLocation
|
4318 | }
|
4319 |
|
4320 | get history() {
|
4321 | return this.delegate.history
|
4322 | }
|
4323 |
|
4324 |
|
4325 |
|
4326 | formSubmissionStarted(formSubmission) {
|
4327 |
|
4328 | if (typeof this.adapter.formSubmissionStarted === "function") {
|
4329 | this.adapter.formSubmissionStarted(formSubmission);
|
4330 | }
|
4331 | }
|
4332 |
|
4333 | async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
|
4334 | if (formSubmission == this.formSubmission) {
|
4335 | const responseHTML = await fetchResponse.responseHTML;
|
4336 | if (responseHTML) {
|
4337 | const shouldCacheSnapshot = formSubmission.isSafe;
|
4338 | if (!shouldCacheSnapshot) {
|
4339 | this.view.clearSnapshotCache();
|
4340 | }
|
4341 |
|
4342 | const { statusCode, redirected } = fetchResponse;
|
4343 | const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
|
4344 | const visitOptions = {
|
4345 | action,
|
4346 | shouldCacheSnapshot,
|
4347 | response: { statusCode, responseHTML, redirected }
|
4348 | };
|
4349 | this.proposeVisit(fetchResponse.location, visitOptions);
|
4350 | }
|
4351 | }
|
4352 | }
|
4353 |
|
4354 | async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
4355 | const responseHTML = await fetchResponse.responseHTML;
|
4356 |
|
4357 | if (responseHTML) {
|
4358 | const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
4359 | if (fetchResponse.serverError) {
|
4360 | await this.view.renderError(snapshot, this.currentVisit);
|
4361 | } else {
|
4362 | await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
4363 | }
|
4364 | if(!snapshot.shouldPreserveScrollPosition) {
|
4365 | this.view.scrollToTop();
|
4366 | }
|
4367 | this.view.clearSnapshotCache();
|
4368 | }
|
4369 | }
|
4370 |
|
4371 | formSubmissionErrored(formSubmission, error) {
|
4372 | console.error(error);
|
4373 | }
|
4374 |
|
4375 | formSubmissionFinished(formSubmission) {
|
4376 |
|
4377 | if (typeof this.adapter.formSubmissionFinished === "function") {
|
4378 | this.adapter.formSubmissionFinished(formSubmission);
|
4379 | }
|
4380 | }
|
4381 |
|
4382 |
|
4383 |
|
4384 | visitStarted(visit) {
|
4385 | this.delegate.visitStarted(visit);
|
4386 | }
|
4387 |
|
4388 | visitCompleted(visit) {
|
4389 | this.delegate.visitCompleted(visit);
|
4390 | delete this.currentVisit;
|
4391 | }
|
4392 |
|
4393 | locationWithActionIsSamePage(location, action) {
|
4394 | const anchor = getAnchor(location);
|
4395 | const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
4396 | const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
4397 |
|
4398 | return (
|
4399 | action !== "replace" &&
|
4400 | getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
|
4401 | (isRestorationToTop || (anchor != null && anchor !== currentAnchor))
|
4402 | )
|
4403 | }
|
4404 |
|
4405 | visitScrolledToSamePageLocation(oldURL, newURL) {
|
4406 | this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
4407 | }
|
4408 |
|
4409 |
|
4410 |
|
4411 | get location() {
|
4412 | return this.history.location
|
4413 | }
|
4414 |
|
4415 | get restorationIdentifier() {
|
4416 | return this.history.restorationIdentifier
|
4417 | }
|
4418 |
|
4419 | #getActionForFormSubmission(formSubmission, fetchResponse) {
|
4420 | const { submitter, formElement } = formSubmission;
|
4421 | return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse)
|
4422 | }
|
4423 |
|
4424 | #getDefaultAction(fetchResponse) {
|
4425 | const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
4426 | return sameLocationRedirect ? "replace" : "advance"
|
4427 | }
|
4428 | }
|
4429 |
|
4430 | const PageStage = {
|
4431 | initial: 0,
|
4432 | loading: 1,
|
4433 | interactive: 2,
|
4434 | complete: 3
|
4435 | };
|
4436 |
|
4437 | class PageObserver {
|
4438 | stage = PageStage.initial
|
4439 | started = false
|
4440 |
|
4441 | constructor(delegate) {
|
4442 | this.delegate = delegate;
|
4443 | }
|
4444 |
|
4445 | start() {
|
4446 | if (!this.started) {
|
4447 | if (this.stage == PageStage.initial) {
|
4448 | this.stage = PageStage.loading;
|
4449 | }
|
4450 | document.addEventListener("readystatechange", this.interpretReadyState, false);
|
4451 | addEventListener("pagehide", this.pageWillUnload, false);
|
4452 | this.started = true;
|
4453 | }
|
4454 | }
|
4455 |
|
4456 | stop() {
|
4457 | if (this.started) {
|
4458 | document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
4459 | removeEventListener("pagehide", this.pageWillUnload, false);
|
4460 | this.started = false;
|
4461 | }
|
4462 | }
|
4463 |
|
4464 | interpretReadyState = () => {
|
4465 | const { readyState } = this;
|
4466 | if (readyState == "interactive") {
|
4467 | this.pageIsInteractive();
|
4468 | } else if (readyState == "complete") {
|
4469 | this.pageIsComplete();
|
4470 | }
|
4471 | }
|
4472 |
|
4473 | pageIsInteractive() {
|
4474 | if (this.stage == PageStage.loading) {
|
4475 | this.stage = PageStage.interactive;
|
4476 | this.delegate.pageBecameInteractive();
|
4477 | }
|
4478 | }
|
4479 |
|
4480 | pageIsComplete() {
|
4481 | this.pageIsInteractive();
|
4482 | if (this.stage == PageStage.interactive) {
|
4483 | this.stage = PageStage.complete;
|
4484 | this.delegate.pageLoaded();
|
4485 | }
|
4486 | }
|
4487 |
|
4488 | pageWillUnload = () => {
|
4489 | this.delegate.pageWillUnload();
|
4490 | }
|
4491 |
|
4492 | get readyState() {
|
4493 | return document.readyState
|
4494 | }
|
4495 | }
|
4496 |
|
4497 | class ScrollObserver {
|
4498 | started = false
|
4499 |
|
4500 | constructor(delegate) {
|
4501 | this.delegate = delegate;
|
4502 | }
|
4503 |
|
4504 | start() {
|
4505 | if (!this.started) {
|
4506 | addEventListener("scroll", this.onScroll, false);
|
4507 | this.onScroll();
|
4508 | this.started = true;
|
4509 | }
|
4510 | }
|
4511 |
|
4512 | stop() {
|
4513 | if (this.started) {
|
4514 | removeEventListener("scroll", this.onScroll, false);
|
4515 | this.started = false;
|
4516 | }
|
4517 | }
|
4518 |
|
4519 | onScroll = () => {
|
4520 | this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });
|
4521 | }
|
4522 |
|
4523 |
|
4524 |
|
4525 | updatePosition(position) {
|
4526 | this.delegate.scrollPositionChanged(position);
|
4527 | }
|
4528 | }
|
4529 |
|
4530 | class StreamMessageRenderer {
|
4531 | render({ fragment }) {
|
4532 | Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => {
|
4533 | withAutofocusFromFragment(fragment, () => {
|
4534 | withPreservedFocus(() => {
|
4535 | document.documentElement.appendChild(fragment);
|
4536 | });
|
4537 | });
|
4538 | });
|
4539 | }
|
4540 |
|
4541 |
|
4542 |
|
4543 | enteringBardo(currentPermanentElement, newPermanentElement) {
|
4544 | newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
4545 | }
|
4546 |
|
4547 | leavingBardo() {}
|
4548 | }
|
4549 |
|
4550 | function getPermanentElementMapForFragment(fragment) {
|
4551 | const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
4552 | const permanentElementMap = {};
|
4553 | for (const permanentElementInDocument of permanentElementsInDocument) {
|
4554 | const { id } = permanentElementInDocument;
|
4555 |
|
4556 | for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
4557 | const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
4558 |
|
4559 | if (elementInStream) {
|
4560 | permanentElementMap[id] = [permanentElementInDocument, elementInStream];
|
4561 | }
|
4562 | }
|
4563 | }
|
4564 |
|
4565 | return permanentElementMap
|
4566 | }
|
4567 |
|
4568 | async function withAutofocusFromFragment(fragment, callback) {
|
4569 | const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
4570 | const turboStreams = fragment.querySelectorAll("turbo-stream");
|
4571 | const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
4572 | let willAutofocusId = null;
|
4573 |
|
4574 | if (elementWithAutofocus) {
|
4575 | if (elementWithAutofocus.id) {
|
4576 | willAutofocusId = elementWithAutofocus.id;
|
4577 | } else {
|
4578 | willAutofocusId = generatedID;
|
4579 | }
|
4580 |
|
4581 | elementWithAutofocus.id = willAutofocusId;
|
4582 | }
|
4583 |
|
4584 | callback();
|
4585 | await nextRepaint();
|
4586 |
|
4587 | const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
4588 |
|
4589 | if (hasNoActiveElement && willAutofocusId) {
|
4590 | const elementToAutofocus = document.getElementById(willAutofocusId);
|
4591 |
|
4592 | if (elementIsFocusable(elementToAutofocus)) {
|
4593 | elementToAutofocus.focus();
|
4594 | }
|
4595 | if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
4596 | elementToAutofocus.removeAttribute("id");
|
4597 | }
|
4598 | }
|
4599 | }
|
4600 |
|
4601 | async function withPreservedFocus(callback) {
|
4602 | const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, () => document.activeElement);
|
4603 |
|
4604 | const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
4605 |
|
4606 | if (restoreFocusTo) {
|
4607 | const elementToFocus = document.getElementById(restoreFocusTo);
|
4608 |
|
4609 | if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
4610 | elementToFocus.focus();
|
4611 | }
|
4612 | }
|
4613 | }
|
4614 |
|
4615 | function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
4616 | for (const streamElement of nodeListOfStreamElements) {
|
4617 | const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
4618 |
|
4619 | if (elementWithAutofocus) return elementWithAutofocus
|
4620 | }
|
4621 |
|
4622 | return null
|
4623 | }
|
4624 |
|
4625 | class StreamObserver {
|
4626 | sources = new Set()
|
4627 | #started = false
|
4628 |
|
4629 | constructor(delegate) {
|
4630 | this.delegate = delegate;
|
4631 | }
|
4632 |
|
4633 | start() {
|
4634 | if (!this.#started) {
|
4635 | this.#started = true;
|
4636 | addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
4637 | }
|
4638 | }
|
4639 |
|
4640 | stop() {
|
4641 | if (this.#started) {
|
4642 | this.#started = false;
|
4643 | removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
4644 | }
|
4645 | }
|
4646 |
|
4647 | connectStreamSource(source) {
|
4648 | if (!this.streamSourceIsConnected(source)) {
|
4649 | this.sources.add(source);
|
4650 | source.addEventListener("message", this.receiveMessageEvent, false);
|
4651 | }
|
4652 | }
|
4653 |
|
4654 | disconnectStreamSource(source) {
|
4655 | if (this.streamSourceIsConnected(source)) {
|
4656 | this.sources.delete(source);
|
4657 | source.removeEventListener("message", this.receiveMessageEvent, false);
|
4658 | }
|
4659 | }
|
4660 |
|
4661 | streamSourceIsConnected(source) {
|
4662 | return this.sources.has(source)
|
4663 | }
|
4664 |
|
4665 | inspectFetchResponse = (event) => {
|
4666 | const response = fetchResponseFromEvent(event);
|
4667 | if (response && fetchResponseIsStream(response)) {
|
4668 | event.preventDefault();
|
4669 | this.receiveMessageResponse(response);
|
4670 | }
|
4671 | }
|
4672 |
|
4673 | receiveMessageEvent = (event) => {
|
4674 | if (this.#started && typeof event.data == "string") {
|
4675 | this.receiveMessageHTML(event.data);
|
4676 | }
|
4677 | }
|
4678 |
|
4679 | async receiveMessageResponse(response) {
|
4680 | const html = await response.responseHTML;
|
4681 | if (html) {
|
4682 | this.receiveMessageHTML(html);
|
4683 | }
|
4684 | }
|
4685 |
|
4686 | receiveMessageHTML(html) {
|
4687 | this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
4688 | }
|
4689 | }
|
4690 |
|
4691 | function fetchResponseFromEvent(event) {
|
4692 | const fetchResponse = event.detail?.fetchResponse;
|
4693 | if (fetchResponse instanceof FetchResponse) {
|
4694 | return fetchResponse
|
4695 | }
|
4696 | }
|
4697 |
|
4698 | function fetchResponseIsStream(response) {
|
4699 | const contentType = response.contentType ?? "";
|
4700 | return contentType.startsWith(StreamMessage.contentType)
|
4701 | }
|
4702 |
|
4703 | class ErrorRenderer extends Renderer {
|
4704 | static renderElement(currentElement, newElement) {
|
4705 | const { documentElement, body } = document;
|
4706 |
|
4707 | documentElement.replaceChild(newElement, body);
|
4708 | }
|
4709 |
|
4710 | async render() {
|
4711 | this.replaceHeadAndBody();
|
4712 | this.activateScriptElements();
|
4713 | }
|
4714 |
|
4715 | replaceHeadAndBody() {
|
4716 | const { documentElement, head } = document;
|
4717 | documentElement.replaceChild(this.newHead, head);
|
4718 | this.renderElement(this.currentElement, this.newElement);
|
4719 | }
|
4720 |
|
4721 | activateScriptElements() {
|
4722 | for (const replaceableElement of this.scriptElements) {
|
4723 | const parentNode = replaceableElement.parentNode;
|
4724 | if (parentNode) {
|
4725 | const element = activateScriptElement(replaceableElement);
|
4726 | parentNode.replaceChild(element, replaceableElement);
|
4727 | }
|
4728 | }
|
4729 | }
|
4730 |
|
4731 | get newHead() {
|
4732 | return this.newSnapshot.headSnapshot.element
|
4733 | }
|
4734 |
|
4735 | get scriptElements() {
|
4736 | return document.documentElement.querySelectorAll("script")
|
4737 | }
|
4738 | }
|
4739 |
|
4740 | class PageRenderer extends Renderer {
|
4741 | static renderElement(currentElement, newElement) {
|
4742 | if (document.body && newElement instanceof HTMLBodyElement) {
|
4743 | document.body.replaceWith(newElement);
|
4744 | } else {
|
4745 | document.documentElement.appendChild(newElement);
|
4746 | }
|
4747 | }
|
4748 |
|
4749 | get shouldRender() {
|
4750 | return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical
|
4751 | }
|
4752 |
|
4753 | get reloadReason() {
|
4754 | if (!this.newSnapshot.isVisitable) {
|
4755 | return {
|
4756 | reason: "turbo_visit_control_is_reload"
|
4757 | }
|
4758 | }
|
4759 |
|
4760 | if (!this.trackedElementsAreIdentical) {
|
4761 | return {
|
4762 | reason: "tracked_element_mismatch"
|
4763 | }
|
4764 | }
|
4765 | }
|
4766 |
|
4767 | async prepareToRender() {
|
4768 | this.#setLanguage();
|
4769 | await this.mergeHead();
|
4770 | }
|
4771 |
|
4772 | async render() {
|
4773 | if (this.willRender) {
|
4774 | await this.replaceBody();
|
4775 | }
|
4776 | }
|
4777 |
|
4778 | finishRendering() {
|
4779 | super.finishRendering();
|
4780 | if (!this.isPreview) {
|
4781 | this.focusFirstAutofocusableElement();
|
4782 | }
|
4783 | }
|
4784 |
|
4785 | get currentHeadSnapshot() {
|
4786 | return this.currentSnapshot.headSnapshot
|
4787 | }
|
4788 |
|
4789 | get newHeadSnapshot() {
|
4790 | return this.newSnapshot.headSnapshot
|
4791 | }
|
4792 |
|
4793 | get newElement() {
|
4794 | return this.newSnapshot.element
|
4795 | }
|
4796 |
|
4797 | #setLanguage() {
|
4798 | const { documentElement } = this.currentSnapshot;
|
4799 | const { lang } = this.newSnapshot;
|
4800 |
|
4801 | if (lang) {
|
4802 | documentElement.setAttribute("lang", lang);
|
4803 | } else {
|
4804 | documentElement.removeAttribute("lang");
|
4805 | }
|
4806 | }
|
4807 |
|
4808 | async mergeHead() {
|
4809 | const mergedHeadElements = this.mergeProvisionalElements();
|
4810 | const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
4811 | this.copyNewHeadScriptElements();
|
4812 |
|
4813 | await mergedHeadElements;
|
4814 | await newStylesheetElements;
|
4815 |
|
4816 | if (this.willRender) {
|
4817 | this.removeUnusedDynamicStylesheetElements();
|
4818 | }
|
4819 | }
|
4820 |
|
4821 | async replaceBody() {
|
4822 | await this.preservingPermanentElements(async () => {
|
4823 | this.activateNewBody();
|
4824 | await this.assignNewBody();
|
4825 | });
|
4826 | }
|
4827 |
|
4828 | get trackedElementsAreIdentical() {
|
4829 | return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature
|
4830 | }
|
4831 |
|
4832 | async copyNewHeadStylesheetElements() {
|
4833 | const loadingElements = [];
|
4834 |
|
4835 | for (const element of this.newHeadStylesheetElements) {
|
4836 | loadingElements.push(waitForLoad(element));
|
4837 |
|
4838 | document.head.appendChild(element);
|
4839 | }
|
4840 |
|
4841 | await Promise.all(loadingElements);
|
4842 | }
|
4843 |
|
4844 | copyNewHeadScriptElements() {
|
4845 | for (const element of this.newHeadScriptElements) {
|
4846 | document.head.appendChild(activateScriptElement(element));
|
4847 | }
|
4848 | }
|
4849 |
|
4850 | removeUnusedDynamicStylesheetElements() {
|
4851 | for (const element of this.unusedDynamicStylesheetElements) {
|
4852 | document.head.removeChild(element);
|
4853 | }
|
4854 | }
|
4855 |
|
4856 | async mergeProvisionalElements() {
|
4857 | const newHeadElements = [...this.newHeadProvisionalElements];
|
4858 |
|
4859 | for (const element of this.currentHeadProvisionalElements) {
|
4860 | if (!this.isCurrentElementInElementList(element, newHeadElements)) {
|
4861 | document.head.removeChild(element);
|
4862 | }
|
4863 | }
|
4864 |
|
4865 | for (const element of newHeadElements) {
|
4866 | document.head.appendChild(element);
|
4867 | }
|
4868 | }
|
4869 |
|
4870 | isCurrentElementInElementList(element, elementList) {
|
4871 | for (const [index, newElement] of elementList.entries()) {
|
4872 |
|
4873 | if (element.tagName == "TITLE") {
|
4874 | if (newElement.tagName != "TITLE") {
|
4875 | continue
|
4876 | }
|
4877 | if (element.innerHTML == newElement.innerHTML) {
|
4878 | elementList.splice(index, 1);
|
4879 | return true
|
4880 | }
|
4881 | }
|
4882 |
|
4883 |
|
4884 | if (newElement.isEqualNode(element)) {
|
4885 | elementList.splice(index, 1);
|
4886 | return true
|
4887 | }
|
4888 | }
|
4889 |
|
4890 | return false
|
4891 | }
|
4892 |
|
4893 | removeCurrentHeadProvisionalElements() {
|
4894 | for (const element of this.currentHeadProvisionalElements) {
|
4895 | document.head.removeChild(element);
|
4896 | }
|
4897 | }
|
4898 |
|
4899 | copyNewHeadProvisionalElements() {
|
4900 | for (const element of this.newHeadProvisionalElements) {
|
4901 | document.head.appendChild(element);
|
4902 | }
|
4903 | }
|
4904 |
|
4905 | activateNewBody() {
|
4906 | document.adoptNode(this.newElement);
|
4907 | this.activateNewBodyScriptElements();
|
4908 | }
|
4909 |
|
4910 | activateNewBodyScriptElements() {
|
4911 | for (const inertScriptElement of this.newBodyScriptElements) {
|
4912 | const activatedScriptElement = activateScriptElement(inertScriptElement);
|
4913 | inertScriptElement.replaceWith(activatedScriptElement);
|
4914 | }
|
4915 | }
|
4916 |
|
4917 | async assignNewBody() {
|
4918 | await this.renderElement(this.currentElement, this.newElement);
|
4919 | }
|
4920 |
|
4921 | get unusedDynamicStylesheetElements() {
|
4922 | return this.oldHeadStylesheetElements.filter((element) => {
|
4923 | return element.getAttribute("data-turbo-track") === "dynamic"
|
4924 | })
|
4925 | }
|
4926 |
|
4927 | get oldHeadStylesheetElements() {
|
4928 | return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot)
|
4929 | }
|
4930 |
|
4931 | get newHeadStylesheetElements() {
|
4932 | return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot)
|
4933 | }
|
4934 |
|
4935 | get newHeadScriptElements() {
|
4936 | return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot)
|
4937 | }
|
4938 |
|
4939 | get currentHeadProvisionalElements() {
|
4940 | return this.currentHeadSnapshot.provisionalElements
|
4941 | }
|
4942 |
|
4943 | get newHeadProvisionalElements() {
|
4944 | return this.newHeadSnapshot.provisionalElements
|
4945 | }
|
4946 |
|
4947 | get newBodyScriptElements() {
|
4948 | return this.newElement.querySelectorAll("script")
|
4949 | }
|
4950 | }
|
4951 |
|
4952 | class MorphingPageRenderer extends PageRenderer {
|
4953 | static renderElement(currentElement, newElement) {
|
4954 | morphElements(currentElement, newElement, {
|
4955 | callbacks: {
|
4956 | beforeNodeMorphed: element => !canRefreshFrame(element)
|
4957 | }
|
4958 | });
|
4959 |
|
4960 | for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
4961 | if (canRefreshFrame(frame)) frame.reload();
|
4962 | }
|
4963 |
|
4964 | dispatch("turbo:morph", { detail: { currentElement, newElement } });
|
4965 | }
|
4966 |
|
4967 | async preservingPermanentElements(callback) {
|
4968 | return await callback()
|
4969 | }
|
4970 |
|
4971 | get renderMethod() {
|
4972 | return "morph"
|
4973 | }
|
4974 |
|
4975 | get shouldAutofocus() {
|
4976 | return false
|
4977 | }
|
4978 | }
|
4979 |
|
4980 | function canRefreshFrame(frame) {
|
4981 | return frame instanceof FrameElement &&
|
4982 | frame.src &&
|
4983 | frame.refresh === "morph" &&
|
4984 | !frame.closest("[data-turbo-permanent]")
|
4985 | }
|
4986 |
|
4987 | class SnapshotCache {
|
4988 | keys = []
|
4989 | snapshots = {}
|
4990 |
|
4991 | constructor(size) {
|
4992 | this.size = size;
|
4993 | }
|
4994 |
|
4995 | has(location) {
|
4996 | return toCacheKey(location) in this.snapshots
|
4997 | }
|
4998 |
|
4999 | get(location) {
|
5000 | if (this.has(location)) {
|
5001 | const snapshot = this.read(location);
|
5002 | this.touch(location);
|
5003 | return snapshot
|
5004 | }
|
5005 | }
|
5006 |
|
5007 | put(location, snapshot) {
|
5008 | this.write(location, snapshot);
|
5009 | this.touch(location);
|
5010 | return snapshot
|
5011 | }
|
5012 |
|
5013 | clear() {
|
5014 | this.snapshots = {};
|
5015 | }
|
5016 |
|
5017 |
|
5018 |
|
5019 | read(location) {
|
5020 | return this.snapshots[toCacheKey(location)]
|
5021 | }
|
5022 |
|
5023 | write(location, snapshot) {
|
5024 | this.snapshots[toCacheKey(location)] = snapshot;
|
5025 | }
|
5026 |
|
5027 | touch(location) {
|
5028 | const key = toCacheKey(location);
|
5029 | const index = this.keys.indexOf(key);
|
5030 | if (index > -1) this.keys.splice(index, 1);
|
5031 | this.keys.unshift(key);
|
5032 | this.trim();
|
5033 | }
|
5034 |
|
5035 | trim() {
|
5036 | for (const key of this.keys.splice(this.size)) {
|
5037 | delete this.snapshots[key];
|
5038 | }
|
5039 | }
|
5040 | }
|
5041 |
|
5042 | class PageView extends View {
|
5043 | snapshotCache = new SnapshotCache(10)
|
5044 | lastRenderedLocation = new URL(location.href)
|
5045 | forceReloaded = false
|
5046 |
|
5047 | shouldTransitionTo(newSnapshot) {
|
5048 | return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions
|
5049 | }
|
5050 |
|
5051 | renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
5052 | const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
5053 | const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
5054 |
|
5055 | const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
5056 |
|
5057 | if (!renderer.shouldRender) {
|
5058 | this.forceReloaded = true;
|
5059 | } else {
|
5060 | visit?.changeHistory();
|
5061 | }
|
5062 |
|
5063 | return this.render(renderer)
|
5064 | }
|
5065 |
|
5066 | renderError(snapshot, visit) {
|
5067 | visit?.changeHistory();
|
5068 | const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
5069 | return this.render(renderer)
|
5070 | }
|
5071 |
|
5072 | clearSnapshotCache() {
|
5073 | this.snapshotCache.clear();
|
5074 | }
|
5075 |
|
5076 | async cacheSnapshot(snapshot = this.snapshot) {
|
5077 | if (snapshot.isCacheable) {
|
5078 | this.delegate.viewWillCacheSnapshot();
|
5079 | const { lastRenderedLocation: location } = this;
|
5080 | await nextEventLoopTick();
|
5081 | const cachedSnapshot = snapshot.clone();
|
5082 | this.snapshotCache.put(location, cachedSnapshot);
|
5083 | return cachedSnapshot
|
5084 | }
|
5085 | }
|
5086 |
|
5087 | getCachedSnapshotForLocation(location) {
|
5088 | return this.snapshotCache.get(location)
|
5089 | }
|
5090 |
|
5091 | isPageRefresh(visit) {
|
5092 | return !visit || (this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace")
|
5093 | }
|
5094 |
|
5095 | shouldPreserveScrollPosition(visit) {
|
5096 | return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition
|
5097 | }
|
5098 |
|
5099 | get snapshot() {
|
5100 | return PageSnapshot.fromElement(this.element)
|
5101 | }
|
5102 | }
|
5103 |
|
5104 | class Preloader {
|
5105 | selector = "a[data-turbo-preload]"
|
5106 |
|
5107 | constructor(delegate, snapshotCache) {
|
5108 | this.delegate = delegate;
|
5109 | this.snapshotCache = snapshotCache;
|
5110 | }
|
5111 |
|
5112 | start() {
|
5113 | if (document.readyState === "loading") {
|
5114 | document.addEventListener("DOMContentLoaded", this.#preloadAll);
|
5115 | } else {
|
5116 | this.preloadOnLoadLinksForView(document.body);
|
5117 | }
|
5118 | }
|
5119 |
|
5120 | stop() {
|
5121 | document.removeEventListener("DOMContentLoaded", this.#preloadAll);
|
5122 | }
|
5123 |
|
5124 | preloadOnLoadLinksForView(element) {
|
5125 | for (const link of element.querySelectorAll(this.selector)) {
|
5126 | if (this.delegate.shouldPreloadLink(link)) {
|
5127 | this.preloadURL(link);
|
5128 | }
|
5129 | }
|
5130 | }
|
5131 |
|
5132 | async preloadURL(link) {
|
5133 | const location = new URL(link.href);
|
5134 |
|
5135 | if (this.snapshotCache.has(location)) {
|
5136 | return
|
5137 | }
|
5138 |
|
5139 | const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams(), link);
|
5140 | await fetchRequest.perform();
|
5141 | }
|
5142 |
|
5143 |
|
5144 |
|
5145 | prepareRequest(fetchRequest) {
|
5146 | fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
|
5147 | }
|
5148 |
|
5149 | async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
5150 | try {
|
5151 | const responseHTML = await fetchResponse.responseHTML;
|
5152 | const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
5153 |
|
5154 | this.snapshotCache.put(fetchRequest.url, snapshot);
|
5155 | } catch (_) {
|
5156 |
|
5157 | }
|
5158 | }
|
5159 |
|
5160 | requestStarted(fetchRequest) {}
|
5161 |
|
5162 | requestErrored(fetchRequest) {}
|
5163 |
|
5164 | requestFinished(fetchRequest) {}
|
5165 |
|
5166 | requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
5167 |
|
5168 | requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
5169 |
|
5170 | #preloadAll = () => {
|
5171 | this.preloadOnLoadLinksForView(document.body);
|
5172 | }
|
5173 | }
|
5174 |
|
5175 | class Cache {
|
5176 | constructor(session) {
|
5177 | this.session = session;
|
5178 | }
|
5179 |
|
5180 | clear() {
|
5181 | this.session.clearCache();
|
5182 | }
|
5183 |
|
5184 | resetCacheControl() {
|
5185 | this.#setCacheControl("");
|
5186 | }
|
5187 |
|
5188 | exemptPageFromCache() {
|
5189 | this.#setCacheControl("no-cache");
|
5190 | }
|
5191 |
|
5192 | exemptPageFromPreview() {
|
5193 | this.#setCacheControl("no-preview");
|
5194 | }
|
5195 |
|
5196 | #setCacheControl(value) {
|
5197 | setMetaContent("turbo-cache-control", value);
|
5198 | }
|
5199 | }
|
5200 |
|
5201 | class Session {
|
5202 | navigator = new Navigator(this)
|
5203 | history = new History(this)
|
5204 | view = new PageView(this, document.documentElement)
|
5205 | adapter = new BrowserAdapter(this)
|
5206 |
|
5207 | pageObserver = new PageObserver(this)
|
5208 | cacheObserver = new CacheObserver()
|
5209 | linkPrefetchObserver = new LinkPrefetchObserver(this, document)
|
5210 | linkClickObserver = new LinkClickObserver(this, window)
|
5211 | formSubmitObserver = new FormSubmitObserver(this, document)
|
5212 | scrollObserver = new ScrollObserver(this)
|
5213 | streamObserver = new StreamObserver(this)
|
5214 | formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement)
|
5215 | frameRedirector = new FrameRedirector(this, document.documentElement)
|
5216 | streamMessageRenderer = new StreamMessageRenderer()
|
5217 | cache = new Cache(this)
|
5218 |
|
5219 | enabled = true
|
5220 | started = false
|
5221 | #pageRefreshDebouncePeriod = 150
|
5222 |
|
5223 | constructor(recentRequests) {
|
5224 | this.recentRequests = recentRequests;
|
5225 | this.preloader = new Preloader(this, this.view.snapshotCache);
|
5226 | this.debouncedRefresh = this.refresh;
|
5227 | this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
|
5228 | }
|
5229 |
|
5230 | start() {
|
5231 | if (!this.started) {
|
5232 | this.pageObserver.start();
|
5233 | this.cacheObserver.start();
|
5234 | this.linkPrefetchObserver.start();
|
5235 | this.formLinkClickObserver.start();
|
5236 | this.linkClickObserver.start();
|
5237 | this.formSubmitObserver.start();
|
5238 | this.scrollObserver.start();
|
5239 | this.streamObserver.start();
|
5240 | this.frameRedirector.start();
|
5241 | this.history.start();
|
5242 | this.preloader.start();
|
5243 | this.started = true;
|
5244 | this.enabled = true;
|
5245 | }
|
5246 | }
|
5247 |
|
5248 | disable() {
|
5249 | this.enabled = false;
|
5250 | }
|
5251 |
|
5252 | stop() {
|
5253 | if (this.started) {
|
5254 | this.pageObserver.stop();
|
5255 | this.cacheObserver.stop();
|
5256 | this.linkPrefetchObserver.stop();
|
5257 | this.formLinkClickObserver.stop();
|
5258 | this.linkClickObserver.stop();
|
5259 | this.formSubmitObserver.stop();
|
5260 | this.scrollObserver.stop();
|
5261 | this.streamObserver.stop();
|
5262 | this.frameRedirector.stop();
|
5263 | this.history.stop();
|
5264 | this.preloader.stop();
|
5265 | this.started = false;
|
5266 | }
|
5267 | }
|
5268 |
|
5269 | registerAdapter(adapter) {
|
5270 | this.adapter = adapter;
|
5271 | }
|
5272 |
|
5273 | visit(location, options = {}) {
|
5274 | const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
5275 |
|
5276 | if (frameElement instanceof FrameElement) {
|
5277 | const action = options.action || getVisitAction(frameElement);
|
5278 |
|
5279 | frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
|
5280 | frameElement.src = location.toString();
|
5281 | } else {
|
5282 | this.navigator.proposeVisit(expandURL(location), options);
|
5283 | }
|
5284 | }
|
5285 |
|
5286 | refresh(url, requestId) {
|
5287 | const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
5288 | if (!isRecentRequest && !this.navigator.currentVisit) {
|
5289 | this.visit(url, { action: "replace", shouldCacheSnapshot: false });
|
5290 | }
|
5291 | }
|
5292 |
|
5293 | connectStreamSource(source) {
|
5294 | this.streamObserver.connectStreamSource(source);
|
5295 | }
|
5296 |
|
5297 | disconnectStreamSource(source) {
|
5298 | this.streamObserver.disconnectStreamSource(source);
|
5299 | }
|
5300 |
|
5301 | renderStreamMessage(message) {
|
5302 | this.streamMessageRenderer.render(StreamMessage.wrap(message));
|
5303 | }
|
5304 |
|
5305 | clearCache() {
|
5306 | this.view.clearSnapshotCache();
|
5307 | }
|
5308 |
|
5309 | setProgressBarDelay(delay) {
|
5310 | console.warn(
|
5311 | "Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`"
|
5312 | );
|
5313 |
|
5314 | this.progressBarDelay = delay;
|
5315 | }
|
5316 |
|
5317 | set progressBarDelay(delay) {
|
5318 | config.drive.progressBarDelay = delay;
|
5319 | }
|
5320 |
|
5321 | get progressBarDelay() {
|
5322 | return config.drive.progressBarDelay
|
5323 | }
|
5324 |
|
5325 | set drive(value) {
|
5326 | config.drive.enabled = value;
|
5327 | }
|
5328 |
|
5329 | get drive() {
|
5330 | return config.drive.enabled
|
5331 | }
|
5332 |
|
5333 | set formMode(value) {
|
5334 | config.forms.mode = value;
|
5335 | }
|
5336 |
|
5337 | get formMode() {
|
5338 | return config.forms.mode
|
5339 | }
|
5340 |
|
5341 | get location() {
|
5342 | return this.history.location
|
5343 | }
|
5344 |
|
5345 | get restorationIdentifier() {
|
5346 | return this.history.restorationIdentifier
|
5347 | }
|
5348 |
|
5349 | get pageRefreshDebouncePeriod() {
|
5350 | return this.#pageRefreshDebouncePeriod
|
5351 | }
|
5352 |
|
5353 | set pageRefreshDebouncePeriod(value) {
|
5354 | this.refresh = debounce(this.debouncedRefresh.bind(this), value);
|
5355 | this.#pageRefreshDebouncePeriod = value;
|
5356 | }
|
5357 |
|
5358 |
|
5359 |
|
5360 | shouldPreloadLink(element) {
|
5361 | const isUnsafe = element.hasAttribute("data-turbo-method");
|
5362 | const isStream = element.hasAttribute("data-turbo-stream");
|
5363 | const frameTarget = element.getAttribute("data-turbo-frame");
|
5364 | const frame = frameTarget == "_top" ?
|
5365 | null :
|
5366 | document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
|
5367 |
|
5368 | if (isUnsafe || isStream || frame instanceof FrameElement) {
|
5369 | return false
|
5370 | } else {
|
5371 | const location = new URL(element.href);
|
5372 |
|
5373 | return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation)
|
5374 | }
|
5375 | }
|
5376 |
|
5377 |
|
5378 |
|
5379 | historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
|
5380 | if (this.enabled) {
|
5381 | this.navigator.startVisit(location, restorationIdentifier, {
|
5382 | action: "restore",
|
5383 | historyChanged: true,
|
5384 | direction
|
5385 | });
|
5386 | } else {
|
5387 | this.adapter.pageInvalidated({
|
5388 | reason: "turbo_disabled"
|
5389 | });
|
5390 | }
|
5391 | }
|
5392 |
|
5393 |
|
5394 |
|
5395 | scrollPositionChanged(position) {
|
5396 | this.history.updateRestorationData({ scrollPosition: position });
|
5397 | }
|
5398 |
|
5399 |
|
5400 |
|
5401 | willSubmitFormLinkToLocation(link, location) {
|
5402 | return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation)
|
5403 | }
|
5404 |
|
5405 | submittedFormLinkToLocation() {}
|
5406 |
|
5407 |
|
5408 |
|
5409 | canPrefetchRequestToLocation(link, location) {
|
5410 | return (
|
5411 | this.elementIsNavigatable(link) &&
|
5412 | locationIsVisitable(location, this.snapshot.rootLocation)
|
5413 | )
|
5414 | }
|
5415 |
|
5416 |
|
5417 |
|
5418 | willFollowLinkToLocation(link, location, event) {
|
5419 | return (
|
5420 | this.elementIsNavigatable(link) &&
|
5421 | locationIsVisitable(location, this.snapshot.rootLocation) &&
|
5422 | this.applicationAllowsFollowingLinkToLocation(link, location, event)
|
5423 | )
|
5424 | }
|
5425 |
|
5426 | followedLinkToLocation(link, location) {
|
5427 | const action = this.getActionForLink(link);
|
5428 | const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
|
5429 |
|
5430 | this.visit(location.href, { action, acceptsStreamResponse });
|
5431 | }
|
5432 |
|
5433 |
|
5434 |
|
5435 | allowsVisitingLocationWithAction(location, action) {
|
5436 | return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location)
|
5437 | }
|
5438 |
|
5439 | visitProposedToLocation(location, options) {
|
5440 | extendURLWithDeprecatedProperties(location);
|
5441 | this.adapter.visitProposedToLocation(location, options);
|
5442 | }
|
5443 |
|
5444 |
|
5445 |
|
5446 | visitStarted(visit) {
|
5447 | if (!visit.acceptsStreamResponse) {
|
5448 | markAsBusy(document.documentElement);
|
5449 | this.view.markVisitDirection(visit.direction);
|
5450 | }
|
5451 | extendURLWithDeprecatedProperties(visit.location);
|
5452 | if (!visit.silent) {
|
5453 | this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
5454 | }
|
5455 | }
|
5456 |
|
5457 | visitCompleted(visit) {
|
5458 | this.view.unmarkVisitDirection();
|
5459 | clearBusyState(document.documentElement);
|
5460 | this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
5461 | }
|
5462 |
|
5463 | locationWithActionIsSamePage(location, action) {
|
5464 | return this.navigator.locationWithActionIsSamePage(location, action)
|
5465 | }
|
5466 |
|
5467 | visitScrolledToSamePageLocation(oldURL, newURL) {
|
5468 | this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
5469 | }
|
5470 |
|
5471 |
|
5472 |
|
5473 | willSubmitForm(form, submitter) {
|
5474 | const action = getAction$1(form, submitter);
|
5475 |
|
5476 | return (
|
5477 | this.submissionIsNavigatable(form, submitter) &&
|
5478 | locationIsVisitable(expandURL(action), this.snapshot.rootLocation)
|
5479 | )
|
5480 | }
|
5481 |
|
5482 | formSubmitted(form, submitter) {
|
5483 | this.navigator.submitForm(form, submitter);
|
5484 | }
|
5485 |
|
5486 |
|
5487 |
|
5488 | pageBecameInteractive() {
|
5489 | this.view.lastRenderedLocation = this.location;
|
5490 | this.notifyApplicationAfterPageLoad();
|
5491 | }
|
5492 |
|
5493 | pageLoaded() {
|
5494 | this.history.assumeControlOfScrollRestoration();
|
5495 | }
|
5496 |
|
5497 | pageWillUnload() {
|
5498 | this.history.relinquishControlOfScrollRestoration();
|
5499 | }
|
5500 |
|
5501 |
|
5502 |
|
5503 | receivedMessageFromStream(message) {
|
5504 | this.renderStreamMessage(message);
|
5505 | }
|
5506 |
|
5507 |
|
5508 |
|
5509 | viewWillCacheSnapshot() {
|
5510 | if (!this.navigator.currentVisit?.silent) {
|
5511 | this.notifyApplicationBeforeCachingSnapshot();
|
5512 | }
|
5513 | }
|
5514 |
|
5515 | allowsImmediateRender({ element }, options) {
|
5516 | const event = this.notifyApplicationBeforeRender(element, options);
|
5517 | const {
|
5518 | defaultPrevented,
|
5519 | detail: { render }
|
5520 | } = event;
|
5521 |
|
5522 | if (this.view.renderer && render) {
|
5523 | this.view.renderer.renderElement = render;
|
5524 | }
|
5525 |
|
5526 | return !defaultPrevented
|
5527 | }
|
5528 |
|
5529 | viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
|
5530 | this.view.lastRenderedLocation = this.history.location;
|
5531 | this.notifyApplicationAfterRender(renderMethod);
|
5532 | }
|
5533 |
|
5534 | preloadOnLoadLinksForView(element) {
|
5535 | this.preloader.preloadOnLoadLinksForView(element);
|
5536 | }
|
5537 |
|
5538 | viewInvalidated(reason) {
|
5539 | this.adapter.pageInvalidated(reason);
|
5540 | }
|
5541 |
|
5542 |
|
5543 |
|
5544 | frameLoaded(frame) {
|
5545 | this.notifyApplicationAfterFrameLoad(frame);
|
5546 | }
|
5547 |
|
5548 | frameRendered(fetchResponse, frame) {
|
5549 | this.notifyApplicationAfterFrameRender(fetchResponse, frame);
|
5550 | }
|
5551 |
|
5552 |
|
5553 |
|
5554 | applicationAllowsFollowingLinkToLocation(link, location, ev) {
|
5555 | const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
|
5556 | return !event.defaultPrevented
|
5557 | }
|
5558 |
|
5559 | applicationAllowsVisitingLocation(location) {
|
5560 | const event = this.notifyApplicationBeforeVisitingLocation(location);
|
5561 | return !event.defaultPrevented
|
5562 | }
|
5563 |
|
5564 | notifyApplicationAfterClickingLinkToLocation(link, location, event) {
|
5565 | return dispatch("turbo:click", {
|
5566 | target: link,
|
5567 | detail: { url: location.href, originalEvent: event },
|
5568 | cancelable: true
|
5569 | })
|
5570 | }
|
5571 |
|
5572 | notifyApplicationBeforeVisitingLocation(location) {
|
5573 | return dispatch("turbo:before-visit", {
|
5574 | detail: { url: location.href },
|
5575 | cancelable: true
|
5576 | })
|
5577 | }
|
5578 |
|
5579 | notifyApplicationAfterVisitingLocation(location, action) {
|
5580 | return dispatch("turbo:visit", { detail: { url: location.href, action } })
|
5581 | }
|
5582 |
|
5583 | notifyApplicationBeforeCachingSnapshot() {
|
5584 | return dispatch("turbo:before-cache")
|
5585 | }
|
5586 |
|
5587 | notifyApplicationBeforeRender(newBody, options) {
|
5588 | return dispatch("turbo:before-render", {
|
5589 | detail: { newBody, ...options },
|
5590 | cancelable: true
|
5591 | })
|
5592 | }
|
5593 |
|
5594 | notifyApplicationAfterRender(renderMethod) {
|
5595 | return dispatch("turbo:render", { detail: { renderMethod } })
|
5596 | }
|
5597 |
|
5598 | notifyApplicationAfterPageLoad(timing = {}) {
|
5599 | return dispatch("turbo:load", {
|
5600 | detail: { url: this.location.href, timing }
|
5601 | })
|
5602 | }
|
5603 |
|
5604 | notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
5605 | dispatchEvent(
|
5606 | new HashChangeEvent("hashchange", {
|
5607 | oldURL: oldURL.toString(),
|
5608 | newURL: newURL.toString()
|
5609 | })
|
5610 | );
|
5611 | }
|
5612 |
|
5613 | notifyApplicationAfterFrameLoad(frame) {
|
5614 | return dispatch("turbo:frame-load", { target: frame })
|
5615 | }
|
5616 |
|
5617 | notifyApplicationAfterFrameRender(fetchResponse, frame) {
|
5618 | return dispatch("turbo:frame-render", {
|
5619 | detail: { fetchResponse },
|
5620 | target: frame,
|
5621 | cancelable: true
|
5622 | })
|
5623 | }
|
5624 |
|
5625 |
|
5626 |
|
5627 | submissionIsNavigatable(form, submitter) {
|
5628 | if (config.forms.mode == "off") {
|
5629 | return false
|
5630 | } else {
|
5631 | const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
5632 |
|
5633 | if (config.forms.mode == "optin") {
|
5634 | return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null
|
5635 | } else {
|
5636 | return submitterIsNavigatable && this.elementIsNavigatable(form)
|
5637 | }
|
5638 | }
|
5639 | }
|
5640 |
|
5641 | elementIsNavigatable(element) {
|
5642 | const container = findClosestRecursively(element, "[data-turbo]");
|
5643 | const withinFrame = findClosestRecursively(element, "turbo-frame");
|
5644 |
|
5645 |
|
5646 | if (config.drive.enabled || withinFrame) {
|
5647 |
|
5648 | if (container) {
|
5649 | return container.getAttribute("data-turbo") != "false"
|
5650 | } else {
|
5651 | return true
|
5652 | }
|
5653 | } else {
|
5654 |
|
5655 | if (container) {
|
5656 | return container.getAttribute("data-turbo") == "true"
|
5657 | } else {
|
5658 | return false
|
5659 | }
|
5660 | }
|
5661 | }
|
5662 |
|
5663 |
|
5664 |
|
5665 | getActionForLink(link) {
|
5666 | return getVisitAction(link) || "advance"
|
5667 | }
|
5668 |
|
5669 | get snapshot() {
|
5670 | return this.view.snapshot
|
5671 | }
|
5672 | }
|
5673 |
|
5674 |
|
5675 |
|
5676 |
|
5677 |
|
5678 |
|
5679 |
|
5680 |
|
5681 |
|
5682 |
|
5683 |
|
5684 |
|
5685 | function extendURLWithDeprecatedProperties(url) {
|
5686 | Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
5687 | }
|
5688 |
|
5689 | const deprecatedLocationPropertyDescriptors = {
|
5690 | absoluteURL: {
|
5691 | get() {
|
5692 | return this.toString()
|
5693 | }
|
5694 | }
|
5695 | };
|
5696 |
|
5697 | const session = new Session(recentRequests);
|
5698 | const { cache, navigator: navigator$1 } = session;
|
5699 |
|
5700 | |
5701 |
|
5702 |
|
5703 |
|
5704 |
|
5705 | function start() {
|
5706 | session.start();
|
5707 | }
|
5708 |
|
5709 | |
5710 |
|
5711 |
|
5712 |
|
5713 |
|
5714 | function registerAdapter(adapter) {
|
5715 | session.registerAdapter(adapter);
|
5716 | }
|
5717 |
|
5718 | |
5719 |
|
5720 |
|
5721 |
|
5722 |
|
5723 |
|
5724 |
|
5725 |
|
5726 |
|
5727 |
|
5728 |
|
5729 |
|
5730 |
|
5731 |
|
5732 | function visit(location, options) {
|
5733 | session.visit(location, options);
|
5734 | }
|
5735 |
|
5736 | |
5737 |
|
5738 |
|
5739 |
|
5740 |
|
5741 | function connectStreamSource(source) {
|
5742 | session.connectStreamSource(source);
|
5743 | }
|
5744 |
|
5745 | |
5746 |
|
5747 |
|
5748 |
|
5749 |
|
5750 | function disconnectStreamSource(source) {
|
5751 | session.disconnectStreamSource(source);
|
5752 | }
|
5753 |
|
5754 | |
5755 |
|
5756 |
|
5757 |
|
5758 |
|
5759 |
|
5760 | function renderStreamMessage(message) {
|
5761 | session.renderStreamMessage(message);
|
5762 | }
|
5763 |
|
5764 | |
5765 |
|
5766 |
|
5767 |
|
5768 |
|
5769 |
|
5770 | function clearCache() {
|
5771 | console.warn(
|
5772 | "Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
5773 | );
|
5774 | session.clearCache();
|
5775 | }
|
5776 |
|
5777 | |
5778 |
|
5779 |
|
5780 |
|
5781 |
|
5782 |
|
5783 |
|
5784 |
|
5785 |
|
5786 |
|
5787 | function setProgressBarDelay(delay) {
|
5788 | console.warn(
|
5789 | "Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
5790 | );
|
5791 | config.drive.progressBarDelay = delay;
|
5792 | }
|
5793 |
|
5794 | function setConfirmMethod(confirmMethod) {
|
5795 | console.warn(
|
5796 | "Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
5797 | );
|
5798 | config.forms.confirm = confirmMethod;
|
5799 | }
|
5800 |
|
5801 | function setFormMode(mode) {
|
5802 | console.warn(
|
5803 | "Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
5804 | );
|
5805 | config.forms.mode = mode;
|
5806 | }
|
5807 |
|
5808 | var Turbo = Object.freeze({
|
5809 | __proto__: null,
|
5810 | navigator: navigator$1,
|
5811 | session: session,
|
5812 | cache: cache,
|
5813 | PageRenderer: PageRenderer,
|
5814 | PageSnapshot: PageSnapshot,
|
5815 | FrameRenderer: FrameRenderer,
|
5816 | fetch: fetchWithTurboHeaders,
|
5817 | config: config,
|
5818 | start: start,
|
5819 | registerAdapter: registerAdapter,
|
5820 | visit: visit,
|
5821 | connectStreamSource: connectStreamSource,
|
5822 | disconnectStreamSource: disconnectStreamSource,
|
5823 | renderStreamMessage: renderStreamMessage,
|
5824 | clearCache: clearCache,
|
5825 | setProgressBarDelay: setProgressBarDelay,
|
5826 | setConfirmMethod: setConfirmMethod,
|
5827 | setFormMode: setFormMode
|
5828 | });
|
5829 |
|
5830 | class TurboFrameMissingError extends Error {}
|
5831 |
|
5832 | class FrameController {
|
5833 | fetchResponseLoaded = (_fetchResponse) => Promise.resolve()
|
5834 | #currentFetchRequest = null
|
5835 | #resolveVisitPromise = () => {}
|
5836 | #connected = false
|
5837 | #hasBeenLoaded = false
|
5838 | #ignoredAttributes = new Set()
|
5839 | #shouldMorphFrame = false
|
5840 | action = null
|
5841 |
|
5842 | constructor(element) {
|
5843 | this.element = element;
|
5844 | this.view = new FrameView(this, this.element);
|
5845 | this.appearanceObserver = new AppearanceObserver(this, this.element);
|
5846 | this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
|
5847 | this.linkInterceptor = new LinkInterceptor(this, this.element);
|
5848 | this.restorationIdentifier = uuid();
|
5849 | this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
5850 | }
|
5851 |
|
5852 |
|
5853 |
|
5854 | connect() {
|
5855 | if (!this.#connected) {
|
5856 | this.#connected = true;
|
5857 | if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
5858 | this.appearanceObserver.start();
|
5859 | } else {
|
5860 | this.#loadSourceURL();
|
5861 | }
|
5862 | this.formLinkClickObserver.start();
|
5863 | this.linkInterceptor.start();
|
5864 | this.formSubmitObserver.start();
|
5865 | }
|
5866 | }
|
5867 |
|
5868 | disconnect() {
|
5869 | if (this.#connected) {
|
5870 | this.#connected = false;
|
5871 | this.appearanceObserver.stop();
|
5872 | this.formLinkClickObserver.stop();
|
5873 | this.linkInterceptor.stop();
|
5874 | this.formSubmitObserver.stop();
|
5875 | }
|
5876 | }
|
5877 |
|
5878 | disabledChanged() {
|
5879 | if (this.loadingStyle == FrameLoadingStyle.eager) {
|
5880 | this.#loadSourceURL();
|
5881 | }
|
5882 | }
|
5883 |
|
5884 | sourceURLChanged() {
|
5885 | if (this.#isIgnoringChangesTo("src")) return
|
5886 |
|
5887 | if (this.element.isConnected) {
|
5888 | this.complete = false;
|
5889 | }
|
5890 |
|
5891 | if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
|
5892 | this.#loadSourceURL();
|
5893 | }
|
5894 | }
|
5895 |
|
5896 | sourceURLReloaded() {
|
5897 | const { refresh, src } = this.element;
|
5898 |
|
5899 | this.#shouldMorphFrame = src && refresh === "morph";
|
5900 |
|
5901 | this.element.removeAttribute("complete");
|
5902 | this.element.src = null;
|
5903 | this.element.src = src;
|
5904 | return this.element.loaded
|
5905 | }
|
5906 |
|
5907 | loadingStyleChanged() {
|
5908 | if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
5909 | this.appearanceObserver.start();
|
5910 | } else {
|
5911 | this.appearanceObserver.stop();
|
5912 | this.#loadSourceURL();
|
5913 | }
|
5914 | }
|
5915 |
|
5916 | async #loadSourceURL() {
|
5917 | if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
5918 | this.element.loaded = this.#visit(expandURL(this.sourceURL));
|
5919 | this.appearanceObserver.stop();
|
5920 | await this.element.loaded;
|
5921 | this.#hasBeenLoaded = true;
|
5922 | }
|
5923 | }
|
5924 |
|
5925 | async loadResponse(fetchResponse) {
|
5926 | if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
|
5927 | this.sourceURL = fetchResponse.response.url;
|
5928 | }
|
5929 |
|
5930 | try {
|
5931 | const html = await fetchResponse.responseHTML;
|
5932 | if (html) {
|
5933 | const document = parseHTMLDocument(html);
|
5934 | const pageSnapshot = PageSnapshot.fromDocument(document);
|
5935 |
|
5936 | if (pageSnapshot.isVisitable) {
|
5937 | await this.#loadFrameResponse(fetchResponse, document);
|
5938 | } else {
|
5939 | await this.#handleUnvisitableFrameResponse(fetchResponse);
|
5940 | }
|
5941 | }
|
5942 | } finally {
|
5943 | this.#shouldMorphFrame = false;
|
5944 | this.fetchResponseLoaded = () => Promise.resolve();
|
5945 | }
|
5946 | }
|
5947 |
|
5948 |
|
5949 |
|
5950 | elementAppearedInViewport(element) {
|
5951 | this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
|
5952 | this.#loadSourceURL();
|
5953 | }
|
5954 |
|
5955 |
|
5956 |
|
5957 | willSubmitFormLinkToLocation(link) {
|
5958 | return this.#shouldInterceptNavigation(link)
|
5959 | }
|
5960 |
|
5961 | submittedFormLinkToLocation(link, _location, form) {
|
5962 | const frame = this.#findFrameElement(link);
|
5963 | if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
5964 | }
|
5965 |
|
5966 |
|
5967 |
|
5968 | shouldInterceptLinkClick(element, _location, _event) {
|
5969 | return this.#shouldInterceptNavigation(element)
|
5970 | }
|
5971 |
|
5972 | linkClickIntercepted(element, location) {
|
5973 | this.#navigateFrame(element, location);
|
5974 | }
|
5975 |
|
5976 |
|
5977 |
|
5978 | willSubmitForm(element, submitter) {
|
5979 | return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter)
|
5980 | }
|
5981 |
|
5982 | formSubmitted(element, submitter) {
|
5983 | if (this.formSubmission) {
|
5984 | this.formSubmission.stop();
|
5985 | }
|
5986 |
|
5987 | this.formSubmission = new FormSubmission(this, element, submitter);
|
5988 | const { fetchRequest } = this.formSubmission;
|
5989 | this.prepareRequest(fetchRequest);
|
5990 | this.formSubmission.start();
|
5991 | }
|
5992 |
|
5993 |
|
5994 |
|
5995 | prepareRequest(request) {
|
5996 | request.headers["Turbo-Frame"] = this.id;
|
5997 |
|
5998 | if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
5999 | request.acceptResponseType(StreamMessage.contentType);
|
6000 | }
|
6001 | }
|
6002 |
|
6003 | requestStarted(_request) {
|
6004 | markAsBusy(this.element);
|
6005 | }
|
6006 |
|
6007 | requestPreventedHandlingResponse(_request, _response) {
|
6008 | this.#resolveVisitPromise();
|
6009 | }
|
6010 |
|
6011 | async requestSucceededWithResponse(request, response) {
|
6012 | await this.loadResponse(response);
|
6013 | this.#resolveVisitPromise();
|
6014 | }
|
6015 |
|
6016 | async requestFailedWithResponse(request, response) {
|
6017 | await this.loadResponse(response);
|
6018 | this.#resolveVisitPromise();
|
6019 | }
|
6020 |
|
6021 | requestErrored(request, error) {
|
6022 | console.error(error);
|
6023 | this.#resolveVisitPromise();
|
6024 | }
|
6025 |
|
6026 | requestFinished(_request) {
|
6027 | clearBusyState(this.element);
|
6028 | }
|
6029 |
|
6030 |
|
6031 |
|
6032 | formSubmissionStarted({ formElement }) {
|
6033 | markAsBusy(formElement, this.#findFrameElement(formElement));
|
6034 | }
|
6035 |
|
6036 | formSubmissionSucceededWithResponse(formSubmission, response) {
|
6037 | const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
6038 |
|
6039 | frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
|
6040 | frame.delegate.loadResponse(response);
|
6041 |
|
6042 | if (!formSubmission.isSafe) {
|
6043 | session.clearCache();
|
6044 | }
|
6045 | }
|
6046 |
|
6047 | formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
6048 | this.element.delegate.loadResponse(fetchResponse);
|
6049 | session.clearCache();
|
6050 | }
|
6051 |
|
6052 | formSubmissionErrored(formSubmission, error) {
|
6053 | console.error(error);
|
6054 | }
|
6055 |
|
6056 | formSubmissionFinished({ formElement }) {
|
6057 | clearBusyState(formElement, this.#findFrameElement(formElement));
|
6058 | }
|
6059 |
|
6060 |
|
6061 |
|
6062 | allowsImmediateRender({ element: newFrame }, options) {
|
6063 | const event = dispatch("turbo:before-frame-render", {
|
6064 | target: this.element,
|
6065 | detail: { newFrame, ...options },
|
6066 | cancelable: true
|
6067 | });
|
6068 |
|
6069 | const {
|
6070 | defaultPrevented,
|
6071 | detail: { render }
|
6072 | } = event;
|
6073 |
|
6074 | if (this.view.renderer && render) {
|
6075 | this.view.renderer.renderElement = render;
|
6076 | }
|
6077 |
|
6078 | return !defaultPrevented
|
6079 | }
|
6080 |
|
6081 | viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
|
6082 |
|
6083 | preloadOnLoadLinksForView(element) {
|
6084 | session.preloadOnLoadLinksForView(element);
|
6085 | }
|
6086 |
|
6087 | viewInvalidated() {}
|
6088 |
|
6089 |
|
6090 |
|
6091 | willRenderFrame(currentElement, _newElement) {
|
6092 | this.previousFrameElement = currentElement.cloneNode(true);
|
6093 | }
|
6094 |
|
6095 | visitCachedSnapshot = ({ element }) => {
|
6096 | const frame = element.querySelector("#" + this.element.id);
|
6097 |
|
6098 | if (frame && this.previousFrameElement) {
|
6099 | frame.replaceChildren(...this.previousFrameElement.children);
|
6100 | }
|
6101 |
|
6102 | delete this.previousFrameElement;
|
6103 | }
|
6104 |
|
6105 |
|
6106 |
|
6107 | async #loadFrameResponse(fetchResponse, document) {
|
6108 | const newFrameElement = await this.extractForeignFrameElement(document.body);
|
6109 | const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;
|
6110 |
|
6111 | if (newFrameElement) {
|
6112 | const snapshot = new Snapshot(newFrameElement);
|
6113 | const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);
|
6114 | if (this.view.renderPromise) await this.view.renderPromise;
|
6115 | this.changeHistory();
|
6116 |
|
6117 | await this.view.render(renderer);
|
6118 | this.complete = true;
|
6119 | session.frameRendered(fetchResponse, this.element);
|
6120 | session.frameLoaded(this.element);
|
6121 | await this.fetchResponseLoaded(fetchResponse);
|
6122 | } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
|
6123 | this.#handleFrameMissingFromResponse(fetchResponse);
|
6124 | }
|
6125 | }
|
6126 |
|
6127 | async #visit(url) {
|
6128 | const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
|
6129 |
|
6130 | this.#currentFetchRequest?.cancel();
|
6131 | this.#currentFetchRequest = request;
|
6132 |
|
6133 | return new Promise((resolve) => {
|
6134 | this.#resolveVisitPromise = () => {
|
6135 | this.#resolveVisitPromise = () => {};
|
6136 | this.#currentFetchRequest = null;
|
6137 | resolve();
|
6138 | };
|
6139 | request.perform();
|
6140 | })
|
6141 | }
|
6142 |
|
6143 | #navigateFrame(element, url, submitter) {
|
6144 | const frame = this.#findFrameElement(element, submitter);
|
6145 |
|
6146 | frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
|
6147 |
|
6148 | this.#withCurrentNavigationElement(element, () => {
|
6149 | frame.src = url;
|
6150 | });
|
6151 | }
|
6152 |
|
6153 | proposeVisitIfNavigatedWithAction(frame, action = null) {
|
6154 | this.action = action;
|
6155 |
|
6156 | if (this.action) {
|
6157 | const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
6158 | const { visitCachedSnapshot } = frame.delegate;
|
6159 |
|
6160 | frame.delegate.fetchResponseLoaded = async (fetchResponse) => {
|
6161 | if (frame.src) {
|
6162 | const { statusCode, redirected } = fetchResponse;
|
6163 | const responseHTML = await fetchResponse.responseHTML;
|
6164 | const response = { statusCode, redirected, responseHTML };
|
6165 | const options = {
|
6166 | response,
|
6167 | visitCachedSnapshot,
|
6168 | willRender: false,
|
6169 | updateHistory: false,
|
6170 | restorationIdentifier: this.restorationIdentifier,
|
6171 | snapshot: pageSnapshot
|
6172 | };
|
6173 |
|
6174 | if (this.action) options.action = this.action;
|
6175 |
|
6176 | session.visit(frame.src, options);
|
6177 | }
|
6178 | };
|
6179 | }
|
6180 | }
|
6181 |
|
6182 | changeHistory() {
|
6183 | if (this.action) {
|
6184 | const method = getHistoryMethodForAction(this.action);
|
6185 | session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
6186 | }
|
6187 | }
|
6188 |
|
6189 | async #handleUnvisitableFrameResponse(fetchResponse) {
|
6190 | console.warn(
|
6191 | `The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`
|
6192 | );
|
6193 |
|
6194 | await this.#visitResponse(fetchResponse.response);
|
6195 | }
|
6196 |
|
6197 | #willHandleFrameMissingFromResponse(fetchResponse) {
|
6198 | this.element.setAttribute("complete", "");
|
6199 |
|
6200 | const response = fetchResponse.response;
|
6201 | const visit = async (url, options) => {
|
6202 | if (url instanceof Response) {
|
6203 | this.#visitResponse(url);
|
6204 | } else {
|
6205 | session.visit(url, options);
|
6206 | }
|
6207 | };
|
6208 |
|
6209 | const event = dispatch("turbo:frame-missing", {
|
6210 | target: this.element,
|
6211 | detail: { response, visit },
|
6212 | cancelable: true
|
6213 | });
|
6214 |
|
6215 | return !event.defaultPrevented
|
6216 | }
|
6217 |
|
6218 | #handleFrameMissingFromResponse(fetchResponse) {
|
6219 | this.view.missing();
|
6220 | this.#throwFrameMissingError(fetchResponse);
|
6221 | }
|
6222 |
|
6223 | #throwFrameMissingError(fetchResponse) {
|
6224 | const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
|
6225 | throw new TurboFrameMissingError(message)
|
6226 | }
|
6227 |
|
6228 | async #visitResponse(response) {
|
6229 | const wrapped = new FetchResponse(response);
|
6230 | const responseHTML = await wrapped.responseHTML;
|
6231 | const { location, redirected, statusCode } = wrapped;
|
6232 |
|
6233 | return session.visit(location, { response: { redirected, statusCode, responseHTML } })
|
6234 | }
|
6235 |
|
6236 | #findFrameElement(element, submitter) {
|
6237 | const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
6238 | return getFrameElementById(id) ?? this.element
|
6239 | }
|
6240 |
|
6241 | async extractForeignFrameElement(container) {
|
6242 | let element;
|
6243 | const id = CSS.escape(this.id);
|
6244 |
|
6245 | try {
|
6246 | element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
|
6247 | if (element) {
|
6248 | return element
|
6249 | }
|
6250 |
|
6251 | element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
|
6252 | if (element) {
|
6253 | await element.loaded;
|
6254 | return await this.extractForeignFrameElement(element)
|
6255 | }
|
6256 | } catch (error) {
|
6257 | console.error(error);
|
6258 | return new FrameElement()
|
6259 | }
|
6260 |
|
6261 | return null
|
6262 | }
|
6263 |
|
6264 | #formActionIsVisitable(form, submitter) {
|
6265 | const action = getAction$1(form, submitter);
|
6266 |
|
6267 | return locationIsVisitable(expandURL(action), this.rootLocation)
|
6268 | }
|
6269 |
|
6270 | #shouldInterceptNavigation(element, submitter) {
|
6271 | const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
6272 |
|
6273 | if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
|
6274 | return false
|
6275 | }
|
6276 |
|
6277 | if (!this.enabled || id == "_top") {
|
6278 | return false
|
6279 | }
|
6280 |
|
6281 | if (id) {
|
6282 | const frameElement = getFrameElementById(id);
|
6283 | if (frameElement) {
|
6284 | return !frameElement.disabled
|
6285 | }
|
6286 | }
|
6287 |
|
6288 | if (!session.elementIsNavigatable(element)) {
|
6289 | return false
|
6290 | }
|
6291 |
|
6292 | if (submitter && !session.elementIsNavigatable(submitter)) {
|
6293 | return false
|
6294 | }
|
6295 |
|
6296 | return true
|
6297 | }
|
6298 |
|
6299 |
|
6300 |
|
6301 | get id() {
|
6302 | return this.element.id
|
6303 | }
|
6304 |
|
6305 | get enabled() {
|
6306 | return !this.element.disabled
|
6307 | }
|
6308 |
|
6309 | get sourceURL() {
|
6310 | if (this.element.src) {
|
6311 | return this.element.src
|
6312 | }
|
6313 | }
|
6314 |
|
6315 | set sourceURL(sourceURL) {
|
6316 | this.#ignoringChangesToAttribute("src", () => {
|
6317 | this.element.src = sourceURL ?? null;
|
6318 | });
|
6319 | }
|
6320 |
|
6321 | get loadingStyle() {
|
6322 | return this.element.loading
|
6323 | }
|
6324 |
|
6325 | get isLoading() {
|
6326 | return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined
|
6327 | }
|
6328 |
|
6329 | get complete() {
|
6330 | return this.element.hasAttribute("complete")
|
6331 | }
|
6332 |
|
6333 | set complete(value) {
|
6334 | if (value) {
|
6335 | this.element.setAttribute("complete", "");
|
6336 | } else {
|
6337 | this.element.removeAttribute("complete");
|
6338 | }
|
6339 | }
|
6340 |
|
6341 | get isActive() {
|
6342 | return this.element.isActive && this.#connected
|
6343 | }
|
6344 |
|
6345 | get rootLocation() {
|
6346 | const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
6347 | const root = meta?.content ?? "/";
|
6348 | return expandURL(root)
|
6349 | }
|
6350 |
|
6351 | #isIgnoringChangesTo(attributeName) {
|
6352 | return this.#ignoredAttributes.has(attributeName)
|
6353 | }
|
6354 |
|
6355 | #ignoringChangesToAttribute(attributeName, callback) {
|
6356 | this.#ignoredAttributes.add(attributeName);
|
6357 | callback();
|
6358 | this.#ignoredAttributes.delete(attributeName);
|
6359 | }
|
6360 |
|
6361 | #withCurrentNavigationElement(element, callback) {
|
6362 | this.currentNavigationElement = element;
|
6363 | callback();
|
6364 | delete this.currentNavigationElement;
|
6365 | }
|
6366 | }
|
6367 |
|
6368 | function getFrameElementById(id) {
|
6369 | if (id != null) {
|
6370 | const element = document.getElementById(id);
|
6371 | if (element instanceof FrameElement) {
|
6372 | return element
|
6373 | }
|
6374 | }
|
6375 | }
|
6376 |
|
6377 | function activateElement(element, currentURL) {
|
6378 | if (element) {
|
6379 | const src = element.getAttribute("src");
|
6380 | if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
|
6381 | throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`)
|
6382 | }
|
6383 | if (element.ownerDocument !== document) {
|
6384 | element = document.importNode(element, true);
|
6385 | }
|
6386 |
|
6387 | if (element instanceof FrameElement) {
|
6388 | element.connectedCallback();
|
6389 | element.disconnectedCallback();
|
6390 | return element
|
6391 | }
|
6392 | }
|
6393 | }
|
6394 |
|
6395 | const StreamActions = {
|
6396 | after() {
|
6397 | this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling));
|
6398 | },
|
6399 |
|
6400 | append() {
|
6401 | this.removeDuplicateTargetChildren();
|
6402 | this.targetElements.forEach((e) => e.append(this.templateContent));
|
6403 | },
|
6404 |
|
6405 | before() {
|
6406 | this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e));
|
6407 | },
|
6408 |
|
6409 | prepend() {
|
6410 | this.removeDuplicateTargetChildren();
|
6411 | this.targetElements.forEach((e) => e.prepend(this.templateContent));
|
6412 | },
|
6413 |
|
6414 | remove() {
|
6415 | this.targetElements.forEach((e) => e.remove());
|
6416 | },
|
6417 |
|
6418 | replace() {
|
6419 | const method = this.getAttribute("method");
|
6420 |
|
6421 | this.targetElements.forEach((targetElement) => {
|
6422 | if (method === "morph") {
|
6423 | morphElements(targetElement, this.templateContent);
|
6424 | } else {
|
6425 | targetElement.replaceWith(this.templateContent);
|
6426 | }
|
6427 | });
|
6428 | },
|
6429 |
|
6430 | update() {
|
6431 | const method = this.getAttribute("method");
|
6432 |
|
6433 | this.targetElements.forEach((targetElement) => {
|
6434 | if (method === "morph") {
|
6435 | morphChildren(targetElement, this.templateContent);
|
6436 | } else {
|
6437 | targetElement.innerHTML = "";
|
6438 | targetElement.append(this.templateContent);
|
6439 | }
|
6440 | });
|
6441 | },
|
6442 |
|
6443 | refresh() {
|
6444 | session.refresh(this.baseURI, this.requestId);
|
6445 | }
|
6446 | };
|
6447 |
|
6448 |
|
6449 |
|
6450 | |
6451 |
|
6452 |
|
6453 |
|
6454 |
|
6455 |
|
6456 |
|
6457 |
|
6458 |
|
6459 |
|
6460 |
|
6461 |
|
6462 |
|
6463 |
|
6464 |
|
6465 |
|
6466 |
|
6467 |
|
6468 |
|
6469 |
|
6470 |
|
6471 |
|
6472 | class StreamElement extends HTMLElement {
|
6473 | static async renderElement(newElement) {
|
6474 | await newElement.performAction();
|
6475 | }
|
6476 |
|
6477 | async connectedCallback() {
|
6478 | try {
|
6479 | await this.render();
|
6480 | } catch (error) {
|
6481 | console.error(error);
|
6482 | } finally {
|
6483 | this.disconnect();
|
6484 | }
|
6485 | }
|
6486 |
|
6487 | async render() {
|
6488 | return (this.renderPromise ??= (async () => {
|
6489 | const event = this.beforeRenderEvent;
|
6490 |
|
6491 | if (this.dispatchEvent(event)) {
|
6492 | await nextRepaint();
|
6493 | await event.detail.render(this);
|
6494 | }
|
6495 | })())
|
6496 | }
|
6497 |
|
6498 | disconnect() {
|
6499 | try {
|
6500 | this.remove();
|
6501 |
|
6502 | } catch {}
|
6503 | }
|
6504 |
|
6505 | |
6506 |
|
6507 |
|
6508 | removeDuplicateTargetChildren() {
|
6509 | this.duplicateChildren.forEach((c) => c.remove());
|
6510 | }
|
6511 |
|
6512 | |
6513 |
|
6514 |
|
6515 | get duplicateChildren() {
|
6516 | const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
|
6517 | const newChildrenIds = [...(this.templateContent?.children || [])].filter((c) => !!c.id).map((c) => c.id);
|
6518 |
|
6519 | return existingChildren.filter((c) => newChildrenIds.includes(c.id))
|
6520 | }
|
6521 |
|
6522 | |
6523 |
|
6524 |
|
6525 | get performAction() {
|
6526 | if (this.action) {
|
6527 | const actionFunction = StreamActions[this.action];
|
6528 | if (actionFunction) {
|
6529 | return actionFunction
|
6530 | }
|
6531 | this.#raise("unknown action");
|
6532 | }
|
6533 | this.#raise("action attribute is missing");
|
6534 | }
|
6535 |
|
6536 | |
6537 |
|
6538 |
|
6539 | get targetElements() {
|
6540 | if (this.target) {
|
6541 | return this.targetElementsById
|
6542 | } else if (this.targets) {
|
6543 | return this.targetElementsByQuery
|
6544 | } else {
|
6545 | this.#raise("target or targets attribute is missing");
|
6546 | }
|
6547 | }
|
6548 |
|
6549 | |
6550 |
|
6551 |
|
6552 | get templateContent() {
|
6553 | return this.templateElement.content.cloneNode(true)
|
6554 | }
|
6555 |
|
6556 | |
6557 |
|
6558 |
|
6559 | get templateElement() {
|
6560 | if (this.firstElementChild === null) {
|
6561 | const template = this.ownerDocument.createElement("template");
|
6562 | this.appendChild(template);
|
6563 | return template
|
6564 | } else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
6565 | return this.firstElementChild
|
6566 | }
|
6567 | this.#raise("first child element must be a <template> element");
|
6568 | }
|
6569 |
|
6570 | |
6571 |
|
6572 |
|
6573 | get action() {
|
6574 | return this.getAttribute("action")
|
6575 | }
|
6576 |
|
6577 | |
6578 |
|
6579 |
|
6580 |
|
6581 | get target() {
|
6582 | return this.getAttribute("target")
|
6583 | }
|
6584 |
|
6585 | |
6586 |
|
6587 |
|
6588 | get targets() {
|
6589 | return this.getAttribute("targets")
|
6590 | }
|
6591 |
|
6592 | |
6593 |
|
6594 |
|
6595 | get requestId() {
|
6596 | return this.getAttribute("request-id")
|
6597 | }
|
6598 |
|
6599 | #raise(message) {
|
6600 | throw new Error(`${this.description}: ${message}`)
|
6601 | }
|
6602 |
|
6603 | get description() {
|
6604 | return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>"
|
6605 | }
|
6606 |
|
6607 | get beforeRenderEvent() {
|
6608 | return new CustomEvent("turbo:before-stream-render", {
|
6609 | bubbles: true,
|
6610 | cancelable: true,
|
6611 | detail: { newStream: this, render: StreamElement.renderElement }
|
6612 | })
|
6613 | }
|
6614 |
|
6615 | get targetElementsById() {
|
6616 | const element = this.ownerDocument?.getElementById(this.target);
|
6617 |
|
6618 | if (element !== null) {
|
6619 | return [element]
|
6620 | } else {
|
6621 | return []
|
6622 | }
|
6623 | }
|
6624 |
|
6625 | get targetElementsByQuery() {
|
6626 | const elements = this.ownerDocument?.querySelectorAll(this.targets);
|
6627 |
|
6628 | if (elements.length !== 0) {
|
6629 | return Array.prototype.slice.call(elements)
|
6630 | } else {
|
6631 | return []
|
6632 | }
|
6633 | }
|
6634 | }
|
6635 |
|
6636 | class StreamSourceElement extends HTMLElement {
|
6637 | streamSource = null
|
6638 |
|
6639 | connectedCallback() {
|
6640 | this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
6641 |
|
6642 | connectStreamSource(this.streamSource);
|
6643 | }
|
6644 |
|
6645 | disconnectedCallback() {
|
6646 | if (this.streamSource) {
|
6647 | this.streamSource.close();
|
6648 |
|
6649 | disconnectStreamSource(this.streamSource);
|
6650 | }
|
6651 | }
|
6652 |
|
6653 | get src() {
|
6654 | return this.getAttribute("src") || ""
|
6655 | }
|
6656 | }
|
6657 |
|
6658 | FrameElement.delegateConstructor = FrameController;
|
6659 |
|
6660 | if (customElements.get("turbo-frame") === undefined) {
|
6661 | customElements.define("turbo-frame", FrameElement);
|
6662 | }
|
6663 |
|
6664 | if (customElements.get("turbo-stream") === undefined) {
|
6665 | customElements.define("turbo-stream", StreamElement);
|
6666 | }
|
6667 |
|
6668 | if (customElements.get("turbo-stream-source") === undefined) {
|
6669 | customElements.define("turbo-stream-source", StreamSourceElement);
|
6670 | }
|
6671 |
|
6672 | (() => {
|
6673 | let element = document.currentScript;
|
6674 | if (!element) return
|
6675 | if (element.hasAttribute("data-turbo-suppress-warning")) return
|
6676 |
|
6677 | element = element.parentElement;
|
6678 | while (element) {
|
6679 | if (element == document.body) {
|
6680 | return console.warn(
|
6681 | unindent`
|
6682 | You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
6683 |
|
6684 | Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
6685 |
|
6686 | For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements
|
6687 |
|
6688 | ——
|
6689 | Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
6690 | `,
|
6691 | element.outerHTML
|
6692 | )
|
6693 | }
|
6694 |
|
6695 | element = element.parentElement;
|
6696 | }
|
6697 | })();
|
6698 |
|
6699 | window.Turbo = { ...Turbo, StreamActions };
|
6700 | start();
|
6701 |
|
6702 | exports.FetchEnctype = FetchEnctype;
|
6703 | exports.FetchMethod = FetchMethod;
|
6704 | exports.FetchRequest = FetchRequest;
|
6705 | exports.FetchResponse = FetchResponse;
|
6706 | exports.FrameElement = FrameElement;
|
6707 | exports.FrameLoadingStyle = FrameLoadingStyle;
|
6708 | exports.FrameRenderer = FrameRenderer;
|
6709 | exports.PageRenderer = PageRenderer;
|
6710 | exports.PageSnapshot = PageSnapshot;
|
6711 | exports.StreamActions = StreamActions;
|
6712 | exports.StreamElement = StreamElement;
|
6713 | exports.StreamSourceElement = StreamSourceElement;
|
6714 | exports.cache = cache;
|
6715 | exports.clearCache = clearCache;
|
6716 | exports.config = config;
|
6717 | exports.connectStreamSource = connectStreamSource;
|
6718 | exports.disconnectStreamSource = disconnectStreamSource;
|
6719 | exports.fetch = fetchWithTurboHeaders;
|
6720 | exports.fetchEnctypeFromString = fetchEnctypeFromString;
|
6721 | exports.fetchMethodFromString = fetchMethodFromString;
|
6722 | exports.isSafe = isSafe;
|
6723 | exports.navigator = navigator$1;
|
6724 | exports.registerAdapter = registerAdapter;
|
6725 | exports.renderStreamMessage = renderStreamMessage;
|
6726 | exports.session = session;
|
6727 | exports.setConfirmMethod = setConfirmMethod;
|
6728 | exports.setFormMode = setFormMode;
|
6729 | exports.setProgressBarDelay = setProgressBarDelay;
|
6730 | exports.start = start;
|
6731 | exports.visit = visit;
|
6732 |
|
6733 | Object.defineProperty(exports, '__esModule', { value: true });
|
6734 |
|
6735 | }));
|