@hotwired/turbo 7.2.3 → 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.2
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
  }
@@ -1869,11 +1889,12 @@ class Visit {
1869
1889
  this.render(async () => {
1870
1890
  this.cacheSnapshot();
1871
1891
  this.performScroll();
1892
+ this.changeHistory();
1872
1893
  this.adapter.visitRendered(this);
1873
1894
  });
1874
1895
  }
1875
1896
  }
1876
- prepareHeadersForRequest(headers, request) {
1897
+ prepareRequest(request) {
1877
1898
  if (this.acceptsStreamResponse) {
1878
1899
  request.acceptResponseType(StreamMessage.contentType);
1879
1900
  }
@@ -2277,7 +2298,6 @@ class Navigator {
2277
2298
  }
2278
2299
  }
2279
2300
  startVisit(locatable, restorationIdentifier, options = {}) {
2280
- this.lastVisit = this.currentVisit;
2281
2301
  this.stop();
2282
2302
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2283
2303
  this.currentVisit.start();
@@ -2359,13 +2379,11 @@ class Navigator {
2359
2379
  this.delegate.visitCompleted(visit);
2360
2380
  }
2361
2381
  locationWithActionIsSamePage(location, action) {
2362
- var _a;
2363
2382
  const anchor = getAnchor(location);
2364
- const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2365
- const currentAnchor = getAnchor(lastLocation);
2383
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2366
2384
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2367
2385
  return (action !== "replace" &&
2368
- getRequestURL(location) === getRequestURL(lastLocation) &&
2386
+ getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2369
2387
  (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2370
2388
  }
2371
2389
  visitScrolledToSamePageLocation(oldURL, newURL) {
@@ -2377,10 +2395,8 @@ class Navigator {
2377
2395
  get restorationIdentifier() {
2378
2396
  return this.history.restorationIdentifier;
2379
2397
  }
2380
- getActionForFormSubmission(formSubmission) {
2381
- const { formElement, submitter } = formSubmission;
2382
- const action = getAttribute("data-turbo-action", submitter, formElement);
2383
- return isAction(action) ? action : "advance";
2398
+ getActionForFormSubmission({ submitter, formElement }) {
2399
+ return getVisitAction(submitter, formElement) || "advance";
2384
2400
  }
2385
2401
  }
2386
2402
 
@@ -2622,7 +2638,7 @@ class PageRenderer extends Renderer {
2622
2638
  }
2623
2639
  async render() {
2624
2640
  if (this.willRender) {
2625
- this.replaceBody();
2641
+ await this.replaceBody();
2626
2642
  }
2627
2643
  }
2628
2644
  finishRendering() {
@@ -2641,16 +2657,16 @@ class PageRenderer extends Renderer {
2641
2657
  return this.newSnapshot.element;
2642
2658
  }
2643
2659
  async mergeHead() {
2660
+ const mergedHeadElements = this.mergeProvisionalElements();
2644
2661
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2645
2662
  this.copyNewHeadScriptElements();
2646
- this.removeCurrentHeadProvisionalElements();
2647
- this.copyNewHeadProvisionalElements();
2663
+ await mergedHeadElements;
2648
2664
  await newStylesheetElements;
2649
2665
  }
2650
- replaceBody() {
2651
- this.preservingPermanentElements(() => {
2666
+ async replaceBody() {
2667
+ await this.preservingPermanentElements(async () => {
2652
2668
  this.activateNewBody();
2653
- this.assignNewBody();
2669
+ await this.assignNewBody();
2654
2670
  });
2655
2671
  }
2656
2672
  get trackedElementsAreIdentical() {
@@ -2669,6 +2685,35 @@ class PageRenderer extends Renderer {
2669
2685
  document.head.appendChild(activateScriptElement(element));
2670
2686
  }
2671
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
+ }
2672
2717
  removeCurrentHeadProvisionalElements() {
2673
2718
  for (const element of this.currentHeadProvisionalElements) {
2674
2719
  document.head.removeChild(element);
@@ -2689,8 +2734,8 @@ class PageRenderer extends Renderer {
2689
2734
  inertScriptElement.replaceWith(activatedScriptElement);
2690
2735
  }
2691
2736
  }
2692
- assignNewBody() {
2693
- this.renderElement(this.currentElement, this.newElement);
2737
+ async assignNewBody() {
2738
+ await this.renderElement(this.currentElement, this.newElement);
2694
2739
  }
2695
2740
  get newHeadStylesheetElements() {
2696
2741
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3107,8 +3152,8 @@ class Session {
3107
3152
  }
3108
3153
  }
3109
3154
  elementIsNavigatable(element) {
3110
- const container = element.closest("[data-turbo]");
3111
- const withinFrame = element.closest("turbo-frame");
3155
+ const container = findClosestRecursively(element, "[data-turbo]");
3156
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3112
3157
  if (this.drive || withinFrame) {
3113
3158
  if (container) {
3114
3159
  return container.getAttribute("data-turbo") != "false";
@@ -3127,8 +3172,7 @@ class Session {
3127
3172
  }
3128
3173
  }
3129
3174
  getActionForLink(link) {
3130
- const action = link.getAttribute("data-turbo-action");
3131
- return isAction(action) ? action : "advance";
3175
+ return getVisitAction(link) || "advance";
3132
3176
  }
3133
3177
  get snapshot() {
3134
3178
  return this.view.snapshot;
@@ -3188,7 +3232,10 @@ const StreamActions = {
3188
3232
  this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3189
3233
  },
3190
3234
  update() {
3191
- this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3235
+ this.targetElements.forEach((targetElement) => {
3236
+ targetElement.innerHTML = "";
3237
+ targetElement.append(this.templateContent);
3238
+ });
3192
3239
  },
3193
3240
  };
3194
3241
 
@@ -3376,7 +3423,8 @@ class FrameController {
3376
3423
  this.fetchResponseLoaded = () => { };
3377
3424
  }
3378
3425
  }
3379
- elementAppearedInViewport(_element) {
3426
+ elementAppearedInViewport(element) {
3427
+ this.proposeVisitIfNavigatedWithAction(element, element);
3380
3428
  this.loadSourceURL();
3381
3429
  }
3382
3430
  willSubmitFormLinkToLocation(link) {
@@ -3402,12 +3450,12 @@ class FrameController {
3402
3450
  }
3403
3451
  this.formSubmission = new FormSubmission(this, element, submitter);
3404
3452
  const { fetchRequest } = this.formSubmission;
3405
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3453
+ this.prepareRequest(fetchRequest);
3406
3454
  this.formSubmission.start();
3407
3455
  }
3408
- prepareHeadersForRequest(headers, request) {
3456
+ prepareRequest(request) {
3409
3457
  var _a;
3410
- headers["Turbo-Frame"] = this.id;
3458
+ request.headers["Turbo-Frame"] = this.id;
3411
3459
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3412
3460
  request.acceptResponseType(StreamMessage.contentType);
3413
3461
  }
@@ -3487,7 +3535,6 @@ class FrameController {
3487
3535
  }
3488
3536
  navigateFrame(element, url, submitter) {
3489
3537
  const frame = this.findFrameElement(element, submitter);
3490
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3491
3538
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3492
3539
  this.withCurrentNavigationElement(element, () => {
3493
3540
  frame.src = url;
@@ -3495,7 +3542,8 @@ class FrameController {
3495
3542
  }
3496
3543
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3497
3544
  this.action = getVisitAction(submitter, element, frame);
3498
- if (isAction(this.action)) {
3545
+ if (this.action) {
3546
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3499
3547
  const { visitCachedSnapshot } = frame.delegate;
3500
3548
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3501
3549
  if (frame.src) {
@@ -3508,7 +3556,7 @@ class FrameController {
3508
3556
  willRender: false,
3509
3557
  updateHistory: false,
3510
3558
  restorationIdentifier: this.restorationIdentifier,
3511
- snapshot: this.pageSnapshot,
3559
+ snapshot: pageSnapshot,
3512
3560
  };
3513
3561
  if (this.action)
3514
3562
  options.action = this.action;
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.2
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
  }
@@ -1875,11 +1895,12 @@ Copyright © 2022 37signals LLC
1875
1895
  this.render(async () => {
1876
1896
  this.cacheSnapshot();
1877
1897
  this.performScroll();
1898
+ this.changeHistory();
1878
1899
  this.adapter.visitRendered(this);
1879
1900
  });
1880
1901
  }
1881
1902
  }
1882
- prepareHeadersForRequest(headers, request) {
1903
+ prepareRequest(request) {
1883
1904
  if (this.acceptsStreamResponse) {
1884
1905
  request.acceptResponseType(StreamMessage.contentType);
1885
1906
  }
@@ -2283,7 +2304,6 @@ Copyright © 2022 37signals LLC
2283
2304
  }
2284
2305
  }
2285
2306
  startVisit(locatable, restorationIdentifier, options = {}) {
2286
- this.lastVisit = this.currentVisit;
2287
2307
  this.stop();
2288
2308
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2289
2309
  this.currentVisit.start();
@@ -2365,13 +2385,11 @@ Copyright © 2022 37signals LLC
2365
2385
  this.delegate.visitCompleted(visit);
2366
2386
  }
2367
2387
  locationWithActionIsSamePage(location, action) {
2368
- var _a;
2369
2388
  const anchor = getAnchor(location);
2370
- const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2371
- const currentAnchor = getAnchor(lastLocation);
2389
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2372
2390
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2373
2391
  return (action !== "replace" &&
2374
- getRequestURL(location) === getRequestURL(lastLocation) &&
2392
+ getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2375
2393
  (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2376
2394
  }
2377
2395
  visitScrolledToSamePageLocation(oldURL, newURL) {
@@ -2383,10 +2401,8 @@ Copyright © 2022 37signals LLC
2383
2401
  get restorationIdentifier() {
2384
2402
  return this.history.restorationIdentifier;
2385
2403
  }
2386
- getActionForFormSubmission(formSubmission) {
2387
- const { formElement, submitter } = formSubmission;
2388
- const action = getAttribute("data-turbo-action", submitter, formElement);
2389
- return isAction(action) ? action : "advance";
2404
+ getActionForFormSubmission({ submitter, formElement }) {
2405
+ return getVisitAction(submitter, formElement) || "advance";
2390
2406
  }
2391
2407
  }
2392
2408
 
@@ -2628,7 +2644,7 @@ Copyright © 2022 37signals LLC
2628
2644
  }
2629
2645
  async render() {
2630
2646
  if (this.willRender) {
2631
- this.replaceBody();
2647
+ await this.replaceBody();
2632
2648
  }
2633
2649
  }
2634
2650
  finishRendering() {
@@ -2647,16 +2663,16 @@ Copyright © 2022 37signals LLC
2647
2663
  return this.newSnapshot.element;
2648
2664
  }
2649
2665
  async mergeHead() {
2666
+ const mergedHeadElements = this.mergeProvisionalElements();
2650
2667
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2651
2668
  this.copyNewHeadScriptElements();
2652
- this.removeCurrentHeadProvisionalElements();
2653
- this.copyNewHeadProvisionalElements();
2669
+ await mergedHeadElements;
2654
2670
  await newStylesheetElements;
2655
2671
  }
2656
- replaceBody() {
2657
- this.preservingPermanentElements(() => {
2672
+ async replaceBody() {
2673
+ await this.preservingPermanentElements(async () => {
2658
2674
  this.activateNewBody();
2659
- this.assignNewBody();
2675
+ await this.assignNewBody();
2660
2676
  });
2661
2677
  }
2662
2678
  get trackedElementsAreIdentical() {
@@ -2675,6 +2691,35 @@ Copyright © 2022 37signals LLC
2675
2691
  document.head.appendChild(activateScriptElement(element));
2676
2692
  }
2677
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
+ }
2678
2723
  removeCurrentHeadProvisionalElements() {
2679
2724
  for (const element of this.currentHeadProvisionalElements) {
2680
2725
  document.head.removeChild(element);
@@ -2695,8 +2740,8 @@ Copyright © 2022 37signals LLC
2695
2740
  inertScriptElement.replaceWith(activatedScriptElement);
2696
2741
  }
2697
2742
  }
2698
- assignNewBody() {
2699
- this.renderElement(this.currentElement, this.newElement);
2743
+ async assignNewBody() {
2744
+ await this.renderElement(this.currentElement, this.newElement);
2700
2745
  }
2701
2746
  get newHeadStylesheetElements() {
2702
2747
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3113,8 +3158,8 @@ Copyright © 2022 37signals LLC
3113
3158
  }
3114
3159
  }
3115
3160
  elementIsNavigatable(element) {
3116
- const container = element.closest("[data-turbo]");
3117
- const withinFrame = element.closest("turbo-frame");
3161
+ const container = findClosestRecursively(element, "[data-turbo]");
3162
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3118
3163
  if (this.drive || withinFrame) {
3119
3164
  if (container) {
3120
3165
  return container.getAttribute("data-turbo") != "false";
@@ -3133,8 +3178,7 @@ Copyright © 2022 37signals LLC
3133
3178
  }
3134
3179
  }
3135
3180
  getActionForLink(link) {
3136
- const action = link.getAttribute("data-turbo-action");
3137
- return isAction(action) ? action : "advance";
3181
+ return getVisitAction(link) || "advance";
3138
3182
  }
3139
3183
  get snapshot() {
3140
3184
  return this.view.snapshot;
@@ -3194,7 +3238,10 @@ Copyright © 2022 37signals LLC
3194
3238
  this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3195
3239
  },
3196
3240
  update() {
3197
- this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3241
+ this.targetElements.forEach((targetElement) => {
3242
+ targetElement.innerHTML = "";
3243
+ targetElement.append(this.templateContent);
3244
+ });
3198
3245
  },
3199
3246
  };
3200
3247
 
@@ -3382,7 +3429,8 @@ Copyright © 2022 37signals LLC
3382
3429
  this.fetchResponseLoaded = () => { };
3383
3430
  }
3384
3431
  }
3385
- elementAppearedInViewport(_element) {
3432
+ elementAppearedInViewport(element) {
3433
+ this.proposeVisitIfNavigatedWithAction(element, element);
3386
3434
  this.loadSourceURL();
3387
3435
  }
3388
3436
  willSubmitFormLinkToLocation(link) {
@@ -3408,12 +3456,12 @@ Copyright © 2022 37signals LLC
3408
3456
  }
3409
3457
  this.formSubmission = new FormSubmission(this, element, submitter);
3410
3458
  const { fetchRequest } = this.formSubmission;
3411
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3459
+ this.prepareRequest(fetchRequest);
3412
3460
  this.formSubmission.start();
3413
3461
  }
3414
- prepareHeadersForRequest(headers, request) {
3462
+ prepareRequest(request) {
3415
3463
  var _a;
3416
- headers["Turbo-Frame"] = this.id;
3464
+ request.headers["Turbo-Frame"] = this.id;
3417
3465
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3418
3466
  request.acceptResponseType(StreamMessage.contentType);
3419
3467
  }
@@ -3493,7 +3541,6 @@ Copyright © 2022 37signals LLC
3493
3541
  }
3494
3542
  navigateFrame(element, url, submitter) {
3495
3543
  const frame = this.findFrameElement(element, submitter);
3496
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3497
3544
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3498
3545
  this.withCurrentNavigationElement(element, () => {
3499
3546
  frame.src = url;
@@ -3501,7 +3548,8 @@ Copyright © 2022 37signals LLC
3501
3548
  }
3502
3549
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3503
3550
  this.action = getVisitAction(submitter, element, frame);
3504
- if (isAction(this.action)) {
3551
+ if (this.action) {
3552
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3505
3553
  const { visitCachedSnapshot } = frame.delegate;
3506
3554
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3507
3555
  if (frame.src) {
@@ -3514,7 +3562,7 @@ Copyright © 2022 37signals LLC
3514
3562
  willRender: false,
3515
3563
  updateHistory: false,
3516
3564
  restorationIdentifier: this.restorationIdentifier,
3517
- snapshot: this.pageSnapshot,
3565
+ snapshot: pageSnapshot,
3518
3566
  };
3519
3567
  if (this.action)
3520
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;
@@ -12,7 +12,6 @@ export declare class Navigator {
12
12
  readonly delegate: NavigatorDelegate;
13
13
  formSubmission?: FormSubmission;
14
14
  currentVisit?: Visit;
15
- lastVisit?: Visit;
16
15
  constructor(delegate: NavigatorDelegate);
17
16
  proposeVisit(location: URL, options?: Partial<VisitOptions>): void;
18
17
  startVisit(locatable: Locatable, restorationIdentifier: string, options?: Partial<VisitOptions>): void;
@@ -32,5 +31,5 @@ export declare class Navigator {
32
31
  visitScrolledToSamePageLocation(oldURL: URL, newURL: URL): void;
33
32
  get location(): URL;
34
33
  get restorationIdentifier(): string;
35
- getActionForFormSubmission(formSubmission: FormSubmission): Action;
34
+ getActionForFormSubmission({ submitter, formElement }: FormSubmission): Action;
36
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.3",
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",
@@ -64,8 +64,7 @@
64
64
  "test:browser": "playwright test",
65
65
  "test:unit": "NODE_OPTIONS=--inspect node src/tests/runner.js",
66
66
  "test:unit:win": "SET NODE_OPTIONS=--inspect & node src/tests/runner.js",
67
- "prerelease": "yarn build && git --no-pager diff && echo && npm pack --dry-run && echo && read -n 1 -p \"Look OK? Press any key to publish and commit v$npm_package_version\" && echo",
68
- "release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push",
67
+ "release": "yarn build && npm publish",
69
68
  "lint": "eslint . --ext .ts"
70
69
  },
71
70
  "engines": {