turbo-rails 1.3.1 → 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: ae9e55565c85d150ae5bcafa265fcf6a626f39a2e5973e84d9f6507337344ad4
4
- data.tar.gz: f20222d0d07c40215260bc9bf27002991aca5093147df738c9e8f9d4414be9f5
3
+ metadata.gz: ecb315ac4b0462119b892a555ba5942ac1de84d8bdc565e31d0e166ac244a6c1
4
+ data.tar.gz: a2e2033e73b26803e9b87962ac451b825257e1f052cb86cef013e0b8eb16bbcd
5
5
  SHA512:
6
- metadata.gz: c1207a896a07408a7b229ac7051a2ad7301469bc7d56dd3b944a83c56e847981af934f2abf975ba90ca273647fbfb2cf5fc8eaffea95109c2c39a8415f8ce103
7
- data.tar.gz: 03fb1eca56d27e8f828d865af85e5caddd6bbccf31f4999194e13105f8aaad067a3c0870ce7a99e3a408e5126a2d814f863ea33fb30160a5f7a07ee2bd983dc3
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
  }
@@ -1883,11 +1908,12 @@ class Visit {
1883
1908
  this.render((async () => {
1884
1909
  this.cacheSnapshot();
1885
1910
  this.performScroll();
1911
+ this.changeHistory();
1886
1912
  this.adapter.visitRendered(this);
1887
1913
  }));
1888
1914
  }
1889
1915
  }
