turbo-rails 1.3.2 → 1.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d91b4f1416fb84779360c106d108b2ea6eae46788a9abec0316595ac7d050c5
4
- data.tar.gz: 8a63eef6bae625fcc8260bf343f0e8184b46f84feb56722688ca3d1d5e6825bc
3
+ metadata.gz: ecb315ac4b0462119b892a555ba5942ac1de84d8bdc565e31d0e166ac244a6c1
4
+ data.tar.gz: a2e2033e73b26803e9b87962ac451b825257e1f052cb86cef013e0b8eb16bbcd
5
5
  SHA512:
6
- metadata.gz: 5a63be2834e1cb5253da4fdd3c8bb4d840c39dd277264fb4b9437107f0182b30cc5d3e41b0bead34dec5ad0d355fe84a6e6911ac3438c8aa3a5ad3ba94e919bd
7
- data.tar.gz: a1ba1d13b8baa6a62bb67c22a1c9e5e386f2164ff072029b5821b857eae5e1d6197810c3c14c64f50e976452179bbe3cebb12a16a4b11114fa60d6bc25f480f3
6
+ metadata.gz: d830701cc6b05ceb930ed1d873998849d870f7b25701ccc3d5cede6f5659eb107e175a46465963a23bf1239fd3aeb798f105155047324644446305e1fea34d71
7
+ data.tar.gz: 0bb9370f532eb74a8230320c1aed5ac1d4e5f434873d017454ffe1c005daa124f413ffe1ee11df1cb49f325f85fcd417b9db6bd6909b234d87032d56dcb99284
data/README.md CHANGED
@@ -32,7 +32,7 @@ Turbo reinvents the old HTML technique of frames without any of the drawbacks th
32
32
 
33
33
  It also makes it dead easy to carve a single page into smaller pieces that can all live on their own cache timeline. While the bulk of the page might easily be cached between users, a small personalized toolbar perhaps cannot. With Turbo::Frames, you can designate the toolbar as a frame, which will be **lazy-loaded automatically** by the publicly-cached root page. This means simpler pages, easier caching schemes with fewer dependent keys, and all without needing to write a lick of custom JavaScript.
34
34
 
35
- This gem provides a `turbo_frame_tag` helper to create those frame.
35
+ This gem provides a `turbo_frame_tag` helper to create those frames.
36
36
 
37
37
  For instance:
