@hotwired/turbo 8.0.4 → 8.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/turbo.es2017-esm.js +162 -138
- package/dist/turbo.es2017-umd.js +162 -138
- package/package.json +1 -1
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.
|
|
2
|
+
Turbo 8.0.5
|
|
3
3
|
Copyright © 2024 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -631,14 +631,18 @@ async function around(callback, reader) {
|
|
|
631
631
|
return [before, after]
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
function doesNotTargetIFrame(
|
|
635
|
-
if (
|
|
636
|
-
|
|
634
|
+
function doesNotTargetIFrame(name) {
|
|
635
|
+
if (name === "_blank") {
|
|
636
|
+
return false
|
|
637
|
+
} else if (name) {
|
|
638
|
+
for (const element of document.getElementsByName(name)) {
|
|
637
639
|
if (element instanceof HTMLIFrameElement) return false
|
|
638
640
|
}
|
|
639
|
-
}
|
|
640
641
|
|
|
641
|
-
|
|
642
|
+
return true
|
|
643
|
+
} else {
|
|
644
|
+
return true
|
|
645
|
+
}
|
|
642
646
|
}
|
|
643
647
|
|
|
644
648
|
function findLinkFromClickTarget(target) {
|
|
@@ -744,7 +748,7 @@ class FetchRequest {
|
|
|
744
748
|
this.fetchOptions = {
|
|
745
749
|
credentials: "same-origin",
|
|
746
750
|
redirect: "follow",
|
|
747
|
-
method: method,
|
|
751
|
+
method: method.toUpperCase(),
|
|
748
752
|
headers: { ...this.defaultHeaders },
|
|
749
753
|
body: body,
|
|
750
754
|
signal: this.abortSignal,
|
|
@@ -767,7 +771,7 @@ class FetchRequest {
|
|
|
767
771
|
|
|
768
772
|
this.url = url;
|
|
769
773
|
this.fetchOptions.body = body;
|
|
770
|
-
this.fetchOptions.method = fetchMethod;
|
|
774
|
+
this.fetchOptions.method = fetchMethod.toUpperCase();
|
|
771
775
|
}
|
|
772
776
|
|
|
773
777
|
get headers() {
|
|
@@ -1398,17 +1402,9 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
|
1398
1402
|
}
|
|
1399
1403
|
|
|
1400
1404
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
1401
|
-
|
|
1402
|
-
const target = submitter?.getAttribute("formtarget") || form.target;
|
|
1403
|
-
|
|
1404
|
-
for (const element of document.getElementsByName(target)) {
|
|
1405
|
-
if (element instanceof HTMLIFrameElement) return false
|
|
1406
|
-
}
|
|
1405
|
+
const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
|
1407
1406
|
|
|
1408
|
-
|
|
1409
|
-
} else {
|
|
1410
|
-
return true
|
|
1411
|
-
}
|
|
1407
|
+
return doesNotTargetIFrame(target)
|
|
1412
1408
|
}
|
|
1413
1409
|
|
|
1414
1410
|
class View {
|
|
@@ -1561,7 +1557,7 @@ class LinkInterceptor {
|
|
|
1561
1557
|
}
|
|
1562
1558
|
|
|
1563
1559
|
clickBubbled = (event) => {
|
|
1564
|
-
if (this.
|
|
1560
|
+
if (this.clickEventIsSignificant(event)) {
|
|
1565
1561
|
this.clickEvent = event;
|
|
1566
1562
|
} else {
|
|
1567
1563
|
delete this.clickEvent;
|
|
@@ -1569,7 +1565,7 @@ class LinkInterceptor {
|
|
|
1569
1565
|
}
|
|
1570
1566
|
|
|
1571
1567
|
linkClicked = (event) => {
|
|
1572
|
-
if (this.clickEvent && this.
|
|
1568
|
+
if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
|
1573
1569
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
|
1574
1570
|
this.clickEvent.preventDefault();
|
|
1575
1571
|
event.preventDefault();
|
|
@@ -1583,9 +1579,11 @@ class LinkInterceptor {
|
|
|
1583
1579
|
delete this.clickEvent;
|
|
1584
1580
|
}
|
|
1585
1581
|
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1582
|
+
clickEventIsSignificant(event) {
|
|
1583
|
+
const target = event.composed ? event.target?.parentElement : event.target;
|
|
1584
|
+
const element = findLinkFromClickTarget(target) || target;
|
|
1585
|
+
|
|
1586
|
+
return element instanceof Element && element.closest("turbo-frame, html") == this.element
|
|
1589
1587
|
}
|
|
1590
1588
|
}
|
|
1591
1589
|
|
|
@@ -1620,7 +1618,7 @@ class LinkClickObserver {
|
|
|
1620
1618
|
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
|
1621
1619
|
const target = (event.composedPath && event.composedPath()[0]) || event.target;
|
|
1622
1620
|
const link = findLinkFromClickTarget(target);
|
|
1623
|
-
if (link && doesNotTargetIFrame(link)) {
|
|
1621
|
+
if (link && doesNotTargetIFrame(link.target)) {
|
|
1624
1622
|
const location = getLocationForLink(link);
|
|
1625
1623
|
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
1626
1624
|
event.preventDefault();
|
|
@@ -1789,6 +1787,10 @@ class Renderer {
|
|
|
1789
1787
|
return true
|
|
1790
1788
|
}
|
|
1791
1789
|
|
|
1790
|
+
get shouldAutofocus() {
|
|
1791
|
+
return true
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1792
1794
|
get reloadReason() {
|
|
1793
1795
|
return
|
|
1794
1796
|
}
|
|
@@ -1813,9 +1815,11 @@ class Renderer {
|
|
|
1813
1815
|
}
|
|
1814
1816
|
|
|
1815
1817
|
focusFirstAutofocusableElement() {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
element
|
|
1818
|
+
if (this.shouldAutofocus) {
|
|
1819
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
1820
|
+
if (element) {
|
|
1821
|
+
element.focus();
|
|
1822
|
+
}
|
|
1819
1823
|
}
|
|
1820
1824
|
}
|
|
1821
1825
|
|
|
@@ -3166,7 +3170,7 @@ class LinkPrefetchObserver {
|
|
|
3166
3170
|
}
|
|
3167
3171
|
|
|
3168
3172
|
#tryToUsePrefetchedRequest = (event) => {
|
|
3169
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "
|
|
3173
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
3170
3174
|
const cached = prefetchCache.get(event.detail.url.toString());
|
|
3171
3175
|
|
|
3172
3176
|
if (cached) {
|
|
@@ -3383,6 +3387,7 @@ class Navigator {
|
|
|
3383
3387
|
|
|
3384
3388
|
visitCompleted(visit) {
|
|
3385
3389
|
this.delegate.visitCompleted(visit);
|
|
3390
|
+
delete this.currentVisit;
|
|
3386
3391
|
}
|
|
3387
3392
|
|
|
3388
3393
|
locationWithActionIsSamePage(location, action) {
|
|
@@ -4565,6 +4570,81 @@ var Idiomorph = (function () {
|
|
|
4565
4570
|
}
|
|
4566
4571
|
})();
|
|
4567
4572
|
|
|
4573
|
+
function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
|
|
4574
|
+
Idiomorph.morph(currentElement, newElement, {
|
|
4575
|
+
...options,
|
|
4576
|
+
callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
|
4577
|
+
});
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
function morphChildren(currentElement, newElement) {
|
|
4581
|
+
morphElements(currentElement, newElement.children, {
|
|
4582
|
+
morphStyle: "innerHTML"
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
class DefaultIdiomorphCallbacks {
|
|
4587
|
+
#beforeNodeMorphed
|
|
4588
|
+
|
|
4589
|
+
constructor({ beforeNodeMorphed } = {}) {
|
|
4590
|
+
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
beforeNodeAdded = (node) => {
|
|
4594
|
+
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
beforeNodeMorphed = (currentElement, newElement) => {
|
|
4598
|
+
if (currentElement instanceof Element) {
|
|
4599
|
+
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
|
4600
|
+
const event = dispatch("turbo:before-morph-element", {
|
|
4601
|
+
cancelable: true,
|
|
4602
|
+
target: currentElement,
|
|
4603
|
+
detail: { currentElement, newElement }
|
|
4604
|
+
});
|
|
4605
|
+
|
|
4606
|
+
return !event.defaultPrevented
|
|
4607
|
+
} else {
|
|
4608
|
+
return false
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
beforeAttributeUpdated = (attributeName, target, mutationType) => {
|
|
4614
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
|
4615
|
+
cancelable: true,
|
|
4616
|
+
target,
|
|
4617
|
+
detail: { attributeName, mutationType }
|
|
4618
|
+
});
|
|
4619
|
+
|
|
4620
|
+
return !event.defaultPrevented
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
beforeNodeRemoved = (node) => {
|
|
4624
|
+
return this.beforeNodeMorphed(node)
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
afterNodeMorphed = (currentElement, newElement) => {
|
|
4628
|
+
if (currentElement instanceof Element) {
|
|
4629
|
+
dispatch("turbo:morph-element", {
|
|
4630
|
+
target: currentElement,
|
|
4631
|
+
detail: { currentElement, newElement }
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
class MorphingFrameRenderer extends FrameRenderer {
|
|
4638
|
+
static renderElement(currentElement, newElement) {
|
|
4639
|
+
dispatch("turbo:before-frame-morph", {
|
|
4640
|
+
target: currentElement,
|
|
4641
|
+
detail: { currentElement, newElement }
|
|
4642
|
+
});
|
|
4643
|
+
|
|
4644
|
+
morphChildren(currentElement, newElement);
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
|
|
4568
4648
|
class PageRenderer extends Renderer {
|
|
4569
4649
|
static renderElement(currentElement, newElement) {
|
|
4570
4650
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
@@ -4777,119 +4857,47 @@ class PageRenderer extends Renderer {
|
|
|
4777
4857
|
}
|
|
4778
4858
|
}
|
|
4779
4859
|
|
|
4780
|
-
class
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
}
|
|
4784
|
-
|
|
4785
|
-
get renderMethod() {
|
|
4786
|
-
return "morph"
|
|
4787
|
-
}
|
|
4788
|
-
|
|
4789
|
-
// Private
|
|
4790
|
-
|
|
4791
|
-
async #morphBody() {
|
|
4792
|
-
this.#morphElements(this.currentElement, this.newElement);
|
|
4793
|
-
this.#reloadRemoteFrames();
|
|
4794
|
-
|
|
4795
|
-
dispatch("turbo:morph", {
|
|
4796
|
-
detail: {
|
|
4797
|
-
currentElement: this.currentElement,
|
|
4798
|
-
newElement: this.newElement
|
|
4799
|
-
}
|
|
4800
|
-
});
|
|
4801
|
-
}
|
|
4802
|
-
|
|
4803
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4804
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4805
|
-
|
|
4806
|
-
Idiomorph.morph(currentElement, newElement, {
|
|
4807
|
-
morphStyle: morphStyle,
|
|
4860
|
+
class MorphingPageRenderer extends PageRenderer {
|
|
4861
|
+
static renderElement(currentElement, newElement) {
|
|
4862
|
+
morphElements(currentElement, newElement, {
|
|
4808
4863
|
callbacks: {
|
|
4809
|
-
|
|
4810
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4811
|
-
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
|
4812
|
-
beforeNodeRemoved: this.#shouldRemoveElement,
|
|
4813
|
-
afterNodeMorphed: this.#didMorphElement
|
|
4864
|
+
beforeNodeMorphed: element => !canRefreshFrame(element)
|
|
4814
4865
|
}
|
|
4815
4866
|
});
|
|
4816
|
-
}
|
|
4817
4867
|
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
}
|
|
4821
|
-
|
|
4822
|
-
#shouldMorphElement = (oldNode, newNode) => {
|
|
4823
|
-
if (oldNode instanceof HTMLElement) {
|
|
4824
|
-
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
|
4825
|
-
const event = dispatch("turbo:before-morph-element", {
|
|
4826
|
-
cancelable: true,
|
|
4827
|
-
target: oldNode,
|
|
4828
|
-
detail: {
|
|
4829
|
-
newElement: newNode
|
|
4830
|
-
}
|
|
4831
|
-
});
|
|
4832
|
-
|
|
4833
|
-
return !event.defaultPrevented
|
|
4834
|
-
} else {
|
|
4835
|
-
return false
|
|
4836
|
-
}
|
|
4868
|
+
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
|
4869
|
+
if (canRefreshFrame(frame)) refreshFrame(frame);
|
|
4837
4870
|
}
|
|
4838
|
-
}
|
|
4839
|
-
|
|
4840
|
-
#shouldUpdateAttribute = (attributeName, target, mutationType) => {
|
|
4841
|
-
const event = dispatch("turbo:before-morph-attribute", { cancelable: true, target, detail: { attributeName, mutationType } });
|
|
4842
4871
|
|
|
4843
|
-
|
|
4872
|
+
dispatch("turbo:morph", { detail: { currentElement, newElement } });
|
|
4844
4873
|
}
|
|
4845
4874
|
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
dispatch("turbo:morph-element", {
|
|
4849
|
-
target: oldNode,
|
|
4850
|
-
detail: {
|
|
4851
|
-
newElement: newNode
|
|
4852
|
-
}
|
|
4853
|
-
});
|
|
4854
|
-
}
|
|
4855
|
-
}
|
|
4856
|
-
|
|
4857
|
-
#shouldRemoveElement = (node) => {
|
|
4858
|
-
return this.#shouldMorphElement(node)
|
|
4875
|
+
async preservingPermanentElements(callback) {
|
|
4876
|
+
return await callback()
|
|
4859
4877
|
}
|
|
4860
4878
|
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4864
|
-
this.#renderFrameWithMorph(frame);
|
|
4865
|
-
frame.reload();
|
|
4866
|
-
}
|
|
4867
|
-
});
|
|
4879
|
+
get renderMethod() {
|
|
4880
|
+
return "morph"
|
|
4868
4881
|
}
|
|
4869
4882
|
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
event.detail.render = this.#morphFrameUpdate;
|
|
4873
|
-
}, { once: true });
|
|
4883
|
+
get shouldAutofocus() {
|
|
4884
|
+
return false
|
|
4874
4885
|
}
|
|
4886
|
+
}
|
|
4875
4887
|
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
}
|
|
4888
|
+
function canRefreshFrame(frame) {
|
|
4889
|
+
return frame instanceof FrameElement &&
|
|
4890
|
+
frame.src &&
|
|
4891
|
+
frame.refresh === "morph" &&
|
|
4892
|
+
!frame.closest("[data-turbo-permanent]")
|
|
4893
|
+
}
|
|
4883
4894
|
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4895
|
+
function refreshFrame(frame) {
|
|
4896
|
+
frame.addEventListener("turbo:before-frame-render", ({ detail }) => {
|
|
4897
|
+
detail.render = MorphingFrameRenderer.renderElement;
|
|
4898
|
+
}, { once: true });
|
|
4887
4899
|
|
|
4888
|
-
|
|
4889
|
-
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4890
|
-
return !frame.closest('[data-turbo-permanent]')
|
|
4891
|
-
})
|
|
4892
|
-
}
|
|
4900
|
+
frame.reload();
|
|
4893
4901
|
}
|
|
4894
4902
|
|
|
4895
4903
|
class SnapshotCache {
|
|
@@ -4958,9 +4966,9 @@ class PageView extends View {
|
|
|
4958
4966
|
|
|
4959
4967
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
4960
4968
|
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
|
4961
|
-
const rendererClass = shouldMorphPage ?
|
|
4969
|
+
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
4962
4970
|
|
|
4963
|
-
const renderer = new rendererClass(this.snapshot, snapshot,
|
|
4971
|
+
const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
|
|
4964
4972
|
|
|
4965
4973
|
if (!renderer.shouldRender) {
|
|
4966
4974
|
this.forceReloaded = true;
|
|
@@ -5196,7 +5204,7 @@ class Session {
|
|
|
5196
5204
|
|
|
5197
5205
|
refresh(url, requestId) {
|
|
5198
5206
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
5199
|
-
if (!isRecentRequest) {
|
|
5207
|
+
if (!isRecentRequest && !this.navigator.currentVisit) {
|
|
5200
5208
|
this.visit(url, { action: "replace", shouldCacheSnapshot: false });
|
|
5201
5209
|
}
|
|
5202
5210
|
}
|
|
@@ -6286,13 +6294,27 @@ const StreamActions = {
|
|
|
6286
6294
|
},
|
|
6287
6295
|
|
|
6288
6296
|
replace() {
|
|
6289
|
-
|
|
6297
|
+
const method = this.getAttribute("method");
|
|
6298
|
+
|
|
6299
|
+
this.targetElements.forEach((targetElement) => {
|
|
6300
|
+
if (method === "morph") {
|
|
6301
|
+
morphElements(targetElement, this.templateContent);
|
|
6302
|
+
} else {
|
|
6303
|
+
targetElement.replaceWith(this.templateContent);
|
|
6304
|
+
}
|
|
6305
|
+
});
|
|
6290
6306
|
},
|
|
6291
6307
|
|
|
6292
6308
|
update() {
|
|
6309
|
+
const method = this.getAttribute("method");
|
|
6310
|
+
|
|
6293
6311
|
this.targetElements.forEach((targetElement) => {
|
|
6294
|
-
|
|
6295
|
-
|
|
6312
|
+
if (method === "morph") {
|
|
6313
|
+
morphChildren(targetElement, this.templateContent);
|
|
6314
|
+
} else {
|
|
6315
|
+
targetElement.innerHTML = "";
|
|
6316
|
+
targetElement.append(this.templateContent);
|
|
6317
|
+
}
|
|
6296
6318
|
});
|
|
6297
6319
|
},
|
|
6298
6320
|
|
|
@@ -6306,20 +6328,22 @@ const StreamActions = {
|
|
|
6306
6328
|
/**
|
|
6307
6329
|
* Renders updates to the page from a stream of messages.
|
|
6308
6330
|
*
|
|
6309
|
-
* Using the `action` attribute, this can be configured one of
|
|
6331
|
+
* Using the `action` attribute, this can be configured one of eight ways:
|
|
6310
6332
|
*
|
|
6311
|
-
* - `append` - appends the result to the container
|
|
6312
|
-
* - `prepend` - prepends the result to the container
|
|
6313
|
-
* - `replace` - replaces the contents of the container
|
|
6314
|
-
* - `remove` - removes the container
|
|
6315
|
-
* - `before` - inserts the result before the target
|
|
6316
6333
|
* - `after` - inserts the result after the target
|
|
6334
|
+
* - `append` - appends the result to the target
|
|
6335
|
+
* - `before` - inserts the result before the target
|
|
6336
|
+
* - `prepend` - prepends the result to the target
|
|
6337
|
+
* - `refresh` - initiates a page refresh
|
|
6338
|
+
* - `remove` - removes the target
|
|
6339
|
+
* - `replace` - replaces the outer HTML of the target
|
|
6340
|
+
* - `update` - replaces the inner HTML of the target
|
|
6317
6341
|
*
|
|
6318
6342
|
* @customElement turbo-stream
|
|
6319
6343
|
* @example
|
|
6320
6344
|
* <turbo-stream action="append" target="dom_id">
|
|
6321
6345
|
* <template>
|
|
6322
|
-
* Content to append to
|
|
6346
|
+
* Content to append to target designated with the dom_id.
|
|
6323
6347
|
* </template>
|
|
6324
6348
|
* </turbo-stream>
|
|
6325
6349
|
*/
|
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.
|
|
2
|
+
Turbo 8.0.5
|
|
3
3
|
Copyright © 2024 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -637,14 +637,18 @@ Copyright © 2024 37signals LLC
|
|
|
637
637
|
return [before, after]
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
-
function doesNotTargetIFrame(
|
|
641
|
-
if (
|
|
642
|
-
|
|
640
|
+
function doesNotTargetIFrame(name) {
|
|
641
|
+
if (name === "_blank") {
|
|
642
|
+
return false
|
|
643
|
+
} else if (name) {
|
|
644
|
+
for (const element of document.getElementsByName(name)) {
|
|
643
645
|
if (element instanceof HTMLIFrameElement) return false
|
|
644
646
|
}
|
|
645
|
-
}
|
|
646
647
|
|
|
647
|
-
|
|
648
|
+
return true
|
|
649
|
+
} else {
|
|
650
|
+
return true
|
|
651
|
+
}
|
|
648
652
|
}
|
|
649
653
|
|
|
650
654
|
function findLinkFromClickTarget(target) {
|
|
@@ -750,7 +754,7 @@ Copyright © 2024 37signals LLC
|
|
|
750
754
|
this.fetchOptions = {
|
|
751
755
|
credentials: "same-origin",
|
|
752
756
|
redirect: "follow",
|
|
753
|
-
method: method,
|
|
757
|
+
method: method.toUpperCase(),
|
|
754
758
|
headers: { ...this.defaultHeaders },
|
|
755
759
|
body: body,
|
|
756
760
|
signal: this.abortSignal,
|
|
@@ -773,7 +777,7 @@ Copyright © 2024 37signals LLC
|
|
|
773
777
|
|
|
774
778
|
this.url = url;
|
|
775
779
|
this.fetchOptions.body = body;
|
|
776
|
-
this.fetchOptions.method = fetchMethod;
|
|
780
|
+
this.fetchOptions.method = fetchMethod.toUpperCase();
|
|
777
781
|
}
|
|
778
782
|
|
|
779
783
|
get headers() {
|
|
@@ -1404,17 +1408,9 @@ Copyright © 2024 37signals LLC
|
|
|
1404
1408
|
}
|
|
1405
1409
|
|
|
1406
1410
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
1407
|
-
|
|
1408
|
-
const target = submitter?.getAttribute("formtarget") || form.target;
|
|
1409
|
-
|
|
1410
|
-
for (const element of document.getElementsByName(target)) {
|
|
1411
|
-
if (element instanceof HTMLIFrameElement) return false
|
|
1412
|
-
}
|
|
1411
|
+
const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
|
1413
1412
|
|
|
1414
|
-
|
|
1415
|
-
} else {
|
|
1416
|
-
return true
|
|
1417
|
-
}
|
|
1413
|
+
return doesNotTargetIFrame(target)
|
|
1418
1414
|
}
|
|
1419
1415
|
|
|
1420
1416
|
class View {
|
|
@@ -1567,7 +1563,7 @@ Copyright © 2024 37signals LLC
|
|
|
1567
1563
|
}
|
|
1568
1564
|
|
|
1569
1565
|
clickBubbled = (event) => {
|
|
1570
|
-
if (this.
|
|
1566
|
+
if (this.clickEventIsSignificant(event)) {
|
|
1571
1567
|
this.clickEvent = event;
|
|
1572
1568
|
} else {
|
|
1573
1569
|
delete this.clickEvent;
|
|
@@ -1575,7 +1571,7 @@ Copyright © 2024 37signals LLC
|
|
|
1575
1571
|
}
|
|
1576
1572
|
|
|
1577
1573
|
linkClicked = (event) => {
|
|
1578
|
-
if (this.clickEvent && this.
|
|
1574
|
+
if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
|
1579
1575
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
|
1580
1576
|
this.clickEvent.preventDefault();
|
|
1581
1577
|
event.preventDefault();
|
|
@@ -1589,9 +1585,11 @@ Copyright © 2024 37signals LLC
|
|
|
1589
1585
|
delete this.clickEvent;
|
|
1590
1586
|
}
|
|
1591
1587
|
|
|
1592
|
-
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1588
|
+
clickEventIsSignificant(event) {
|
|
1589
|
+
const target = event.composed ? event.target?.parentElement : event.target;
|
|
1590
|
+
const element = findLinkFromClickTarget(target) || target;
|
|
1591
|
+
|
|
1592
|
+
return element instanceof Element && element.closest("turbo-frame, html") == this.element
|
|
1595
1593
|
}
|
|
1596
1594
|
}
|
|
1597
1595
|
|
|
@@ -1626,7 +1624,7 @@ Copyright © 2024 37signals LLC
|
|
|
1626
1624
|
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
|
1627
1625
|
const target = (event.composedPath && event.composedPath()[0]) || event.target;
|
|
1628
1626
|
const link = findLinkFromClickTarget(target);
|
|
1629
|
-
if (link && doesNotTargetIFrame(link)) {
|
|
1627
|
+
if (link && doesNotTargetIFrame(link.target)) {
|
|
1630
1628
|
const location = getLocationForLink(link);
|
|
1631
1629
|
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
1632
1630
|
event.preventDefault();
|
|
@@ -1795,6 +1793,10 @@ Copyright © 2024 37signals LLC
|
|
|
1795
1793
|
return true
|
|
1796
1794
|
}
|
|
1797
1795
|
|
|
1796
|
+
get shouldAutofocus() {
|
|
1797
|
+
return true
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1798
1800
|
get reloadReason() {
|
|
1799
1801
|
return
|
|
1800
1802
|
}
|
|
@@ -1819,9 +1821,11 @@ Copyright © 2024 37signals LLC
|
|
|
1819
1821
|
}
|
|
1820
1822
|
|
|
1821
1823
|
focusFirstAutofocusableElement() {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
element
|
|
1824
|
+
if (this.shouldAutofocus) {
|
|
1825
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
1826
|
+
if (element) {
|
|
1827
|
+
element.focus();
|
|
1828
|
+
}
|
|
1825
1829
|
}
|
|
1826
1830
|
}
|
|
1827
1831
|
|
|
@@ -3172,7 +3176,7 @@ Copyright © 2024 37signals LLC
|
|
|
3172
3176
|
}
|
|
3173
3177
|
|
|
3174
3178
|
#tryToUsePrefetchedRequest = (event) => {
|
|
3175
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "
|
|
3179
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
3176
3180
|
const cached = prefetchCache.get(event.detail.url.toString());
|
|
3177
3181
|
|
|
3178
3182
|
if (cached) {
|
|
@@ -3389,6 +3393,7 @@ Copyright © 2024 37signals LLC
|
|
|
3389
3393
|
|
|
3390
3394
|
visitCompleted(visit) {
|
|
3391
3395
|
this.delegate.visitCompleted(visit);
|
|
3396
|
+
delete this.currentVisit;
|
|
3392
3397
|
}
|
|
3393
3398
|
|
|
3394
3399
|
locationWithActionIsSamePage(location, action) {
|
|
@@ -4571,6 +4576,81 @@ Copyright © 2024 37signals LLC
|
|
|
4571
4576
|
}
|
|
4572
4577
|
})();
|
|
4573
4578
|
|
|
4579
|
+
function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
|
|
4580
|
+
Idiomorph.morph(currentElement, newElement, {
|
|
4581
|
+
...options,
|
|
4582
|
+
callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
function morphChildren(currentElement, newElement) {
|
|
4587
|
+
morphElements(currentElement, newElement.children, {
|
|
4588
|
+
morphStyle: "innerHTML"
|
|
4589
|
+
});
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
class DefaultIdiomorphCallbacks {
|
|
4593
|
+
#beforeNodeMorphed
|
|
4594
|
+
|
|
4595
|
+
constructor({ beforeNodeMorphed } = {}) {
|
|
4596
|
+
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
beforeNodeAdded = (node) => {
|
|
4600
|
+
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
beforeNodeMorphed = (currentElement, newElement) => {
|
|
4604
|
+
if (currentElement instanceof Element) {
|
|
4605
|
+
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
|
4606
|
+
const event = dispatch("turbo:before-morph-element", {
|
|
4607
|
+
cancelable: true,
|
|
4608
|
+
target: currentElement,
|
|
4609
|
+
detail: { currentElement, newElement }
|
|
4610
|
+
});
|
|
4611
|
+
|
|
4612
|
+
return !event.defaultPrevented
|
|
4613
|
+
} else {
|
|
4614
|
+
return false
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4619
|
+
beforeAttributeUpdated = (attributeName, target, mutationType) => {
|
|
4620
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
|
4621
|
+
cancelable: true,
|
|
4622
|
+
target,
|
|
4623
|
+
detail: { attributeName, mutationType }
|
|
4624
|
+
});
|
|
4625
|
+
|
|
4626
|
+
return !event.defaultPrevented
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
beforeNodeRemoved = (node) => {
|
|
4630
|
+
return this.beforeNodeMorphed(node)
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
afterNodeMorphed = (currentElement, newElement) => {
|
|
4634
|
+
if (currentElement instanceof Element) {
|
|
4635
|
+
dispatch("turbo:morph-element", {
|
|
4636
|
+
target: currentElement,
|
|
4637
|
+
detail: { currentElement, newElement }
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
class MorphingFrameRenderer extends FrameRenderer {
|
|
4644
|
+
static renderElement(currentElement, newElement) {
|
|
4645
|
+
dispatch("turbo:before-frame-morph", {
|
|
4646
|
+
target: currentElement,
|
|
4647
|
+
detail: { currentElement, newElement }
|
|
4648
|
+
});
|
|
4649
|
+
|
|
4650
|
+
morphChildren(currentElement, newElement);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4574
4654
|
class PageRenderer extends Renderer {
|
|
4575
4655
|
static renderElement(currentElement, newElement) {
|
|
4576
4656
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
@@ -4783,119 +4863,47 @@ Copyright © 2024 37signals LLC
|
|
|
4783
4863
|
}
|
|
4784
4864
|
}
|
|
4785
4865
|
|
|
4786
|
-
class
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
}
|
|
4790
|
-
|
|
4791
|
-
get renderMethod() {
|
|
4792
|
-
return "morph"
|
|
4793
|
-
}
|
|
4794
|
-
|
|
4795
|
-
// Private
|
|
4796
|
-
|
|
4797
|
-
async #morphBody() {
|
|
4798
|
-
this.#morphElements(this.currentElement, this.newElement);
|
|
4799
|
-
this.#reloadRemoteFrames();
|
|
4800
|
-
|
|
4801
|
-
dispatch("turbo:morph", {
|
|
4802
|
-
detail: {
|
|
4803
|
-
currentElement: this.currentElement,
|
|
4804
|
-
newElement: this.newElement
|
|
4805
|
-
}
|
|
4806
|
-
});
|
|
4807
|
-
}
|
|
4808
|
-
|
|
4809
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4810
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4811
|
-
|
|
4812
|
-
Idiomorph.morph(currentElement, newElement, {
|
|
4813
|
-
morphStyle: morphStyle,
|
|
4866
|
+
class MorphingPageRenderer extends PageRenderer {
|
|
4867
|
+
static renderElement(currentElement, newElement) {
|
|
4868
|
+
morphElements(currentElement, newElement, {
|
|
4814
4869
|
callbacks: {
|
|
4815
|
-
|
|
4816
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4817
|
-
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
|
4818
|
-
beforeNodeRemoved: this.#shouldRemoveElement,
|
|
4819
|
-
afterNodeMorphed: this.#didMorphElement
|
|
4870
|
+
beforeNodeMorphed: element => !canRefreshFrame(element)
|
|
4820
4871
|
}
|
|
4821
4872
|
});
|
|
4822
|
-
}
|
|
4823
4873
|
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
}
|
|
4827
|
-
|
|
4828
|
-
#shouldMorphElement = (oldNode, newNode) => {
|
|
4829
|
-
if (oldNode instanceof HTMLElement) {
|
|
4830
|
-
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
|
4831
|
-
const event = dispatch("turbo:before-morph-element", {
|
|
4832
|
-
cancelable: true,
|
|
4833
|
-
target: oldNode,
|
|
4834
|
-
detail: {
|
|
4835
|
-
newElement: newNode
|
|
4836
|
-
}
|
|
4837
|
-
});
|
|
4838
|
-
|
|
4839
|
-
return !event.defaultPrevented
|
|
4840
|
-
} else {
|
|
4841
|
-
return false
|
|
4842
|
-
}
|
|
4874
|
+
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
|
4875
|
+
if (canRefreshFrame(frame)) refreshFrame(frame);
|
|
4843
4876
|
}
|
|
4844
|
-
}
|
|
4845
|
-
|
|
4846
|
-
#shouldUpdateAttribute = (attributeName, target, mutationType) => {
|
|
4847
|
-
const event = dispatch("turbo:before-morph-attribute", { cancelable: true, target, detail: { attributeName, mutationType } });
|
|
4848
4877
|
|
|
4849
|
-
|
|
4878
|
+
dispatch("turbo:morph", { detail: { currentElement, newElement } });
|
|
4850
4879
|
}
|
|
4851
4880
|
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
dispatch("turbo:morph-element", {
|
|
4855
|
-
target: oldNode,
|
|
4856
|
-
detail: {
|
|
4857
|
-
newElement: newNode
|
|
4858
|
-
}
|
|
4859
|
-
});
|
|
4860
|
-
}
|
|
4861
|
-
}
|
|
4862
|
-
|
|
4863
|
-
#shouldRemoveElement = (node) => {
|
|
4864
|
-
return this.#shouldMorphElement(node)
|
|
4881
|
+
async preservingPermanentElements(callback) {
|
|
4882
|
+
return await callback()
|
|
4865
4883
|
}
|
|
4866
4884
|
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4870
|
-
this.#renderFrameWithMorph(frame);
|
|
4871
|
-
frame.reload();
|
|
4872
|
-
}
|
|
4873
|
-
});
|
|
4885
|
+
get renderMethod() {
|
|
4886
|
+
return "morph"
|
|
4874
4887
|
}
|
|
4875
4888
|
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
event.detail.render = this.#morphFrameUpdate;
|
|
4879
|
-
}, { once: true });
|
|
4889
|
+
get shouldAutofocus() {
|
|
4890
|
+
return false
|
|
4880
4891
|
}
|
|
4892
|
+
}
|
|
4881
4893
|
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
}
|
|
4894
|
+
function canRefreshFrame(frame) {
|
|
4895
|
+
return frame instanceof FrameElement &&
|
|
4896
|
+
frame.src &&
|
|
4897
|
+
frame.refresh === "morph" &&
|
|
4898
|
+
!frame.closest("[data-turbo-permanent]")
|
|
4899
|
+
}
|
|
4889
4900
|
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4901
|
+
function refreshFrame(frame) {
|
|
4902
|
+
frame.addEventListener("turbo:before-frame-render", ({ detail }) => {
|
|
4903
|
+
detail.render = MorphingFrameRenderer.renderElement;
|
|
4904
|
+
}, { once: true });
|
|
4893
4905
|
|
|
4894
|
-
|
|
4895
|
-
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4896
|
-
return !frame.closest('[data-turbo-permanent]')
|
|
4897
|
-
})
|
|
4898
|
-
}
|
|
4906
|
+
frame.reload();
|
|
4899
4907
|
}
|
|
4900
4908
|
|
|
4901
4909
|
class SnapshotCache {
|
|
@@ -4964,9 +4972,9 @@ Copyright © 2024 37signals LLC
|
|
|
4964
4972
|
|
|
4965
4973
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
4966
4974
|
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
|
4967
|
-
const rendererClass = shouldMorphPage ?
|
|
4975
|
+
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
4968
4976
|
|
|
4969
|
-
const renderer = new rendererClass(this.snapshot, snapshot,
|
|
4977
|
+
const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
|
|
4970
4978
|
|
|
4971
4979
|
if (!renderer.shouldRender) {
|
|
4972
4980
|
this.forceReloaded = true;
|
|
@@ -5202,7 +5210,7 @@ Copyright © 2024 37signals LLC
|
|
|
5202
5210
|
|
|
5203
5211
|
refresh(url, requestId) {
|
|
5204
5212
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
5205
|
-
if (!isRecentRequest) {
|
|
5213
|
+
if (!isRecentRequest && !this.navigator.currentVisit) {
|
|
5206
5214
|
this.visit(url, { action: "replace", shouldCacheSnapshot: false });
|
|
5207
5215
|
}
|
|
5208
5216
|
}
|
|
@@ -6292,13 +6300,27 @@ Copyright © 2024 37signals LLC
|
|
|
6292
6300
|
},
|
|
6293
6301
|
|
|
6294
6302
|
replace() {
|
|
6295
|
-
|
|
6303
|
+
const method = this.getAttribute("method");
|
|
6304
|
+
|
|
6305
|
+
this.targetElements.forEach((targetElement) => {
|
|
6306
|
+
if (method === "morph") {
|
|
6307
|
+
morphElements(targetElement, this.templateContent);
|
|
6308
|
+
} else {
|
|
6309
|
+
targetElement.replaceWith(this.templateContent);
|
|
6310
|
+
}
|
|
6311
|
+
});
|
|
6296
6312
|
},
|
|
6297
6313
|
|
|
6298
6314
|
update() {
|
|
6315
|
+
const method = this.getAttribute("method");
|
|
6316
|
+
|
|
6299
6317
|
this.targetElements.forEach((targetElement) => {
|
|
6300
|
-
|
|
6301
|
-
|
|
6318
|
+
if (method === "morph") {
|
|
6319
|
+
morphChildren(targetElement, this.templateContent);
|
|
6320
|
+
} else {
|
|
6321
|
+
targetElement.innerHTML = "";
|
|
6322
|
+
targetElement.append(this.templateContent);
|
|
6323
|
+
}
|
|
6302
6324
|
});
|
|
6303
6325
|
},
|
|
6304
6326
|
|
|
@@ -6312,20 +6334,22 @@ Copyright © 2024 37signals LLC
|
|
|
6312
6334
|
/**
|
|
6313
6335
|
* Renders updates to the page from a stream of messages.
|
|
6314
6336
|
*
|
|
6315
|
-
* Using the `action` attribute, this can be configured one of
|
|
6337
|
+
* Using the `action` attribute, this can be configured one of eight ways:
|
|
6316
6338
|
*
|
|
6317
|
-
* - `append` - appends the result to the container
|
|
6318
|
-
* - `prepend` - prepends the result to the container
|
|
6319
|
-
* - `replace` - replaces the contents of the container
|
|
6320
|
-
* - `remove` - removes the container
|
|
6321
|
-
* - `before` - inserts the result before the target
|
|
6322
6339
|
* - `after` - inserts the result after the target
|
|
6340
|
+
* - `append` - appends the result to the target
|
|
6341
|
+
* - `before` - inserts the result before the target
|
|
6342
|
+
* - `prepend` - prepends the result to the target
|
|
6343
|
+
* - `refresh` - initiates a page refresh
|
|
6344
|
+
* - `remove` - removes the target
|
|
6345
|
+
* - `replace` - replaces the outer HTML of the target
|
|
6346
|
+
* - `update` - replaces the inner HTML of the target
|
|
6323
6347
|
*
|
|
6324
6348
|
* @customElement turbo-stream
|
|
6325
6349
|
* @example
|
|
6326
6350
|
* <turbo-stream action="append" target="dom_id">
|
|
6327
6351
|
* <template>
|
|
6328
|
-
* Content to append to
|
|
6352
|
+
* Content to append to target designated with the dom_id.
|
|
6329
6353
|
* </template>
|
|
6330
6354
|
* </turbo-stream>
|
|
6331
6355
|
*/
|
package/package.json
CHANGED