turbo-rails 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
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 = {