@hotwired/turbo 7.2.4 → 7.2.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.
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.4
3
- Copyright © 2022 37signals LLC
2
+ Turbo 7.2.5
3
+ Copyright © 2023 37signals LLC
4
4
  */
5
5
  (function () {
6
6
  if (window.Reflect === undefined ||
@@ -307,10 +307,6 @@ class FetchResponse {
307
307
  }
308
308
  }
309
309
 
310
- function isAction(action) {
311
- return action == "advance" || action == "replace" || action == "restore";
312
- }
313
-
314
310
  function activateScriptElement(element) {
315
311
  if (element.getAttribute("data-turbo-eval") == "false") {
316
312
  return element;
@@ -341,6 +337,7 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
341
337
  const event = new CustomEvent(eventName, {
342
338
  cancelable,
343
339
  bubbles: true,
340
+ composed: true,
344
341
  detail,
345
342
  });
346
343
  if (target && target.isConnected) {
@@ -440,6 +437,9 @@ function getHistoryMethodForAction(action) {
440
437
  return history.pushState;
441
438
  }
442
439
  }
440
+ function isAction(action) {
441
+ return action == "advance" || action == "replace" || action == "restore";
442
+ }
443
443
  function getVisitAction(...elements) {
444
444
  const action = getAttribute("data-turbo-action", ...elements);
445
445
  return isAction(action) ? action : null;
@@ -461,6 +461,13 @@ function setMetaContent(name, content) {
461
461
  element.setAttribute("content", content);
462
462
  return element;
463
463
  }
464
+ function findClosestRecursively(element, selector) {
465
+ var _a;
466
+ if (element instanceof Element) {
467
+ return (element.closest(selector) ||
468
+ findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
469
+ }
470
+ }
464
471
 
465
472
  var FetchMethod;
466
473
  (function (FetchMethod) {
@@ -508,9 +515,8 @@ class FetchRequest {
508
515
  this.abortController.abort();
509
516
  }
510
517
  async perform() {
511
- var _a, _b;
512
518
  const { fetchOptions } = this;
513
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
519
+ this.delegate.prepareRequest(this);
514
520
  await this.allowRequestToBeIntercepted(fetchOptions);
515
521
  try {
516
522
  this.delegate.requestStarted(this);
@@ -748,11 +754,11 @@ class FormSubmission {
748
754
  return true;
749
755
  }
750
756
  }
751
- prepareHeadersForRequest(headers, request) {
757
+ prepareRequest(request) {
752
758
  if (!request.isIdempotent) {
753
759
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
754
760
  if (token) {
755
- headers["X-CSRF-Token"] = token;
761
+ request.headers["X-CSRF-Token"] = token;
756
762
  }
757
763
  }
758
764
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -940,12 +946,17 @@ function submissionDoesNotDismissDialog(form, submitter) {
940
946
  return method != "dialog";
941
947
  }
942
948
  function submissionDoesNotTargetIFrame(form, submitter) {
943
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
944
- for (const element of document.getElementsByName(target)) {
945
- if (element instanceof HTMLIFrameElement)
946
- return false;
949
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
950
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
951
+ for (const element of document.getElementsByName(target)) {
952
+ if (element instanceof HTMLIFrameElement)
953
+ return false;
954
+ }
955
+ return true;
956
+ }
957
+ else {
958
+ return true;
947
959
  }
948
- return true;
949
960
  }
950
961
 
951
962
  class View {
@@ -1138,20 +1149,23 @@ class LinkClickObserver {
1138
1149
  event.shiftKey);
1139
1150
  }
1140
1151
  findLinkFromClickTarget(target) {
1141
- if (target instanceof Element) {
1142
- return target.closest("a[href]:not([target^=_]):not([download])");
1143
- }
1152
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1144
1153
  }
1145
1154
  getLocationForLink(link) {
1146
1155
  return expandURL(link.getAttribute("href") || "");
1147
1156
  }
1148
1157
  }
1149
1158
  function doesNotTargetIFrame(anchor) {
1150
- for (const element of document.getElementsByName(anchor.target)) {
1151
- if (element instanceof HTMLIFrameElement)
1152
- return false;
1159
+ if (anchor.hasAttribute("target")) {
1160
+ for (const element of document.getElementsByName(anchor.target)) {
1161
+ if (element instanceof HTMLIFrameElement)
1162
+ return false;
1163
+ }
1164
+ return true;
1165
+ }
1166
+ else {
1167
+ return true;
1153
1168
  }
1154
- return true;
1155
1169
  }
1156
1170
 
1157
1171
  class FormLinkClickObserver {
@@ -1170,10 +1184,14 @@ class FormLinkClickObserver {
1170
1184
  link.hasAttribute("data-turbo-method"));
1171
1185
  }
1172
1186
  followedLinkToLocation(link, location) {
1173
- const action = location.href;
1174
1187
  const form = document.createElement("form");
1188
+ const type = "hidden";
1189
+ for (const [name, value] of location.searchParams) {
1190
+ form.append(Object.assign(document.createElement("input"), { type, name, value }));
1191
+ }
1192
+ const action = Object.assign(location, { search: "" });
1175
1193
  form.setAttribute("data-turbo", "true");
1176
- form.setAttribute("action", action);
1194
+ form.setAttribute("action", action.href);
1177
1195
  form.setAttribute("hidden", "");
1178
1196
  const method = link.getAttribute("data-turbo-method");
1179
1197
  if (method)
@@ -1181,7 +1199,7 @@ class FormLinkClickObserver {
1181
1199
  const turboFrame = link.getAttribute("data-turbo-frame");
1182
1200
  if (turboFrame)
1183
1201
  form.setAttribute("data-turbo-frame", turboFrame);
1184
- const turboAction = link.getAttribute("data-turbo-action");
1202
+ const turboAction = getVisitAction(link);
1185
1203
  if (turboAction)
1186
1204
  form.setAttribute("data-turbo-action", turboAction);
1187
1205
  const turboConfirm = link.getAttribute("data-turbo-confirm");
@@ -1202,10 +1220,10 @@ class Bardo {
1202
1220
  this.delegate = delegate;
1203
1221
  this.permanentElementMap = permanentElementMap;
1204
1222
  }
1205
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1223
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1206
1224
  const bardo = new this(delegate, permanentElementMap);
1207
1225
  bardo.enter();
1208
- callback();
1226
+ await callback();
1209
1227
  bardo.leave();
1210
1228
  }
1211
1229
  enter() {
@@ -1274,8 +1292,8 @@ class Renderer {
1274
1292
  delete this.resolvingFunctions;
1275
1293
  }
1276
1294
  }
1277
- preservingPermanentElements(callback) {
1278
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1295
+ async preservingPermanentElements(callback) {
1296
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1279
1297
  }
1280
1298
  focusFirstAutofocusableElement() {
1281
1299
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1860,6 +1878,8 @@ class Visit {
1860
1878
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1861
1879
  action: "replace",
1862
1880
  response: this.response,
1881
+ shouldCacheSnapshot: false,
1882
+ willRender: false,
1863
1883
  });
1864
1884
  this.followedRedirect = true;
1865
1885
  }
@@ -1874,7 +1894,7 @@ class Visit {
1874
1894
  });
1875
1895
  }
1876
1896
  }
1877
- prepareHeadersForRequest(headers, request) {
1897
+ prepareRequest(request) {
1878
1898
  if (this.acceptsStreamResponse) {
1879
1899
  request.acceptResponseType(StreamMessage.contentType);
1880
1900
  }
@@ -2375,10 +2395,8 @@ class Navigator {
2375
2395
  get restorationIdentifier() {
2376
2396
  return this.history.restorationIdentifier;
2377
2397
  }
2378
- getActionForFormSubmission(formSubmission) {
2379
- const { formElement, submitter } = formSubmission;
2380
- const action = getAttribute("data-turbo-action", submitter, formElement);
2381
- return isAction(action) ? action : "advance";
2398
+ getActionForFormSubmission({ submitter, formElement }) {
2399
+ return getVisitAction(submitter, formElement) || "advance";
2382
2400
  }
2383
2401
  }
2384
2402
 
@@ -2620,7 +2638,7 @@ class PageRenderer extends Renderer {
2620
2638
  }
2621
2639
  async render() {
2622
2640
  if (this.willRender) {
2623
- this.replaceBody();
2641
+ await this.replaceBody();
2624
2642
  }
2625
2643
  }
2626
2644
  finishRendering() {
@@ -2639,16 +2657,16 @@ class PageRenderer extends Renderer {
2639
2657
  return this.newSnapshot.element;
2640
2658
  }
2641
2659
  async mergeHead() {
2660
+ const mergedHeadElements = this.mergeProvisionalElements();
2642
2661
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2643
2662
  this.copyNewHeadScriptElements();
2644
- this.removeCurrentHeadProvisionalElements();
2645
- this.copyNewHeadProvisionalElements();
2663
+ await mergedHeadElements;
2646
2664
  await newStylesheetElements;
2647
2665
  }
2648
- replaceBody() {
2649
- this.preservingPermanentElements(() => {
2666
+ async replaceBody() {
2667
+ await this.preservingPermanentElements(async () => {
2650
2668
  this.activateNewBody();
2651
- this.assignNewBody();
2669
+ await this.assignNewBody();
2652
2670
  });
2653
2671
  }
2654
2672
  get trackedElementsAreIdentical() {
@@ -2667,6 +2685,35 @@ class PageRenderer extends Renderer {
2667
2685
  document.head.appendChild(activateScriptElement(element));
2668
2686
  }
2669
2687
  }
2688
+ async mergeProvisionalElements() {
2689
+ const newHeadElements = [...this.newHeadProvisionalElements];
2690
+ for (const element of this.currentHeadProvisionalElements) {
2691
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2692
+ document.head.removeChild(element);
2693
+ }
2694
+ }
2695
+ for (const element of newHeadElements) {
2696
+ document.head.appendChild(element);
2697
+ }
2698
+ }
2699
+ isCurrentElementInElementList(element, elementList) {
2700
+ for (const [index, newElement] of elementList.entries()) {
2701
+ if (element.tagName == "TITLE") {
2702
+ if (newElement.tagName != "TITLE") {
2703
+ continue;
2704
+ }
2705
+ if (element.innerHTML == newElement.innerHTML) {
2706
+ elementList.splice(index, 1);
2707
+ return true;
2708
+ }
2709
+ }
2710
+ if (newElement.isEqualNode(element)) {
2711
+ elementList.splice(index, 1);
2712
+ return true;
2713
+ }
2714
+ }
2715
+ return false;
2716
+ }
2670
2717
  removeCurrentHeadProvisionalElements() {
2671
2718
  for (const element of this.currentHeadProvisionalElements) {
2672
2719
  document.head.removeChild(element);
@@ -2687,8 +2734,8 @@ class PageRenderer extends Renderer {
2687
2734
  inertScriptElement.replaceWith(activatedScriptElement);
2688
2735
  }
2689
2736
  }
2690
- assignNewBody() {
2691
- this.renderElement(this.currentElement, this.newElement);
2737
+ async assignNewBody() {
2738
+ await this.renderElement(this.currentElement, this.newElement);
2692
2739
  }
2693
2740
  get newHeadStylesheetElements() {
2694
2741
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3105,8 +3152,8 @@ class Session {
3105
3152
  }
3106
3153
  }
3107
3154
  elementIsNavigatable(element) {
3108
- const container = element.closest("[data-turbo]");
3109
- const withinFrame = element.closest("turbo-frame");
3155
+ const container = findClosestRecursively(element, "[data-turbo]");
3156
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3110
3157
  if (this.drive || withinFrame) {
3111
3158
  if (container) {
3112
3159
  return container.getAttribute("data-turbo") != "false";
@@ -3125,8 +3172,7 @@ class Session {
3125
3172
  }
3126
3173
  }
3127
3174
  getActionForLink(link) {
3128
- const action = link.getAttribute("data-turbo-action");
3129
- return isAction(action) ? action : "advance";
3175
+ return getVisitAction(link) || "advance";
3130
3176
  }
3131
3177
  get snapshot() {
3132
3178
  return this.view.snapshot;
@@ -3186,7 +3232,10 @@ const StreamActions = {
3186
3232
  this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3187
3233
  },
3188
3234
  update() {
3189
- this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3235
+ this.targetElements.forEach((targetElement) => {
3236
+ targetElement.innerHTML = "";
3237
+ targetElement.append(this.templateContent);
3238
+ });
3190
3239
  },
3191
3240
  };
3192
3241
 
@@ -3374,7 +3423,8 @@ class FrameController {
3374
3423
  this.fetchResponseLoaded = () => { };
3375
3424
  }
3376
3425
  }
3377
- elementAppearedInViewport(_element) {
3426
+ elementAppearedInViewport(element) {
3427
+ this.proposeVisitIfNavigatedWithAction(element, element);
3378
3428
  this.loadSourceURL();
3379
3429
  }
3380
3430
  willSubmitFormLinkToLocation(link) {
@@ -3400,12 +3450,12 @@ class FrameController {
3400
3450
  }
3401
3451
  this.formSubmission = new FormSubmission(this, element, submitter);
3402
3452
  const { fetchRequest } = this.formSubmission;
3403
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3453
+ this.prepareRequest(fetchRequest);
3404
3454
  this.formSubmission.start();
3405
3455
  }
3406
- prepareHeadersForRequest(headers, request) {
3456
+ prepareRequest(request) {
3407
3457
  var _a;
3408
- headers["Turbo-Frame"] = this.id;
3458
+ request.headers["Turbo-Frame"] = this.id;
3409
3459
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3410
3460
  request.acceptResponseType(StreamMessage.contentType);
3411
3461
  }
@@ -3485,7 +3535,6 @@ class FrameController {
3485
3535
  }
3486
3536
  navigateFrame(element, url, submitter) {
3487
3537
  const frame = this.findFrameElement(element, submitter);
3488
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3489
3538
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3490
3539
  this.withCurrentNavigationElement(element, () => {
3491
3540
  frame.src = url;
@@ -3493,7 +3542,8 @@ class FrameController {
3493
3542
  }
3494
3543
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3495
3544
  this.action = getVisitAction(submitter, element, frame);
3496
- if (isAction(this.action)) {
3545
+ if (this.action) {
3546
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3497
3547
  const { visitCachedSnapshot } = frame.delegate;
3498
3548
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3499
3549
  if (frame.src) {
@@ -3506,7 +3556,7 @@ class FrameController {
3506
3556
  willRender: false,
3507
3557
  updateHistory: false,
3508
3558
  restorationIdentifier: this.restorationIdentifier,
3509
- snapshot: this.pageSnapshot,
3559
+ snapshot: pageSnapshot,
3510
3560
  };
3511
3561
  if (this.action)
3512
3562
  options.action = this.action;
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.4
3
- Copyright © 2022 37signals LLC
2
+ Turbo 7.2.5
3
+ Copyright © 2023 37signals LLC
4
4
  */
5
5
  (function (global, factory) {
6
6
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -313,10 +313,6 @@ Copyright © 2022 37signals LLC
313
313
  }
314
314
  }
315
315
 
316
- function isAction(action) {
317
- return action == "advance" || action == "replace" || action == "restore";
318
- }
319
-
320
316
  function activateScriptElement(element) {
321
317
  if (element.getAttribute("data-turbo-eval") == "false") {
322
318
  return element;
@@ -347,6 +343,7 @@ Copyright © 2022 37signals LLC
347
343
  const event = new CustomEvent(eventName, {
348
344
  cancelable,
349
345
  bubbles: true,
346
+ composed: true,
350
347
  detail,
351
348
  });
352
349
  if (target && target.isConnected) {
@@ -446,6 +443,9 @@ Copyright © 2022 37signals LLC
446
443
  return history.pushState;
447
444
  }
448
445
  }
446
+ function isAction(action) {
447
+ return action == "advance" || action == "replace" || action == "restore";
448
+ }
449
449
  function getVisitAction(...elements) {
450
450
  const action = getAttribute("data-turbo-action", ...elements);
451
451
  return isAction(action) ? action : null;
@@ -467,6 +467,13 @@ Copyright © 2022 37signals LLC
467
467
  element.setAttribute("content", content);
468
468
  return element;
469
469
  }
470
+ function findClosestRecursively(element, selector) {
471
+ var _a;
472
+ if (element instanceof Element) {
473
+ return (element.closest(selector) ||
474
+ findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
475
+ }
476
+ }
470
477
 
471
478
  var FetchMethod;
472
479
  (function (FetchMethod) {
@@ -514,9 +521,8 @@ Copyright © 2022 37signals LLC
514
521
  this.abortController.abort();
515
522
  }
516
523
  async perform() {
517
- var _a, _b;
518
524
  const { fetchOptions } = this;
519
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
525
+ this.delegate.prepareRequest(this);
520
526
  await this.allowRequestToBeIntercepted(fetchOptions);
521
527
  try {
522
528
  this.delegate.requestStarted(this);
@@ -754,11 +760,11 @@ Copyright © 2022 37signals LLC
754
760
  return true;
755
761
  }
756
762
  }
757
- prepareHeadersForRequest(headers, request) {
763
+ prepareRequest(request) {
758
764
  if (!request.isIdempotent) {
759
765
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
760
766
  if (token) {
761
- headers["X-CSRF-Token"] = token;
767
+ request.headers["X-CSRF-Token"] = token;
762
768
  }
763
769
  }
764
770
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -946,12 +952,17 @@ Copyright © 2022 37signals LLC
946
952
  return method != "dialog";
947
953
  }
948
954
  function submissionDoesNotTargetIFrame(form, submitter) {
949
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
950
- for (const element of document.getElementsByName(target)) {
951
- if (element instanceof HTMLIFrameElement)
952
- return false;
955
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
956
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
957
+ for (const element of document.getElementsByName(target)) {
958
+ if (element instanceof HTMLIFrameElement)
959
+ return false;
960
+ }
961
+ return true;
962
+ }
963
+ else {
964
+ return true;
953
965
  }
954
- return true;
955
966
  }
956
967
 
957
968
  class View {
@@ -1144,20 +1155,23 @@ Copyright © 2022 37signals LLC
1144
1155
  event.shiftKey);
1145
1156
  }
1146
1157
  findLinkFromClickTarget(target) {
1147
- if (target instanceof Element) {
1148
- return target.closest("a[href]:not([target^=_]):not([download])");
1149
- }
1158
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1150
1159
  }
1151
1160
  getLocationForLink(link) {
1152
1161
  return expandURL(link.getAttribute("href") || "");
1153
1162
  }
1154
1163
  }
1155
1164
  function doesNotTargetIFrame(anchor) {
1156
- for (const element of document.getElementsByName(anchor.target)) {
1157
- if (element instanceof HTMLIFrameElement)
1158
- return false;
1165
+ if (anchor.hasAttribute("target")) {
1166
+ for (const element of document.getElementsByName(anchor.target)) {
1167
+ if (element instanceof HTMLIFrameElement)
1168
+ return false;
1169
+ }
1170
+ return true;
1171
+ }
1172
+ else {
1173
+ return true;
1159
1174
  }
1160
- return true;
1161
1175
  }
1162
1176
 
1163
1177
  class FormLinkClickObserver {
@@ -1176,10 +1190,14 @@ Copyright © 2022 37signals LLC
1176
1190
  link.hasAttribute("data-turbo-method"));
1177
1191
  }
1178
1192
  followedLinkToLocation(link, location) {
1179
- const action = location.href;
1180
1193
  const form = document.createElement("form");
1194
+ const type = "hidden";
1195
+ for (const [name, value] of location.searchParams) {
1196
+ form.append(Object.assign(document.createElement("input"), { type, name, value }));
1197
+ }
1198
+ const action = Object.assign(location, { search: "" });
1181
1199
  form.setAttribute("data-turbo", "true");
1182
- form.setAttribute("action", action);
1200
+ form.setAttribute("action", action.href);
1183
1201
  form.setAttribute("hidden", "");
1184
1202
  const method = link.getAttribute("data-turbo-method");
1185
1203
  if (method)
@@ -1187,7 +1205,7 @@ Copyright © 2022 37signals LLC
1187
1205
  const turboFrame = link.getAttribute("data-turbo-frame");
1188
1206
  if (turboFrame)
1189
1207
  form.setAttribute("data-turbo-frame", turboFrame);
1190
- const turboAction = link.getAttribute("data-turbo-action");
1208
+ const turboAction = getVisitAction(link);
1191
1209
  if (turboAction)
1192
1210
  form.setAttribute("data-turbo-action", turboAction);
1193
1211
  const turboConfirm = link.getAttribute("data-turbo-confirm");
@@ -1208,10 +1226,10 @@ Copyright © 2022 37signals LLC
1208
1226
  this.delegate = delegate;
1209
1227
  this.permanentElementMap = permanentElementMap;
1210
1228
  }
1211
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1229
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1212
1230
  const bardo = new this(delegate, permanentElementMap);
1213
1231
  bardo.enter();
1214
- callback();
1232
+ await callback();
1215
1233
  bardo.leave();
1216
1234
  }
1217
1235
  enter() {
@@ -1280,8 +1298,8 @@ Copyright © 2022 37signals LLC
1280
1298
  delete this.resolvingFunctions;
1281
1299
  }
1282
1300
  }
1283
- preservingPermanentElements(callback) {
1284
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1301
+ async preservingPermanentElements(callback) {
1302
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1285
1303
  }
1286
1304
  focusFirstAutofocusableElement() {
1287
1305
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1866,6 +1884,8 @@ Copyright © 2022 37signals LLC
1866
1884
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1867
1885
  action: "replace",
1868
1886
  response: this.response,
1887
+ shouldCacheSnapshot: false,
1888
+ willRender: false,
1869
1889
  });
1870
1890
  this.followedRedirect = true;
1871
1891
  }
@@ -1880,7 +1900,7 @@ Copyright © 2022 37signals LLC
1880
1900
  });
1881
1901
  }
1882
1902
  }
1883
- prepareHeadersForRequest(headers, request) {
1903
+ prepareRequest(request) {
1884
1904
  if (this.acceptsStreamResponse) {
1885
1905
  request.acceptResponseType(StreamMessage.contentType);
1886
1906
  }
@@ -2381,10 +2401,8 @@ Copyright © 2022 37signals LLC
2381
2401
  get restorationIdentifier() {
2382
2402
  return this.history.restorationIdentifier;
2383
2403
  }
2384
- getActionForFormSubmission(formSubmission) {
2385
- const { formElement, submitter } = formSubmission;
2386
- const action = getAttribute("data-turbo-action", submitter, formElement);
2387
- return isAction(action) ? action : "advance";
2404
+ getActionForFormSubmission({ submitter, formElement }) {
2405
+ return getVisitAction(submitter, formElement) || "advance";
2388
2406
  }
2389
2407
  }
2390
2408
 
@@ -2626,7 +2644,7 @@ Copyright © 2022 37signals LLC
2626
2644
  }
2627
2645
  async render() {
2628
2646
  if (this.willRender) {
2629
- this.replaceBody();
2647
+ await this.replaceBody();
2630
2648
  }
2631
2649
  }
2632
2650
  finishRendering() {
@@ -2645,16 +2663,16 @@ Copyright © 2022 37signals LLC
2645
2663
  return this.newSnapshot.element;
2646
2664
  }
2647
2665
  async mergeHead() {
2666
+ const mergedHeadElements = this.mergeProvisionalElements();
2648
2667
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2649
2668
  this.copyNewHeadScriptElements();
2650
- this.removeCurrentHeadProvisionalElements();
2651
- this.copyNewHeadProvisionalElements();
2669
+ await mergedHeadElements;
2652
2670
  await newStylesheetElements;
2653
2671
  }
2654
- replaceBody() {
2655
- this.preservingPermanentElements(() => {
2672
+ async replaceBody() {
2673
+ await this.preservingPermanentElements(async () => {
2656
2674
  this.activateNewBody();
2657
- this.assignNewBody();
2675
+ await this.assignNewBody();
2658
2676
  });
2659
2677
  }
2660
2678
  get trackedElementsAreIdentical() {
@@ -2673,6 +2691,35 @@ Copyright © 2022 37signals LLC
2673
2691
  document.head.appendChild(activateScriptElement(element));
2674
2692
  }
2675
2693
  }
2694
+ async mergeProvisionalElements() {
2695
+ const newHeadElements = [...this.newHeadProvisionalElements];
2696
+ for (const element of this.currentHeadProvisionalElements) {
2697
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2698
+ document.head.removeChild(element);
2699
+ }
2700
+ }
2701
+ for (const element of newHeadElements) {
2702
+ document.head.appendChild(element);
2703
+ }
2704
+ }
2705
+ isCurrentElementInElementList(element, elementList) {
2706
+ for (const [index, newElement] of elementList.entries()) {
2707
+ if (element.tagName == "TITLE") {
2708
+ if (newElement.tagName != "TITLE") {
2709
+ continue;
2710
+ }
2711
+ if (element.innerHTML == newElement.innerHTML) {
2712
+ elementList.splice(index, 1);
2713
+ return true;
2714
+ }
2715
+ }
2716
+ if (newElement.isEqualNode(element)) {
2717
+ elementList.splice(index, 1);
2718
+ return true;
2719
+ }
2720
+ }
2721
+ return false;
2722
+ }
2676
2723
  removeCurrentHeadProvisionalElements() {
2677
2724
  for (const element of this.currentHeadProvisionalElements) {
2678
2725
  document.head.removeChild(element);
@@ -2693,8 +2740,8 @@ Copyright © 2022 37signals LLC
2693
2740
  inertScriptElement.replaceWith(activatedScriptElement);
2694
2741
  }
2695
2742
  }
2696
- assignNewBody() {
2697
- this.renderElement(this.currentElement, this.newElement);
2743
+ async assignNewBody() {
2744
+ await this.renderElement(this.currentElement, this.newElement);
2698
2745
  }
2699
2746
  get newHeadStylesheetElements() {
2700
2747
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3111,8 +3158,8 @@ Copyright © 2022 37signals LLC
3111
3158
  }
3112
3159
  }
3113
3160
  elementIsNavigatable(element) {
3114
- const container = element.closest("[data-turbo]");
3115
- const withinFrame = element.closest("turbo-frame");
3161
+ const container = findClosestRecursively(element, "[data-turbo]");
3162
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3116
3163
  if (this.drive || withinFrame) {
3117
3164
  if (container) {
3118
3165
  return container.getAttribute("data-turbo") != "false";
@@ -3131,8 +3178,7 @@ Copyright © 2022 37signals LLC
3131
3178
  }
3132
3179
  }
3133
3180
  getActionForLink(link) {
3134
- const action = link.getAttribute("data-turbo-action");
3135
- return isAction(action) ? action : "advance";
3181
+ return getVisitAction(link) || "advance";
3136
3182
  }
3137
3183
  get snapshot() {
3138
3184
  return this.view.snapshot;
@@ -3192,7 +3238,10 @@ Copyright © 2022 37signals LLC
3192
3238
  this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3193
3239
  },
3194
3240
  update() {
3195
- this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3241
+ this.targetElements.forEach((targetElement) => {
3242
+ targetElement.innerHTML = "";
3243
+ targetElement.append(this.templateContent);
3244
+ });
3196
3245
  },
3197
3246
  };
3198
3247
 
@@ -3380,7 +3429,8 @@ Copyright © 2022 37signals LLC
3380
3429
  this.fetchResponseLoaded = () => { };
3381
3430
  }
3382
3431
  }
3383
- elementAppearedInViewport(_element) {
3432
+ elementAppearedInViewport(element) {
3433
+ this.proposeVisitIfNavigatedWithAction(element, element);
3384
3434
  this.loadSourceURL();
3385
3435
  }
3386
3436
  willSubmitFormLinkToLocation(link) {
@@ -3406,12 +3456,12 @@ Copyright © 2022 37signals LLC
3406
3456
  }
3407
3457
  this.formSubmission = new FormSubmission(this, element, submitter);
3408
3458
  const { fetchRequest } = this.formSubmission;
3409
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3459
+ this.prepareRequest(fetchRequest);
3410
3460
  this.formSubmission.start();
3411
3461
  }
3412
- prepareHeadersForRequest(headers, request) {
3462
+ prepareRequest(request) {
3413
3463
  var _a;
3414
- headers["Turbo-Frame"] = this.id;
3464
+ request.headers["Turbo-Frame"] = this.id;
3415
3465
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3416
3466
  request.acceptResponseType(StreamMessage.contentType);
3417
3467
  }
@@ -3491,7 +3541,6 @@ Copyright © 2022 37signals LLC
3491
3541
  }
3492
3542
  navigateFrame(element, url, submitter) {
3493
3543
  const frame = this.findFrameElement(element, submitter);
3494
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3495
3544
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3496
3545
  this.withCurrentNavigationElement(element, () => {
3497
3546
  frame.src = url;
@@ -3499,7 +3548,8 @@ Copyright © 2022 37signals LLC
3499
3548
  }
3500
3549
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3501
3550
  this.action = getVisitAction(submitter, element, frame);
3502
- if (isAction(this.action)) {
3551
+ if (this.action) {
3552
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3503
3553
  const { visitCachedSnapshot } = frame.delegate;
3504
3554
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3505
3555
  if (frame.src) {
@@ -3512,7 +3562,7 @@ Copyright © 2022 37signals LLC
3512
3562
  willRender: false,
3513
3563
  updateHistory: false,
3514
3564
  restorationIdentifier: this.restorationIdentifier,
3515
- snapshot: this.pageSnapshot,
3565
+ snapshot: pageSnapshot,
3516
3566
  };
3517
3567
  if (this.action)
3518
3568
  options.action = this.action;
@@ -6,7 +6,7 @@ export interface BardoDelegate {
6
6
  export declare class Bardo {
7
7
  readonly permanentElementMap: PermanentElementMap;
8
8
  readonly delegate: BardoDelegate;
9
- static preservingPermanentElements(delegate: BardoDelegate, permanentElementMap: PermanentElementMap, callback: () => void): void;
9
+ static preservingPermanentElements(delegate: BardoDelegate, permanentElementMap: PermanentElementMap, callback: () => void): Promise<void>;
10
10
  constructor(delegate: BardoDelegate, permanentElementMap: PermanentElementMap);
11
11
  enter(): void;
12
12
  leave(): void;
@@ -1,4 +1,4 @@
1
- import { FetchRequest, FetchMethod, FetchRequestHeaders } from "../../http/fetch_request";
1
+ import { FetchRequest, FetchMethod } from "../../http/fetch_request";
2
2
  import { FetchResponse } from "../../http/fetch_response";
3
3
  export interface FormSubmissionDelegate {
4
4
  formSubmissionStarted(formSubmission: FormSubmission): void;
@@ -55,7 +55,7 @@ export declare class FormSubmission {
55
55
  get stringFormData(): [string, string][];
56
56
  start(): Promise<void | FetchResponse>;
57
57
  stop(): true | undefined;
58
- prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest): void;
58
+ prepareRequest(request: FetchRequest): void;
59
59
  requestStarted(_request: FetchRequest): void;
60
60
  requestPreventedHandlingResponse(request: FetchRequest, response: FetchResponse): void;
61
61
  requestSucceededWithResponse(request: FetchRequest, response: FetchResponse): void;
@@ -31,5 +31,5 @@ export declare class Navigator {
31
31
  visitScrolledToSamePageLocation(oldURL: URL, newURL: URL): void;
32
32
  get location(): URL;
33
33
  get restorationIdentifier(): string;
34
- getActionForFormSubmission(formSubmission: FormSubmission): Action;
34
+ getActionForFormSubmission({ submitter, formElement }: FormSubmission): Action;
35
35
  }
@@ -12,15 +12,17 @@ export declare class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot
12
12
  get newHeadSnapshot(): import("./head_snapshot").HeadSnapshot;
13
13
  get newElement(): HTMLBodyElement;
14
14
  mergeHead(): Promise<void>;
15
- replaceBody(): void;
15
+ replaceBody(): Promise<void>;
16
16
  get trackedElementsAreIdentical(): boolean;
17
17
  copyNewHeadStylesheetElements(): Promise<void>;
18
18
  copyNewHeadScriptElements(): void;
19
+ mergeProvisionalElements(): Promise<void>;
20
+ isCurrentElementInElementList(element: Element, elementList: Element[]): boolean;
19
21
  removeCurrentHeadProvisionalElements(): void;
20
22
  copyNewHeadProvisionalElements(): void;
21
23
  activateNewBody(): void;
22
24
  activateNewBodyScriptElements(): void;
23
- assignNewBody(): void;
25
+ assignNewBody(): Promise<void>;
24
26
  get newHeadStylesheetElements(): HTMLLinkElement[];
25
27
  get newHeadScriptElements(): HTMLScriptElement[];
26
28
  get currentHeadProvisionalElements(): Element[];
@@ -1,5 +1,5 @@
1
1
  import { Adapter } from "../native/adapter";
2
- import { FetchRequest, FetchRequestDelegate, FetchRequestHeaders } from "../../http/fetch_request";
2
+ import { FetchRequest, FetchRequestDelegate } from "../../http/fetch_request";
3
3
  import { FetchResponse } from "../../http/fetch_response";
4
4
  import { History } from "./history";
5
5
  import { Snapshot } from "../snapshot";
@@ -104,7 +104,7 @@ export declare class Visit implements FetchRequestDelegate {
104
104
  loadCachedSnapshot(): void;
105
105
  followRedirect(): void;
106
106
  goToSamePageAnchor(): void;
107
- prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest): void;
107
+ prepareRequest(request: FetchRequest): void;
108
108
  requestStarted(): void;
109
109
  requestPreventedHandlingResponse(_request: FetchRequest, _response: FetchResponse): void;
110
110
  requestSucceededWithResponse(request: FetchRequest, response: FetchResponse): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { FrameElement, FrameElementDelegate, FrameLoadingStyle } from "../../elements/frame_element";
2
- import { FetchRequest, FetchRequestDelegate, FetchRequestHeaders } from "../../http/fetch_request";
2
+ import { FetchRequest, FetchRequestDelegate } from "../../http/fetch_request";
3
3
  import { FetchResponse } from "../../http/fetch_response";
4
4
  import { AppearanceObserver, AppearanceObserverDelegate } from "../../observers/appearance_observer";
5
5
  import { FormSubmission, FormSubmissionDelegate } from "../drive/form_submission";
@@ -11,16 +11,15 @@ import { FrameView } from "./frame_view";
11
11
  import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor";
12
12
  import { FormLinkClickObserver, FormLinkClickObserverDelegate } from "../../observers/form_link_click_observer";
13
13
  import { VisitOptions } from "../drive/visit";
14
- import { PageSnapshot } from "../drive/page_snapshot";
15
14
  declare type VisitFallback = (location: Response | Locatable, options: Partial<VisitOptions>) => Promise<void>;
16
15
  export declare type TurboFrameMissingEvent = CustomEvent<{
17
16
  response: Response;
18
17
  visit: VisitFallback;
19
18
  }>;
20
- export declare class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormSubmitObserverDelegate, FormSubmissionDelegate, FrameElementDelegate, FormLinkClickObserverDelegate, LinkInterceptorDelegate, ViewDelegate<FrameElement, Snapshot<FrameElement>> {
19
+ export declare class FrameController implements AppearanceObserverDelegate<FrameElement>, FetchRequestDelegate, FormSubmitObserverDelegate, FormSubmissionDelegate, FrameElementDelegate, FormLinkClickObserverDelegate, LinkInterceptorDelegate, ViewDelegate<FrameElement, Snapshot<FrameElement>> {
21
20
  readonly element: FrameElement;
22
21
  readonly view: FrameView;
23
- readonly appearanceObserver: AppearanceObserver;
22
+ readonly appearanceObserver: AppearanceObserver<FrameElement>;
24
23
  readonly formLinkClickObserver: FormLinkClickObserver;
25
24
  readonly linkInterceptor: LinkInterceptor;
26
25
  readonly formSubmitObserver: FormSubmitObserver;
@@ -35,7 +34,6 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
35
34
  readonly restorationIdentifier: string;
36
35
  private previousFrameElement?;
37
36
  private currentNavigationElement?;
38
- pageSnapshot?: PageSnapshot;
39
37
  constructor(element: FrameElement);
40
38
  connect(): void;
41
39
  disconnect(): void;
@@ -46,14 +44,14 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
46
44
  loadingStyleChanged(): void;
47
45
  private loadSourceURL;
48
46
  loadResponse(fetchResponse: FetchResponse): Promise<void>;
49
- elementAppearedInViewport(_element: Element): void;
47
+ elementAppearedInViewport(element: FrameElement): void;
50
48
  willSubmitFormLinkToLocation(link: Element): boolean;
51
49
  submittedFormLinkToLocation(link: Element, _location: URL, form: HTMLFormElement): void;
52
50
  shouldInterceptLinkClick(element: Element, _location: string, _event: MouseEvent): boolean;
53
51
  linkClickIntercepted(element: Element, location: string): void;
54
52
  willSubmitForm(element: HTMLFormElement, submitter?: HTMLElement): boolean;
55
53
  formSubmitted(element: HTMLFormElement, submitter?: HTMLElement): void;
56
- prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest): void;
54
+ prepareRequest(request: FetchRequest): void;
57
55
  requestStarted(_request: FetchRequest): void;
58
56
  requestPreventedHandlingResponse(_request: FetchRequest, _response: FetchResponse): void;
59
57
  requestSucceededWithResponse(request: FetchRequest, response: FetchResponse): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { BardoDelegate } from "./bardo";
2
2
  import { Snapshot } from "./snapshot";
3
3
  import { ReloadReason } from "./native/browser_adapter";
4
- export declare type Render<E> = (newElement: E, currentElement: E) => void;
4
+ export declare type Render<E> = (currentElement: E, newElement: E) => void;
5
5
  export declare abstract class Renderer<E extends Element, S extends Snapshot<E> = Snapshot<E>> implements BardoDelegate {
6
6
  readonly currentSnapshot: S;
7
7
  readonly newSnapshot: S;
@@ -17,7 +17,7 @@ export declare abstract class Renderer<E extends Element, S extends Snapshot<E>
17
17
  prepareToRender(): void;
18
18
  abstract render(): Promise<void>;
19
19
  finishRendering(): void;
20
- preservingPermanentElements(callback: () => void): void;
20
+ preservingPermanentElements(callback: () => void): Promise<void>;
21
21
  focusFirstAutofocusableElement(): void;
22
22
  enteringBardo(currentPermanentElement: Element): void;
23
23
  leavingBardo(currentPermanentElement: Element): void;
@@ -1,5 +1,4 @@
1
1
  export declare type Action = "advance" | "replace" | "restore";
2
- export declare function isAction(action: any): action is Action;
3
2
  export declare type Position = {
4
3
  x: number;
5
4
  y: number;
@@ -14,7 +14,7 @@ export declare type TurboFetchRequestErrorEvent = CustomEvent<{
14
14
  }>;
15
15
  export interface FetchRequestDelegate {
16
16
  referrer?: URL;
17
- prepareHeadersForRequest?(headers: FetchRequestHeaders, request: FetchRequest): void;
17
+ prepareRequest(request: FetchRequest): void;
18
18
  requestStarted(request: FetchRequest): void;
19
19
  requestPreventedHandlingResponse(request: FetchRequest, response: FetchResponse): void;
20
20
  requestSucceededWithResponse(request: FetchRequest, response: FetchResponse): void;
@@ -1,12 +1,12 @@
1
- export interface AppearanceObserverDelegate {
2
- elementAppearedInViewport(element: Element): void;
1
+ export interface AppearanceObserverDelegate<T extends Element> {
2
+ elementAppearedInViewport(element: T): void;
3
3
  }
4
- export declare class AppearanceObserver {
5
- readonly delegate: AppearanceObserverDelegate;
6
- readonly element: Element;
4
+ export declare class AppearanceObserver<T extends Element> {
5
+ readonly delegate: AppearanceObserverDelegate<T>;
6
+ readonly element: T;
7
7
  readonly intersectionObserver: IntersectionObserver;
8
8
  started: boolean;
9
- constructor(delegate: AppearanceObserverDelegate, element: Element);
9
+ constructor(delegate: AppearanceObserverDelegate<T>, element: T);
10
10
  start(): void;
11
11
  stop(): void;
12
12
  intersect: IntersectionObserverCallback;
@@ -12,6 +12,6 @@ export declare class LinkClickObserver {
12
12
  clickCaptured: () => void;
13
13
  clickBubbled: (event: Event) => void;
14
14
  clickEventIsSignificant(event: MouseEvent): boolean;
15
- findLinkFromClickTarget(target: EventTarget | null): HTMLAnchorElement | null | undefined;
15
+ findLinkFromClickTarget(target: EventTarget | null): HTMLAnchorElement | undefined;
16
16
  getLocationForLink(link: Element): URL;
17
17
  }
@@ -1,3 +1,10 @@
1
+ declare global {
2
+ namespace Chai {
3
+ interface AssertStatic {
4
+ equalIgnoringWhitespace(actual: string | null | undefined, expected: string, message?: string): void;
5
+ }
6
+ }
7
+ }
1
8
  declare global {
2
9
  interface Window {
3
10
  frameScriptEvaluationCount?: number;
@@ -1,4 +1,3 @@
1
- /// <reference types="chai" />
2
1
  import Test from "intern/lib/Test";
3
2
  import { Tests } from "intern/lib/interfaces/object";
4
3
  export declare class InternTestCase {
@@ -6,6 +6,8 @@ declare type EventLog = [EventType, EventDetail, Target];
6
6
  declare type MutationAttributeName = string;
7
7
  declare type MutationAttributeValue = string | null;
8
8
  declare type MutationLog = [MutationAttributeName, Target, MutationAttributeValue];
9
+ declare type BodyHTML = string;
10
+ declare type BodyMutationLog = [BodyHTML];
9
11
  export declare function attributeForSelector(page: Page, selector: string, attributeName: string): Promise<string | null>;
10
12
  declare type CancellableEvent = "turbo:click" | "turbo:before-visit";
11
13
  export declare function cancelNextEvent(page: Page, eventName: CancellableEvent): Promise<void>;
@@ -23,13 +25,17 @@ export declare function nextBody(_page: Page, timeout?: number): Promise<void>;
23
25
  export declare function nextEventNamed(page: Page, eventName: string): Promise<any>;
24
26
  export declare function nextEventOnTarget(page: Page, elementId: string, eventName: string): Promise<any>;
25
27
  export declare function listenForEventOnTarget(page: Page, elementId: string, eventName: string): Promise<void>;
28
+ export declare function nextBodyMutation(page: Page): Promise<string | null>;
29
+ export declare function noNextBodyMutation(page: Page): Promise<boolean>;
26
30
  export declare function nextAttributeMutationNamed(page: Page, elementId: string, attributeName: string): Promise<string | null>;
27
31
  export declare function noNextAttributeMutationNamed(page: Page, elementId: string, attributeName: string): Promise<boolean>;
28
32
  export declare function noNextEventNamed(page: Page, eventName: string): Promise<boolean>;
29
33
  export declare function noNextEventOnTarget(page: Page, elementId: string, eventName: string): Promise<boolean>;
30
34
  export declare function outerHTMLForSelector(page: Page, selector: string): Promise<string>;
31
35
  export declare function pathname(url: string): string;
36
+ export declare function pathnameForIFrame(page: Page, name: string): Promise<string>;
32
37
  export declare function propertyForSelector(page: Page, selector: string, propertyName: string): Promise<any>;
38
+ export declare function readBodyMutationLogs(page: Page, length?: number): Promise<BodyMutationLog[]>;
33
39
  export declare function readEventLogs(page: Page, length?: number): Promise<EventLog[]>;
34
40
  export declare function readMutationLogs(page: Page, length?: number): Promise<MutationLog[]>;
35
41
  export declare function search(url: string): string;
@@ -47,6 +53,7 @@ export declare function strictElementEquals(left: Locator, right: Locator): Prom
47
53
  export declare function textContent(page: Page, html: string): Promise<string | null>;
48
54
  export declare function visitAction(page: Page): Promise<string>;
49
55
  export declare function waitForPathname(page: Page, pathname: string): Promise<void>;
56
+ export declare function waitUntilText(page: Page, text: string, state?: "visible" | "attached"): Promise<import("playwright-core").ElementHandle<HTMLElement | SVGElement>>;
50
57
  export declare function waitUntilSelector(page: Page, selector: string, state?: "visible" | "attached"): Promise<import("playwright-core").ElementHandle<HTMLElement | SVGElement>>;
51
58
  export declare function waitUntilNoSelector(page: Page, selector: string, state?: "hidden" | "detached"): Promise<import("playwright-core").ElementHandle<HTMLElement | SVGElement> | null>;
52
59
  export declare function willChangeBody(page: Page, callback: () => Promise<void>): Promise<boolean>;
@@ -19,7 +19,9 @@ export declare function markAsBusy(...elements: Element[]): void;
19
19
  export declare function clearBusyState(...elements: Element[]): void;
20
20
  export declare function waitForLoad(element: HTMLLinkElement, timeoutInMilliseconds?: number): Promise<void>;
21
21
  export declare function getHistoryMethodForAction(action: Action): (data: any, unused: string, url?: string | URL | null | undefined) => void;
22
+ export declare function isAction(action: any): action is Action;
22
23
  export declare function getVisitAction(...elements: (Element | undefined)[]): Action | null;
23
24
  export declare function getMetaElement(name: string): HTMLMetaElement | null;
24
25
  export declare function getMetaContent(name: string): string | null;
25
26
  export declare function setMetaContent(name: string, content: string): HTMLMetaElement;
27
+ export declare function findClosestRecursively<E extends Element>(element: Element | null, selector: string): E | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotwired/turbo",
3
- "version": "7.2.4",
3
+ "version": "7.2.5",
4
4
  "description": "The speed of a single-page web application without having to write any JavaScript",
5
5
  "module": "dist/turbo.es2017-esm.js",
6
6
  "main": "dist/turbo.es2017-umd.js",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "devDependencies": {
38
- "@playwright/test": "^1.22.2",
38
+ "@playwright/test": "^1.28.0",
39
39
  "@rollup/plugin-node-resolve": "13.1.3",
40
40
  "@rollup/plugin-typescript": "^8.5.0",
41
41
  "@types/multer": "^1.4.5",