1890
- prepareHeadersForRequest(headers, request) {
1916
+ prepareRequest(request) {
1891
1917
  if (this.acceptsStreamResponse) {
1892
1918
  request.acceptResponseType(StreamMessage.contentType);
1893
1919
  }
@@ -2296,7 +2322,6 @@ class Navigator {
2296
2322
  }
2297
2323
  }
2298
2324
  startVisit(locatable, restorationIdentifier, options = {}) {
2299
- this.lastVisit = this.currentVisit;
2300
2325
  this.stop();
2301
2326
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2302
2327
  referrer: this.location
@@ -2383,12 +2408,10 @@ class Navigator {
2383
2408
  this.delegate.visitCompleted(visit);
2384
2409
  }
2385
2410
  locationWithActionIsSamePage(location, action) {
2386
- var _a;
2387
2411
  const anchor = getAnchor(location);
2388
- const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2389
- const currentAnchor = getAnchor(lastLocation);
2412
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2390
2413
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2391
- return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2414
+ return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2392
2415
  }
2393
2416
  visitScrolledToSamePageLocation(oldURL, newURL) {
2394
2417
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2399,10 +2422,8 @@ class Navigator {
2399
2422
  get restorationIdentifier() {
2400
2423
  return this.history.restorationIdentifier;
2401
2424
  }
2402
- getActionForFormSubmission(formSubmission) {
2403
- const {formElement: formElement, submitter: submitter} = formSubmission;
2404
- const action = getAttribute("data-turbo-action", submitter, formElement);
2405
- return isAction(action) ? action : "advance";
2425
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2426
+ return getVisitAction(submitter, formElement) || "advance";
2406
2427
  }
2407
2428
  }
2408
2429
 
@@ -2650,7 +2671,7 @@ class PageRenderer extends Renderer {
2650
2671
  }
2651
2672
  async render() {
2652
2673
  if (this.willRender) {
2653
- this.replaceBody();
2674
+ await this.replaceBody();
2654
2675
  }
2655
2676
  }
2656
2677
  finishRendering() {
@@ -2669,16 +2690,16 @@ class PageRenderer extends Renderer {
2669
2690
  return this.newSnapshot.element;
2670
2691
  }
2671
2692
  async mergeHead() {
2693
+ const mergedHeadElements = this.mergeProvisionalElements();
2672
2694
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2673
2695
  this.copyNewHeadScriptElements();
2674
- this.removeCurrentHeadProvisionalElements();
2675
- this.copyNewHeadProvisionalElements();
2696
+ await mergedHeadElements;
2676
2697
  await newStylesheetElements;
2677
2698
  }
2678
- replaceBody() {
2679
- this.preservingPermanentElements((() => {
2699
+ async replaceBody() {
2700
+ await this.preservingPermanentElements((async () => {
2680
2701
  this.activateNewBody();
2681
- this.assignNewBody();
2702
+ await this.assignNewBody();
2682
2703
  }));
2683
2704
  }
2684
2705
  get trackedElementsAreIdentical() {
@@ -2697,6 +2718,35 @@ class PageRenderer extends Renderer {
2697
2718
  document.head.appendChild(activateScriptElement(element));
2698
2719
  }
2699
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
+ }
2700
2750
  removeCurrentHeadProvisionalElements() {
2701
2751
  for (const element of this.currentHeadProvisionalElements) {
2702
2752
  document.head.removeChild(element);
@@ -2717,8 +2767,8 @@ class PageRenderer extends Renderer {
2717
2767
  inertScriptElement.replaceWith(activatedScriptElement);
2718
2768
  }
2719
2769
  }
2720
- assignNewBody() {
2721
- this.renderElement(this.currentElement, this.newElement);
2770
+ async assignNewBody() {
2771
+ await this.renderElement(this.currentElement, this.newElement);
2722
2772
  }
2723
2773
  get newHeadStylesheetElements() {
2724
2774
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3152,8 +3202,8 @@ class Session {
3152
3202
  }
3153
3203
  }
3154
3204
  elementIsNavigatable(element) {
3155
- const container = element.closest("[data-turbo]");
3156
- const withinFrame = element.closest("turbo-frame");
3205
+ const container = findClosestRecursively(element, "[data-turbo]");
3206
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3157
3207
  if (this.drive || withinFrame) {
3158
3208
  if (container) {
3159
3209
  return container.getAttribute("data-turbo") != "false";
@@ -3169,8 +3219,7 @@ class Session {
3169
3219
  }
3170
3220
  }
3171
3221
  getActionForLink(link) {
3172
- const action = link.getAttribute("data-turbo-action");
3173
- return isAction(action) ? action : "advance";
3222
+ return getVisitAction(link) || "advance";
3174
3223
  }
3175
3224
  get snapshot() {
3176
3225
  return this.view.snapshot;
@@ -3238,7 +3287,10 @@ const StreamActions = {
3238
3287
  this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3239
3288
  },
3240
3289
  update() {
3241
- this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3290
+ this.targetElements.forEach((targetElement => {
3291
+ targetElement.innerHTML = "";
3292
+ targetElement.append(this.templateContent);
3293
+ }));
3242
3294
  }
3243
3295
  };
3244
3296
 
@@ -3430,7 +3482,8 @@ class FrameController {
3430
3482
  this.fetchResponseLoaded = () => {};
3431
3483
  }
3432
3484
  }
3433
- elementAppearedInViewport(_element) {
3485
+ elementAppearedInViewport(element) {
3486
+ this.proposeVisitIfNavigatedWithAction(element, element);
3434
3487
  this.loadSourceURL();
3435
3488
  }
3436
3489
  willSubmitFormLinkToLocation(link) {
@@ -3455,12 +3508,12 @@ class FrameController {
3455
3508
  }
3456
3509
  this.formSubmission = new FormSubmission(this, element, submitter);
3457
3510
  const {fetchRequest: fetchRequest} = this.formSubmission;
3458
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3511
+ this.prepareRequest(fetchRequest);
3459
3512
  this.formSubmission.start();
3460
3513
  }
3461
- prepareHeadersForRequest(headers, request) {
3514
+ prepareRequest(request) {
3462
3515
  var _a;
3463
- headers["Turbo-Frame"] = this.id;
3516
+ request.headers["Turbo-Frame"] = this.id;
3464
3517
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3465
3518
  request.acceptResponseType(StreamMessage.contentType);
3466
3519
  }
@@ -3542,7 +3595,6 @@ class FrameController {
3542
3595
  }
3543
3596
  navigateFrame(element, url, submitter) {
3544
3597
  const frame = this.findFrameElement(element, submitter);
3545
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3546
3598
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3547
3599
  this.withCurrentNavigationElement(element, (() => {
3548
3600
  frame.src = url;
@@ -3550,7 +3602,8 @@ class FrameController {
3550
3602
  }
3551
3603
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3552
3604
  this.action = getVisitAction(submitter, element, frame);
3553
- if (isAction(this.action)) {
3605
+ if (this.action) {
3606
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3554
3607
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3555
3608
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3556
3609
  if (frame.src) {
@@ -3567,7 +3620,7 @@ class FrameController {
3567
3620
  willRender: false,
3568
3621
  updateHistory: false,
3569
3622
  restorationIdentifier: this.restorationIdentifier,
3570
- snapshot: this.pageSnapshot
3623
+ snapshot: pageSnapshot
3571
3624
  };
3572
3625
  if (this.action) options.action = this.action;
3573
3626
  session.visit(frame.src, options);
@@ -4021,18 +4074,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
4021
4074
  }
4022
4075
  }
4023
4076
 
4024
- 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
+ }
4025
4080
 
4026
4081
  function encodeMethodIntoRequestBody(event) {
4027
4082
  if (event.target instanceof HTMLFormElement) {
4028
4083
  const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4029
4084
  form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4030
- 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);
4031
4087
  if (!/get/i.test(method)) {
4032
4088
  if (/post/i.test(method)) {
4033
- fetchOptions.body.delete("_method");
4089
+ body.delete("_method");
4034
4090
  } else {
4035
- fetchOptions.body.set("_method", method);
4091
+ body.set("_method", method);
4036
4092
  }
4037
4093
  fetchOptions.method = "post";
4038
4094
  }
@@ -4042,6 +4098,35 @@ function encodeMethodIntoRequestBody(event) {
4042
4098
  }
4043
4099
  }
4044
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
+
4045
4130
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4046
4131
 
4047
4132
  var adapters = {