38
38
  ```erb
@@ -282,10 +282,6 @@ class FetchResponse {
282
282
  }
283
283
  }
284
284
 
285
- function isAction(action) {
286
- return action == "advance" || action == "replace" || action == "restore";
287
- }
288
-
289
285
  function activateScriptElement(element) {
290
286
  if (element.getAttribute("data-turbo-eval") == "false") {
291
287
  return element;
@@ -318,6 +314,7 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
318
314
  const event = new CustomEvent(eventName, {
319
315
  cancelable: cancelable,
320
316
  bubbles: true,
317
+ composed: true,
321
318
  detail: detail
322
319
  });
323
320
  if (target && target.isConnected) {
@@ -431,6 +428,10 @@ function getHistoryMethodForAction(action) {
431
428
  }
432
429
  }
433
430
 
431
+ function isAction(action) {
432
+ return action == "advance" || action == "replace" || action == "restore";
433
+ }
434
+
434
435
  function getVisitAction(...elements) {
435
436
  const action = getAttribute("data-turbo-action", ...elements);
436
437
  return isAction(action) ? action : null;
@@ -456,6 +457,13 @@ function setMetaContent(name, content) {
456
457
  return element;
457
458
  }
458
459
 
460
+ function findClosestRecursively(element, selector) {
461
+ var _a;
462
+ if (element instanceof Element) {
463
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
464
+ }
465
+ }
466
+
459
467
  var FetchMethod;
460
468
 
461
469
  (function(FetchMethod) {
@@ -509,9 +517,8 @@ class FetchRequest {
509
517
  this.abortController.abort();
510
518
  }
511
519
  async perform() {
512
- var _a, _b;
513
520
  const {fetchOptions: fetchOptions} = this;
514
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
521
+ this.delegate.prepareRequest(this);
515
522
  await this.allowRequestToBeIntercepted(fetchOptions);
516
523
  try {
517
524
  this.delegate.requestStarted(this);
@@ -753,11 +760,11 @@ class FormSubmission {
753
760
  return true;
754
761
  }
755
762
  }
756
- prepareHeadersForRequest(headers, request) {
763
+ prepareRequest(request) {
757
764
  if (!request.isIdempotent) {
758
765
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
759
766
  if (token) {
760
- headers["X-CSRF-Token"] = token;
767
+ request.headers["X-CSRF-Token"] = token;
761
768
  }
762
769
  }
763
770
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -960,11 +967,15 @@ function submissionDoesNotDismissDialog(form, submitter) {
960
967
  }
961
968
 
962
969
  function submissionDoesNotTargetIFrame(form, submitter) {
963
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
964
- for (const element of document.getElementsByName(target)) {
965
- if (element instanceof HTMLIFrameElement) return false;
970
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
971
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
972
+ for (const element of document.getElementsByName(target)) {
973
+ if (element instanceof HTMLIFrameElement) return false;
974
+ }
975
+ return true;
976
+ } else {
977
+ return true;
966
978
  }
967
- return true;
968
979
  }
969
980
 
970
981
  class View {
@@ -1153,9 +1164,7 @@ class LinkClickObserver {
1153
1164
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1154
1165
  }
1155
1166
  findLinkFromClickTarget(target) {
1156
- if (target instanceof Element) {
1157
- return target.closest("a[href]:not([target^=_]):not([download])");
1158
- }
1167
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1159
1168
  }
1160
1169
  getLocationForLink(link) {
1161
1170
  return expandURL(link.getAttribute("href") || "");
@@ -1163,10 +1172,14 @@ class LinkClickObserver {
1163
1172
  }
1164
1173
 
1165
1174
  function doesNotTargetIFrame(anchor) {
1166
- for (const element of document.getElementsByName(anchor.target)) {
1167
- if (element instanceof HTMLIFrameElement) return false;
1175
+ if (anchor.hasAttribute("target")) {
1176
+ for (const element of document.getElementsByName(anchor.target)) {
1177
+ if (element instanceof HTMLIFrameElement) return false;
1178
+ }
1179
+ return true;
1180
+ } else {
1181
+ return true;
1168
1182
  }
1169
- return true;
1170
1183
  }
1171
1184
 
1172
1185
  class FormLinkClickObserver {
@@ -1184,16 +1197,26 @@ class FormLinkClickObserver {
1184
1197
  return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1185
1198
  }
1186
1199
  followedLinkToLocation(link, location) {
1187
- const action = location.href;
1188
1200
  const form = document.createElement("form");
1201
+ const type = "hidden";
1202
+ for (const [name, value] of location.searchParams) {
1203
+ form.append(Object.assign(document.createElement("input"), {
1204
+ type: type,
1205
+ name: name,
1206
+ value: value
1207
+ }));
1208
+ }
1209
+ const action = Object.assign(location, {
1210
+ search: ""
1211
+ });
1189
1212
  form.setAttribute("data-turbo", "true");
1190
- form.setAttribute("action", action);
1213
+ form.setAttribute("action", action.href);
1191
1214
  form.setAttribute("hidden", "");
1192
1215
  const method = link.getAttribute("data-turbo-method");
1193
1216
  if (method) form.setAttribute("method", method);
1194
1217
  const turboFrame = link.getAttribute("data-turbo-frame");
1195
1218
  if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1196
- const turboAction = link.getAttribute("data-turbo-action");
1219
+ const turboAction = getVisitAction(link);
1197
1220
  if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1198
1221
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1199
1222
  if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1213,10 +1236,10 @@ class Bardo {
1213
1236
  this.delegate = delegate;
1214
1237
  this.permanentElementMap = permanentElementMap;
1215
1238
  }
1216
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1239
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1217
1240
  const bardo = new this(delegate, permanentElementMap);
1218
1241
  bardo.enter();
1219
- callback();
1242
+ await callback();
1220
1243
  bardo.leave();
1221
1244
  }
1222
1245
  enter() {
@@ -1289,8 +1312,8 @@ class Renderer {
1289
1312
  delete this.resolvingFunctions;
1290
1313
  }
1291
1314
  }
1292
- preservingPermanentElements(callback) {
1293
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1315
+ async preservingPermanentElements(callback) {
1316
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1294
1317
  }
1295
1318
  focusFirstAutofocusableElement() {
1296
1319
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1873,7 +1896,9 @@ class Visit {
1873
1896
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1874
1897
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1875
1898
  action: "replace",
1876
- response: this.response
1899
+ response: this.response,
1900
+ shouldCacheSnapshot: false,
1901
+ willRender: false
1877
1902
  });
1878
1903
  this.followedRedirect = true;
1879
1904
  }
@@ -1888,7 +1913,7 @@ class Visit {
1888
1913
  }));
1889
1914
  }
1890
1915
  }
1891
- prepareHeadersForRequest(headers, request) {
1916
+ prepareRequest(request) {
1892
1917
  if (this.acceptsStreamResponse) {
1893
1918
  request.acceptResponseType(StreamMessage.contentType);
1894
1919
  }
@@ -2397,10 +2422,8 @@ class Navigator {
2397
2422
  get restorationIdentifier() {
2398
2423
  return this.history.restorationIdentifier;
2399
2424
  }
2400
- getActionForFormSubmission(formSubmission) {
2401
- const {formElement: formElement, submitter: submitter} = formSubmission;
2402
- const action = getAttribute("data-turbo-action", submitter, formElement);
2403
- return isAction(action) ? action : "advance";
2425
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2426
+ return getVisitAction(submitter, formElement) || "advance";
2404
2427
  }
2405
2428
  }
2406
2429
 
@@ -2648,7 +2671,7 @@ class PageRenderer extends Renderer {
2648
2671
  }
2649
2672
  async render() {
2650
2673
  if (this.willRender) {
2651
- this.replaceBody();
2674
+ await this.replaceBody();
2652
2675
  }
2653
2676
  }
2654
2677
  finishRendering() {
@@ -2667,16 +2690,16 @@ class PageRenderer extends Renderer {
2667
2690
  return this.newSnapshot.element;
2668
2691
  }
2669
2692
  async mergeHead() {
2693
+ const mergedHeadElements = this.mergeProvisionalElements();
2670
2694
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2671
2695
  this.copyNewHeadScriptElements();
2672
- this.removeCurrentHeadProvisionalElements();
2673
- this.copyNewHeadProvisionalElements();
2696
+ await mergedHeadElements;
2674
2697
  await newStylesheetElements;
2675
2698
  }
2676
- replaceBody() {
2677
- this.preservingPermanentElements((() => {
2699
+ async replaceBody() {
2700
+ await this.preservingPermanentElements((async () => {
2678
2701
  this.activateNewBody();
2679
- this.assignNewBody();
2702
+ await this.assignNewBody();
2680
2703
  }));
2681
2704
  }
2682
2705
  get trackedElementsAreIdentical() {
@@ -2695,6 +2718,35 @@ class PageRenderer extends Renderer {
2695
2718
  document.head.appendChild(activateScriptElement(element));
2696
2719
  }
2697
2720
  }
2721
+ async mergeProvisionalElements() {
2722
+ const newHeadElements = [ ...this.newHeadProvisionalElements ];
2723
+ for (const element of this.currentHeadProvisionalElements) {
2724
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2725
+ document.head.removeChild(element);
2726
+ }
2727
+ }
2728
+ for (const element of newHeadElements) {
2729
+ document.head.appendChild(element);
2730
+ }
2731
+ }
2732
+ isCurrentElementInElementList(element, elementList) {
2733
+ for (const [index, newElement] of elementList.entries()) {
2734
+ if (element.tagName == "TITLE") {
2735
+ if (newElement.tagName != "TITLE") {
2736
+ continue;
2737
+ }
2738
+ if (element.innerHTML == newElement.innerHTML) {
2739
+ elementList.splice(index, 1);
2740
+ return true;
2741
+ }
2742
+ }
2743
+ if (newElement.isEqualNode(element)) {
2744
+ elementList.splice(index, 1);
2745
+ return true;
2746
+ }
2747
+ }
2748
+ return false;
2749
+ }
2698
2750
  removeCurrentHeadProvisionalElements() {
2699
2751
  for (const element of this.currentHeadProvisionalElements) {
2700
2752
  document.head.removeChild(element);
@@ -2715,8 +2767,8 @@ class PageRenderer extends Renderer {
2715
2767
  inertScriptElement.replaceWith(activatedScriptElement);
2716
2768
  }
2717
2769
  }
2718
- assignNewBody() {
2719
- this.renderElement(this.currentElement, this.newElement);
2770
+ async assignNewBody() {
2771
+ await this.renderElement(this.currentElement, this.newElement);
2720
2772
  }
2721
2773
  get newHeadStylesheetElements() {
2722
2774
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3150,8 +3202,8 @@ class Session {
3150
3202
  }
3151
3203
  }
3152
3204
  elementIsNavigatable(element) {
3153
- const container = element.closest("[data-turbo]");
3154
- const withinFrame = element.closest("turbo-frame");
3205
+ const container = findClosestRecursively(element, "[data-turbo]");
3206
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3155
3207
  if (this.drive || withinFrame) {
3156
3208
  if (container) {
3157
3209
  return container.getAttribute("data-turbo") != "false";
@@ -3167,8 +3219,7 @@ class Session {
3167
3219
  }
3168
3220
  }
3169
3221
  getActionForLink(link) {
3170
- const action = link.getAttribute("data-turbo-action");
3171
- return isAction(action) ? action : "advance";
3222
+ return getVisitAction(link) || "advance";
3172
3223
  }
3173
3224
  get snapshot() {
3174
3225
  return this.view.snapshot;
@@ -3236,7 +3287,10 @@ const StreamActions = {
3236
3287
  this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3237
3288
  },
3238
3289
  update() {
3239
- this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3290
+ this.targetElements.forEach((targetElement => {
3291
+ targetElement.innerHTML = "";
3292
+ targetElement.append(this.templateContent);
3293
+ }));
3240
3294
  }
3241
3295
  };
3242
3296
 
@@ -3428,7 +3482,8 @@ class FrameController {
3428
3482
  this.fetchResponseLoaded = () => {};
3429
3483
  }
3430
3484
  }
3431
- elementAppearedInViewport(_element) {
3485
+ elementAppearedInViewport(element) {
3486
+ this.proposeVisitIfNavigatedWithAction(element, element);
3432
3487
  this.loadSourceURL();
3433
3488
  }
3434
3489
  willSubmitFormLinkToLocation(link) {
@@ -3453,12 +3508,12 @@ class FrameController {
3453
3508
  }
3454
3509
  this.formSubmission = new FormSubmission(this, element, submitter);
3455
3510
  const {fetchRequest: fetchRequest} = this.formSubmission;
3456
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3511
+ this.prepareRequest(fetchRequest);
3457
3512
  this.formSubmission.start();
3458
3513
  }
3459
- prepareHeadersForRequest(headers, request) {
3514
+ prepareRequest(request) {
3460
3515
  var _a;
3461
- headers["Turbo-Frame"] = this.id;
3516
+ request.headers["Turbo-Frame"] = this.id;
3462
3517
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3463
3518
  request.acceptResponseType(StreamMessage.contentType);
3464
3519
  }
@@ -3540,7 +3595,6 @@ class FrameController {
3540
3595
  }
3541
3596
  navigateFrame(element, url, submitter) {
3542
3597
  const frame = this.findFrameElement(element, submitter);
3543
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3544
3598
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3545
3599
  this.withCurrentNavigationElement(element, (() => {
3546
3600
  frame.src = url;
@@ -3548,7 +3602,8 @@ class FrameController {
3548
3602
  }
3549
3603
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3550
3604
  this.action = getVisitAction(submitter, element, frame);
3551
- if (isAction(this.action)) {
3605
+ if (this.action) {
3606
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3552
3607
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3553
3608
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3554
3609
  if (frame.src) {
@@ -3565,7 +3620,7 @@ class FrameController {
3565
3620
  willRender: false,
3566
3621
  updateHistory: false,
3567
3622
  restorationIdentifier: this.restorationIdentifier,
3568
- snapshot: this.pageSnapshot
3623
+ snapshot: pageSnapshot
3569
3624
  };
3570
3625
  if (this.action) options.action = this.action;
3571
3626
  session.visit(frame.src, options);
@@ -4019,18 +4074,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
4019
4074
  }
4020
4075
  }
4021
4076
 
4022
- customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4077
+ if (customElements.get("turbo-cable-stream-source") === undefined) {
4078
+ customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4079
+ }
4023
4080
 
4024
4081
  function encodeMethodIntoRequestBody(event) {
4025
4082
  if (event.target instanceof HTMLFormElement) {
4026
4083
  const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4027
4084
  form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4028
- const method = submitter && submitter.formMethod || fetchOptions.body && fetchOptions.body.get("_method") || form.getAttribute("method");
4085
+ const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
4086
+ const method = determineFetchMethod(submitter, body, form);
4029
4087
  if (!/get/i.test(method)) {
4030
4088
  if (/post/i.test(method)) {
4031
- fetchOptions.body.delete("_method");
4089
+ body.delete("_method");
4032
4090
  } else {
4033
- fetchOptions.body.set("_method", method);
4091
+ body.set("_method", method);
4034
4092
  }
4035
4093
  fetchOptions.method = "post";
4036
4094
  }
@@ -4040,6 +4098,35 @@ function encodeMethodIntoRequestBody(event) {
4040
4098
  }
4041
4099
  }
4042
4100
 
4101
+ function determineFetchMethod(submitter, body, form) {
4102
+ const formMethod = determineFormMethod(submitter);
4103
+ const overrideMethod = body.get("_method");
4104
+ const method = form.getAttribute("method") || "get";
4105
+ if (typeof formMethod == "string") {
4106
+ return formMethod;
4107
+ } else if (typeof overrideMethod == "string") {
4108
+ return overrideMethod;
4109
+ } else {
4110
+ return method;
4111
+ }
4112
+ }
4113
+
4114
+ function determineFormMethod(submitter) {
4115
+ if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
4116
+ if (submitter.hasAttribute("formmethod")) {
4117
+ return submitter.formMethod;
4118
+ } else {
4119
+ return null;
4120
+ }
4121
+ } else {
4122
+ return null;
4123
+ }
4124
+ }
4125
+
4126
+ function isBodyInit(body) {
4127
+ return body instanceof FormData || body instanceof URLSearchParams;
4128
+ }
4129
+
4043
4130
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4044
4131
 
4045
4132
  var adapters = {