@angular/core 20.0.0-next.0 → 20.0.0-next.1

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.
Files changed (43) hide show
  1. package/fesm2022/core.mjs +6448 -6198
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/di.mjs +45 -0
  4. package/fesm2022/primitives/di.mjs.map +1 -0
  5. package/fesm2022/primitives/event-dispatch.mjs +2 -2
  6. package/fesm2022/primitives/signals.mjs +2 -2
  7. package/fesm2022/rxjs-interop.mjs +2 -2
  8. package/fesm2022/testing.mjs +280 -119
  9. package/fesm2022/testing.mjs.map +1 -1
  10. package/index.d.ts +138 -51
  11. package/package.json +11 -1
  12. package/primitives/di/index.d.ts +99 -0
  13. package/primitives/event-dispatch/index.d.ts +2 -2
  14. package/primitives/signals/index.d.ts +2 -2
  15. package/rxjs-interop/index.d.ts +2 -2
  16. package/schematics/bundles/{apply_import_manager-0959b78c.js → apply_import_manager-e2a7fe5b.js} +4 -4
  17. package/schematics/bundles/{checker-cf6f7980.js → checker-af521da6.js} +346 -234
  18. package/schematics/bundles/cleanup-unused-imports.js +8 -8
  19. package/schematics/bundles/{compiler_host-cc1379e9.js → compiler_host-5a29293c.js} +3 -3
  20. package/schematics/bundles/control-flow-migration.js +4 -4
  21. package/schematics/bundles/explicit-standalone-flag.js +6 -6
  22. package/schematics/bundles/{imports-31a38653.js → imports-047fbbc8.js} +2 -2
  23. package/schematics/bundles/{index-42d84d69.js → index-1bef3025.js} +5 -5
  24. package/schematics/bundles/{index-6675d6bc.js → index-ef1bffbb.js} +5 -5
  25. package/schematics/bundles/inject-migration.js +8 -8
  26. package/schematics/bundles/{leading_space-6e7a8ec6.js → leading_space-f8944434.js} +2 -2
  27. package/schematics/bundles/{migrate_ts_type_references-5089e4ef.js → migrate_ts_type_references-2a3e9e6b.js} +13 -15
  28. package/schematics/bundles/{ng_decorators-6878e227.js → ng_decorators-b0d8b324.js} +3 -3
  29. package/schematics/bundles/{nodes-ffdce442.js → nodes-7758dbf6.js} +2 -2
  30. package/schematics/bundles/output-migration.js +8 -8
  31. package/schematics/bundles/pending-tasks.js +6 -6
  32. package/schematics/bundles/{program-362689f0.js → program-a449f9bf.js} +14 -14
  33. package/schematics/bundles/{project_paths-7d2daa1e.js → project_paths-17dc204d.js} +4 -4
  34. package/schematics/bundles/{project_tsconfig_paths-6c9cde78.js → project_tsconfig_paths-b558633b.js} +2 -2
  35. package/schematics/bundles/{property_name-42030525.js → property_name-ac18447e.js} +2 -2
  36. package/schematics/bundles/provide-initializer.js +6 -6
  37. package/schematics/bundles/route-lazy-loading.js +6 -6
  38. package/schematics/bundles/self-closing-tags-migration.js +44 -28
  39. package/schematics/bundles/signal-input-migration.js +10 -10
  40. package/schematics/bundles/signal-queries-migration.js +10 -10
  41. package/schematics/bundles/signals.js +10 -10
  42. package/schematics/bundles/standalone-migration.js +10 -10
  43. package/testing/index.d.ts +25 -14
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license Angular v20.0.0-next.0
3
- * (c) 2010-2024 Google LLC. https://angular.io/
2
+ * @license Angular v20.0.0-next.1
3
+ * (c) 2010-2025 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
6
6
 
@@ -175,10 +175,10 @@ class TestBedApplicationErrorHandler {
175
175
  throw e;
176
176
  }
177
177
  }
178
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0-next.0", ngImport: i0, type: TestBedApplicationErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
179
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0-next.0", ngImport: i0, type: TestBedApplicationErrorHandler });
178
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0-next.1", ngImport: i0, type: TestBedApplicationErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
179
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0-next.1", ngImport: i0, type: TestBedApplicationErrorHandler });
180
180
  }
181
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0-next.0", ngImport: i0, type: TestBedApplicationErrorHandler, decorators: [{
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0-next.1", ngImport: i0, type: TestBedApplicationErrorHandler, decorators: [{
182
182
  type: Injectable
183
183
  }] });
184
184
 
@@ -2337,8 +2337,9 @@ class FakeNavigation {
2337
2337
  currentEntryIndex = 0;
2338
2338
  /**
2339
2339
  * The current navigate event.
2340
+ * @internal
2340
2341
  */
2341
- navigateEvent = undefined;
2342
+ navigateEvent = null;
2342
2343
  /**
2343
2344
  * A Map of pending traversals, so that traversals to the same entry can be
2344
2345
  * re-used.
@@ -2361,7 +2362,10 @@ class FakeNavigation {
2361
2362
  synchronousTraversals = false;
2362
2363
  /** Whether to allow a call to setInitialEntryForTesting. */
2363
2364
  canSetInitialEntry = true;
2364
- /** `EventTarget` to dispatch events. */
2365
+ /**
2366
+ * `EventTarget` to dispatch events.
2367
+ * @internal
2368
+ */
2365
2369
  eventTarget;
2366
2370
  /** The next unique id for created entries. Replace recreates this id. */
2367
2371
  nextId = 0;
@@ -2393,7 +2397,7 @@ class FakeNavigation {
2393
2397
  throw new Error('setInitialEntryForTesting can only be called before any ' + 'navigation has occurred');
2394
2398
  }
2395
2399
  const currentInitialEntry = this.entriesArr[0];
2396
- this.entriesArr[0] = new FakeNavigationHistoryEntry(new URL(url).toString(), {
2400
+ this.entriesArr[0] = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), new URL(url).toString(), {
2397
2401
  index: 0,
2398
2402
  key: currentInitialEntry?.key ?? String(this.nextKey++),
2399
2403
  id: currentInitialEntry?.id ?? String(this.nextId++),
@@ -2441,7 +2445,7 @@ class FakeNavigation {
2441
2445
  sameDocument: hashChange,
2442
2446
  historyState: null,
2443
2447
  });
2444
- const result = new InternalNavigationResult();
2448
+ const result = new InternalNavigationResult(this);
2445
2449
  this.userAgentNavigate(destination, result, {
2446
2450
  navigationType,
2447
2451
  cancelable: true,
@@ -2473,7 +2477,7 @@ class FakeNavigation {
2473
2477
  sameDocument: true,
2474
2478
  historyState: data,
2475
2479
  });
2476
- const result = new InternalNavigationResult();
2480
+ const result = new InternalNavigationResult(this);
2477
2481
  this.userAgentNavigate(destination, result, {
2478
2482
  navigationType,
2479
2483
  cancelable: true,
@@ -2481,7 +2485,6 @@ class FakeNavigation {
2481
2485
  // Always false for pushState() or replaceState().
2482
2486
  userInitiated: false,
2483
2487
  hashChange,
2484
- skipPopState: true,
2485
2488
  });
2486
2489
  }
2487
2490
  /** Equivalent to `navigation.traverseTo()`. */
@@ -2523,11 +2526,11 @@ class FakeNavigation {
2523
2526
  sameDocument: entry.sameDocument,
2524
2527
  });
2525
2528
  this.prospectiveEntryIndex = entry.index;
2526
- const result = new InternalNavigationResult();
2529
+ const result = new InternalNavigationResult(this);
2527
2530
  this.traversalQueue.set(entry.key, result);
2528
2531
  this.runTraversal(() => {
2529
2532
  this.traversalQueue.delete(entry.key);
2530
- this.userAgentNavigate(destination, result, {
2533
+ const event = this.userAgentNavigate(destination, result, {
2531
2534
  navigationType: 'traverse',
2532
2535
  cancelable: true,
2533
2536
  canIntercept: true,
@@ -2536,6 +2539,8 @@ class FakeNavigation {
2536
2539
  hashChange,
2537
2540
  info: options?.info,
2538
2541
  });
2542
+ // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
2543
+ this.userAgentTraverse(event);
2539
2544
  });
2540
2545
  return {
2541
2546
  committed: result.committed,
@@ -2604,8 +2609,8 @@ class FakeNavigation {
2604
2609
  index: entry.index,
2605
2610
  sameDocument: entry.sameDocument,
2606
2611
  });
2607
- const result = new InternalNavigationResult();
2608
- this.userAgentNavigate(destination, result, {
2612
+ const result = new InternalNavigationResult(this);
2613
+ const event = this.userAgentNavigate(destination, result, {
2609
2614
  navigationType: 'traverse',
2610
2615
  cancelable: true,
2611
2616
  canIntercept: true,
@@ -2613,6 +2618,8 @@ class FakeNavigation {
2613
2618
  userInitiated: false,
2614
2619
  hashChange,
2615
2620
  });
2621
+ // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
2622
+ this.userAgentTraverse(event);
2616
2623
  });
2617
2624
  }
2618
2625
  /** Runs a traversal synchronously or asynchronously */
@@ -2663,9 +2670,9 @@ class FakeNavigation {
2663
2670
  this.canSetInitialEntry = false;
2664
2671
  if (this.navigateEvent) {
2665
2672
  this.navigateEvent.cancel(new DOMException('Navigation was aborted', 'AbortError'));
2666
- this.navigateEvent = undefined;
2673
+ this.navigateEvent = null;
2667
2674
  }
2668
- const navigateEvent = createFakeNavigateEvent({
2675
+ return dispatchNavigateEvent({
2669
2676
  navigationType: options.navigationType,
2670
2677
  cancelable: options.cancelable,
2671
2678
  canIntercept: options.canIntercept,
@@ -2675,78 +2682,103 @@ class FakeNavigation {
2675
2682
  destination,
2676
2683
  info: options.info,
2677
2684
  sameDocument: destination.sameDocument,
2678
- skipPopState: options.skipPopState,
2679
2685
  result,
2680
- userAgentCommit: () => {
2681
- this.userAgentCommit();
2682
- },
2683
2686
  });
2684
- this.navigateEvent = navigateEvent;
2685
- this.eventTarget.dispatchEvent(navigateEvent);
2686
- navigateEvent.dispatchedNavigateEvent();
2687
- if (navigateEvent.commitOption === 'immediate') {
2688
- navigateEvent.commit(/* internal= */ true);
2689
- }
2690
2687
  }
2691
- /** Implementation to commit a navigation. */
2692
- userAgentCommit() {
2693
- if (!this.navigateEvent) {
2694
- return;
2695
- }
2688
+ /**
2689
+ * Implementation to commit a navigation.
2690
+ * https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-commit
2691
+ * @internal
2692
+ */
2693
+ commitNavigateEvent(navigateEvent) {
2694
+ navigateEvent.interceptionState = 'committed';
2696
2695
  const from = this.currentEntry;
2697
- if (!this.navigateEvent.sameDocument) {
2696
+ if (!from) {
2697
+ throw new Error('cannot commit navigation when current entry is null');
2698
+ }
2699
+ if (!navigateEvent.sameDocument) {
2698
2700
  const error = new Error('Cannot navigate to a non-same-document URL.');
2699
- this.navigateEvent.cancel(error);
2701
+ navigateEvent.cancel(error);
2700
2702
  throw error;
2701
2703
  }
2702
- if (this.navigateEvent.navigationType === 'push' ||
2703
- this.navigateEvent.navigationType === 'replace') {
2704
- this.userAgentPushOrReplace(this.navigateEvent.destination, {
2705
- navigationType: this.navigateEvent.navigationType,
2706
- });
2704
+ // "If navigationType is "push" or "replace", then run the URL and history update steps given document and event's destination's URL, with serialiedData set to event's classic history API state and historyHandling set to navigationType."
2705
+ if (navigateEvent.navigationType === 'push' || navigateEvent.navigationType === 'replace') {
2706
+ this.urlAndHistoryUpdateSteps(navigateEvent);
2707
2707
  }
2708
- else if (this.navigateEvent.navigationType === 'traverse') {
2709
- this.userAgentTraverse(this.navigateEvent.destination);
2708
+ else if (navigateEvent.navigationType === 'reload') {
2709
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2710
2710
  }
2711
- this.navigateEvent.userAgentNavigated(this.currentEntry);
2712
- const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
2713
- from,
2714
- navigationType: this.navigateEvent.navigationType,
2715
- });
2716
- this.eventTarget.dispatchEvent(currentEntryChangeEvent);
2717
- if (!this.navigateEvent.skipPopState) {
2718
- const popStateEvent = createPopStateEvent({
2719
- state: this.navigateEvent.destination.getHistoryState(),
2720
- });
2721
- this.window.dispatchEvent(popStateEvent);
2711
+ else if (navigateEvent.navigationType === 'traverse') {
2712
+ // "If navigationType is "traverse", then this event firing is happening as part of the traversal process, and that process will take care of performing the appropriate session history entry updates."
2722
2713
  }
2723
2714
  }
2724
- /** Implementation for a push or replace navigation. */
2725
- userAgentPushOrReplace(destination, { navigationType }) {
2726
- if (navigationType === 'push') {
2727
- this.currentEntryIndex++;
2728
- this.prospectiveEntryIndex = this.currentEntryIndex;
2729
- }
2730
- const index = this.currentEntryIndex;
2731
- const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
2732
- const entry = new FakeNavigationHistoryEntry(destination.url, {
2733
- id: String(this.nextId++),
2734
- key,
2735
- index,
2736
- sameDocument: true,
2737
- state: destination.getState(),
2738
- historyState: destination.getHistoryState(),
2715
+ /**
2716
+ * Implementation for a push or replace navigation.
2717
+ * https://whatpr.org/html/10919/browsing-the-web.html#url-and-history-update-steps
2718
+ * https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
2719
+ */
2720
+ urlAndHistoryUpdateSteps(navigateEvent) {
2721
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2722
+ }
2723
+ /**
2724
+ * Implementation for a traverse navigation.
2725
+ *
2726
+ * https://whatpr.org/html/10919/browsing-the-web.html#apply-the-traverse-history-step
2727
+ * ...
2728
+ * > Let updateDocument be an algorithm step which performs update document for history step application given targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength, scriptHistoryIndex, navigationType, entriesForNavigationAPI, and previousEntry.
2729
+ * > If targetEntry's document is equal to displayedDocument, then perform updateDocument.
2730
+ * https://whatpr.org/html/10919/browsing-the-web.html#update-document-for-history-step-application
2731
+ * which then goes to https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
2732
+ */
2733
+ userAgentTraverse(navigateEvent) {
2734
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2735
+ // Happens as part of "updating the document" steps https://whatpr.org/html/10919/browsing-the-web.html#updating-the-document
2736
+ const popStateEvent = createPopStateEvent({
2737
+ state: navigateEvent.destination.getHistoryState(),
2739
2738
  });
2740
- if (navigationType === 'push') {
2741
- this.entriesArr.splice(index, Infinity, entry);
2739
+ this.window.dispatchEvent(popStateEvent);
2740
+ // TODO(atscott): If oldURL's fragment is not equal to entry's URL's fragment, then queue a global task to fire an event named hashchange
2741
+ }
2742
+ /** https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation */
2743
+ updateNavigationEntriesForSameDocumentNavigation({ destination, navigationType, result, }) {
2744
+ const oldCurrentNHE = this.currentEntry;
2745
+ const disposedNHEs = [];
2746
+ if (navigationType === 'traverse') {
2747
+ this.currentEntryIndex = destination.index;
2748
+ if (this.currentEntryIndex === -1) {
2749
+ throw new Error('unexpected current entry index');
2750
+ }
2742
2751
  }
2743
- else {
2744
- this.entriesArr[index] = entry;
2752
+ else if (navigationType === 'push') {
2753
+ this.currentEntryIndex++;
2754
+ this.prospectiveEntryIndex = this.currentEntryIndex; // prospectiveEntryIndex isn't in the spec but is an implementation detail
2755
+ disposedNHEs.push(...this.entriesArr.splice(this.currentEntryIndex));
2756
+ }
2757
+ else if (navigationType === 'replace') {
2758
+ disposedNHEs.push(oldCurrentNHE);
2759
+ }
2760
+ if (navigationType === 'push' || navigationType === 'replace') {
2761
+ const index = this.currentEntryIndex;
2762
+ const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
2763
+ const newNHE = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), destination.url, {
2764
+ id: String(this.nextId++),
2765
+ key,
2766
+ index,
2767
+ sameDocument: true,
2768
+ state: destination.getState(),
2769
+ historyState: destination.getHistoryState(),
2770
+ });
2771
+ this.entriesArr[this.currentEntryIndex] = newNHE;
2772
+ }
2773
+ result.committedResolve(this.currentEntry);
2774
+ const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
2775
+ from: oldCurrentNHE,
2776
+ navigationType: navigationType,
2777
+ });
2778
+ this.eventTarget.dispatchEvent(currentEntryChangeEvent);
2779
+ for (const disposedNHE of disposedNHEs) {
2780
+ disposedNHE.dispose();
2745
2781
  }
2746
- }
2747
- /** Implementation for a traverse navigation. */
2748
- userAgentTraverse(destination) {
2749
- this.currentEntryIndex = destination.index;
2750
2782
  }
2751
2783
  /** Utility method for finding entries with the given `key`. */
2752
2784
  findEntry(key) {
@@ -2789,8 +2821,13 @@ class FakeNavigation {
2789
2821
  get onnavigateerror() {
2790
2822
  throw new Error('unimplemented');
2791
2823
  }
2824
+ _transition = null;
2825
+ /** @internal */
2826
+ set transition(t) {
2827
+ this._transition = t;
2828
+ }
2792
2829
  get transition() {
2793
- throw new Error('unimplemented');
2830
+ return this._transition;
2794
2831
  }
2795
2832
  updateCurrentEntry(_options) {
2796
2833
  throw new Error('unimplemented');
@@ -2803,6 +2840,7 @@ class FakeNavigation {
2803
2840
  * Fake equivalent of `NavigationHistoryEntry`.
2804
2841
  */
2805
2842
  class FakeNavigationHistoryEntry {
2843
+ eventTarget;
2806
2844
  url;
2807
2845
  sameDocument;
2808
2846
  id;
@@ -2812,7 +2850,8 @@ class FakeNavigationHistoryEntry {
2812
2850
  historyState;
2813
2851
  // tslint:disable-next-line:no-any
2814
2852
  ondispose = null;
2815
- constructor(url, { id, key, index, sameDocument, state, historyState, }) {
2853
+ constructor(eventTarget, url, { id, key, index, sameDocument, state, historyState, }) {
2854
+ this.eventTarget = eventTarget;
2816
2855
  this.url = url;
2817
2856
  this.id = id;
2818
2857
  this.key = key;
@@ -2832,21 +2871,34 @@ class FakeNavigationHistoryEntry {
2832
2871
  : this.historyState;
2833
2872
  }
2834
2873
  addEventListener(type, callback, options) {
2835
- throw new Error('unimplemented');
2874
+ this.eventTarget.addEventListener(type, callback, options);
2836
2875
  }
2837
2876
  removeEventListener(type, callback, options) {
2838
- throw new Error('unimplemented');
2877
+ this.eventTarget.removeEventListener(type, callback, options);
2839
2878
  }
2840
2879
  dispatchEvent(event) {
2841
- throw new Error('unimplemented');
2880
+ return this.eventTarget.dispatchEvent(event);
2881
+ }
2882
+ /** internal */
2883
+ dispose() {
2884
+ const disposeEvent = new Event('disposed');
2885
+ this.dispatchEvent(disposeEvent);
2886
+ // release current listeners
2887
+ this.eventTarget = null;
2842
2888
  }
2843
2889
  }
2844
2890
  /**
2845
2891
  * Create a fake equivalent of `NavigateEvent`. This is not a class because ES5
2846
2892
  * transpiled JavaScript cannot extend native Event.
2893
+ *
2894
+ * https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing
2847
2895
  */
2848
- function createFakeNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, skipPopState, result, userAgentCommit, }) {
2896
+ function dispatchNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, result, }) {
2897
+ const { navigation } = result;
2849
2898
  const event = new Event('navigate', { bubbles: false, cancelable });
2899
+ event.focusResetBehavior = null;
2900
+ event.scrollBehavior = null;
2901
+ event.interceptionState = 'none';
2850
2902
  event.canIntercept = canIntercept;
2851
2903
  event.userInitiated = userInitiated;
2852
2904
  event.hashChange = hashChange;
@@ -2856,71 +2908,151 @@ function createFakeNavigateEvent({ cancelable, canIntercept, userInitiated, hash
2856
2908
  event.info = info;
2857
2909
  event.downloadRequest = null;
2858
2910
  event.formData = null;
2911
+ event.result = result;
2859
2912
  event.sameDocument = sameDocument;
2860
- event.skipPopState = skipPopState;
2861
2913
  event.commitOption = 'immediate';
2862
- let handlerFinished = undefined;
2863
- let interceptCalled = false;
2914
+ let handlersFinished = [Promise.resolve()];
2864
2915
  let dispatchedNavigateEvent = false;
2865
- let commitCalled = false;
2866
2916
  event.intercept = function (options) {
2867
- interceptCalled = true;
2917
+ if (!this.canIntercept) {
2918
+ throw new DOMException(`Cannot intercept when canIntercept is 'false'`, 'SecurityError');
2919
+ }
2920
+ this.interceptionState = 'intercepted';
2868
2921
  event.sameDocument = true;
2869
2922
  const handler = options?.handler;
2870
2923
  if (handler) {
2871
- handlerFinished = handler();
2872
- }
2873
- if (options?.commit) {
2874
- event.commitOption = options.commit;
2924
+ handlersFinished.push(handler());
2875
2925
  }
2876
- // TODO: handle focus reset and scroll?
2926
+ // override old options with new ones. UA _may_ report a console warning if new options differ from previous
2927
+ event.commitOption = options?.commit ?? event.commitOption;
2928
+ event.scrollBehavior = options?.scroll ?? event.scrollBehavior;
2929
+ event.focusResetBehavior = options?.focusReset ?? event.focusResetBehavior;
2877
2930
  };
2878
2931
  event.scroll = function () {
2879
- // TODO: handle scroll?
2932
+ if (event.interceptionState !== 'committed') {
2933
+ throw new DOMException(`Failed to execute 'scroll' on 'NavigateEvent': scroll() must be ` +
2934
+ `called after commit() and interception options must specify manual scroll.`, 'InvalidStateError');
2935
+ }
2936
+ processScrollBehavior(event);
2880
2937
  };
2881
2938
  event.commit = function (internal = false) {
2882
- if (!internal && !interceptCalled) {
2939
+ if (!internal && this.interceptionState !== 'intercepted') {
2883
2940
  throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': intercept() must be ` +
2884
- `called before commit().`, 'InvalidStateError');
2941
+ `called before commit() and commit() cannot be already called.`, 'InvalidStateError');
2942
+ }
2943
+ if (!internal && event.commitOption !== 'after-transition') {
2944
+ throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
2945
+ `called if commit behavior is not "after-transition",.`, 'InvalidStateError');
2885
2946
  }
2886
2947
  if (!dispatchedNavigateEvent) {
2887
2948
  throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
2888
2949
  `called during event dispatch.`, 'InvalidStateError');
2889
2950
  }
2890
- if (commitCalled) {
2891
- throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() already ` + `called.`, 'InvalidStateError');
2892
- }
2893
- commitCalled = true;
2894
- userAgentCommit();
2951
+ this.interceptionState = 'committed';
2952
+ result.navigation.commitNavigateEvent(event);
2895
2953
  };
2896
2954
  // Internal only.
2897
2955
  event.cancel = function (reason) {
2898
2956
  result.committedReject(reason);
2899
2957
  result.finishedReject(reason);
2900
2958
  };
2901
- // Internal only.
2902
- event.dispatchedNavigateEvent = function () {
2959
+ function dispatch() {
2960
+ navigation.navigateEvent = event;
2961
+ navigation.eventTarget.dispatchEvent(event);
2903
2962
  dispatchedNavigateEvent = true;
2904
- if (event.commitOption === 'after-transition') {
2905
- // If handler finishes before commit, call commit.
2906
- handlerFinished?.then(() => {
2907
- if (!commitCalled) {
2908
- event.commit(/* internal */ true);
2909
- }
2910
- }, () => { });
2963
+ if (event.interceptionState !== 'none') {
2964
+ navigation.transition = new InternalNavigationTransition(navigation.currentEntry, navigationType);
2965
+ if (event.commitOption !== 'after-transition') {
2966
+ event.commit(/** internal */ true);
2967
+ }
2911
2968
  }
2912
- Promise.all([result.committed, handlerFinished]).then(([entry]) => {
2913
- result.finishedResolve(entry);
2969
+ else {
2970
+ // In the spec, this isn't really part of the navigate API. Instead, the navigate event firing returns "true" to indicate
2971
+ // navigation steps should "continue" (https://whatpr.org/html/10919/browsing-the-web.html#beginning-navigation)
2972
+ event.commit(/** internal */ true);
2973
+ }
2974
+ Promise.all(handlersFinished).then(() => {
2975
+ // Follows steps outlined under "Wait for all of promisesList, with the following success steps:"
2976
+ // in the spec https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing.
2977
+ if (result.signal.aborted) {
2978
+ return;
2979
+ }
2980
+ if (event !== navigation.navigateEvent) {
2981
+ throw new Error("Navigation's ongoing event not equal to resolved event");
2982
+ }
2983
+ navigation.navigateEvent = null;
2984
+ if (event.interceptionState === 'intercepted') {
2985
+ navigation.commitNavigateEvent(event);
2986
+ }
2987
+ finishNavigationEvent(event, true);
2988
+ const navigatesuccessEvent = new Event('navigatesuccess', { bubbles: false, cancelable });
2989
+ navigation.eventTarget.dispatchEvent(navigatesuccessEvent);
2990
+ result.finishedResolve();
2991
+ if (navigation.transition !== null) {
2992
+ navigation.transition.finishedResolve();
2993
+ }
2994
+ navigation.transition = null;
2914
2995
  }, (reason) => {
2996
+ if (result.signal.aborted) {
2997
+ return;
2998
+ }
2999
+ if (event !== navigation.navigateEvent) {
3000
+ throw new Error("Navigation's ongoing event not equal to resolved event");
3001
+ }
3002
+ navigation.navigateEvent = null;
3003
+ event.interceptionState = 'rejected'; // TODO(atscott): this is not in the spec https://github.com/whatwg/html/issues/11087
3004
+ finishNavigationEvent(event, false);
3005
+ const navigateerrorEvent = new Event('navigateerror', { bubbles: false, cancelable });
3006
+ navigation.eventTarget.dispatchEvent(navigateerrorEvent);
2915
3007
  result.finishedReject(reason);
3008
+ if (navigation.transition !== null) {
3009
+ navigation.transition.finishedResolve();
3010
+ }
3011
+ navigation.transition = null;
2916
3012
  });
2917
- };
2918
- // Internal only.
2919
- event.userAgentNavigated = function (entry) {
2920
- result.committedResolve(entry);
2921
- };
3013
+ }
3014
+ dispatch();
2922
3015
  return event;
2923
3016
  }
3017
+ /** https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-finish */
3018
+ function finishNavigationEvent(event, didFulfill) {
3019
+ if (event.interceptionState === 'intercepted' || event.interceptionState === 'finished') {
3020
+ throw new Error('Attempting to finish navigation event that was incomplete or already finished');
3021
+ }
3022
+ if (event.interceptionState === 'none') {
3023
+ return;
3024
+ }
3025
+ if (didFulfill) {
3026
+ // TODO(atscott): https://github.com/whatwg/html/issues/11087 focus reset is not guarded by didFulfill in the spec
3027
+ potentiallyResetFocus(event);
3028
+ potentiallyResetScroll(event);
3029
+ }
3030
+ event.interceptionState = 'finished';
3031
+ }
3032
+ /** https://whatpr.org/html/10919/nav-history-apis.html#potentially-reset-the-focus */
3033
+ function potentiallyResetFocus(event) {
3034
+ if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
3035
+ throw new Error('cannot reset focus if navigation event is not committed or scrolled');
3036
+ }
3037
+ // TODO(atscott): The rest of the steps
3038
+ }
3039
+ function potentiallyResetScroll(event) {
3040
+ if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
3041
+ throw new Error('cannot reset scroll if navigation event is not committed or scrolled');
3042
+ }
3043
+ if (event.interceptionState === 'scrolled' || event.scrollBehavior === 'manual') {
3044
+ return;
3045
+ }
3046
+ processScrollBehavior(event);
3047
+ }
3048
+ /* https://whatpr.org/html/10919/nav-history-apis.html#process-scroll-behavior */
3049
+ function processScrollBehavior(event) {
3050
+ if (event.interceptionState !== 'committed') {
3051
+ throw new Error('invalid event interception state when processing scroll behavior');
3052
+ }
3053
+ event.interceptionState = 'scrolled';
3054
+ // TODO(atscott): the rest of the steps
3055
+ }
2924
3056
  /**
2925
3057
  * Create a fake equivalent of `NavigationCurrentEntryChange`. This does not use
2926
3058
  * a class because ES5 transpiled JavaScript cannot extend native Event.
@@ -2980,8 +3112,28 @@ function isHashChange(from, to) {
2980
3112
  to.pathname === from.pathname &&
2981
3113
  to.search === from.search);
2982
3114
  }
2983
- /** Internal utility class for representing the result of a navigation. */
3115
+ class InternalNavigationTransition {
3116
+ from;
3117
+ navigationType;
3118
+ finished;
3119
+ finishedResolve;
3120
+ finishedReject;
3121
+ constructor(from, navigationType) {
3122
+ this.from = from;
3123
+ this.navigationType = navigationType;
3124
+ this.finished = new Promise((resolve, reject) => {
3125
+ this.finishedReject = reject;
3126
+ this.finishedResolve = resolve;
3127
+ });
3128
+ }
3129
+ }
3130
+ /**
3131
+ * Internal utility class for representing the result of a navigation.
3132
+ * Generally equivalent to the "apiMethodTracker" in the spec.
3133
+ */
2984
3134
  class InternalNavigationResult {
3135
+ navigation;
3136
+ committedTo = null;
2985
3137
  committedResolve;
2986
3138
  committedReject;
2987
3139
  finishedResolve;
@@ -2992,13 +3144,22 @@ class InternalNavigationResult {
2992
3144
  return this.abortController.signal;
2993
3145
  }
2994
3146
  abortController = new AbortController();
2995
- constructor() {
3147
+ constructor(navigation) {
3148
+ this.navigation = navigation;
2996
3149
  this.committed = new Promise((resolve, reject) => {
2997
- this.committedResolve = resolve;
3150
+ this.committedResolve = (entry) => {
3151
+ this.committedTo = entry;
3152
+ resolve(entry);
3153
+ };
2998
3154
  this.committedReject = reject;
2999
3155
  });
3000
3156
  this.finished = new Promise(async (resolve, reject) => {
3001
- this.finishedResolve = resolve;
3157
+ this.finishedResolve = () => {
3158
+ if (this.committedTo === null) {
3159
+ throw new Error('NavigateEvent should have been committed before resolving finished promise.');
3160
+ }
3161
+ resolve(this.committedTo);
3162
+ };
3002
3163
  this.finishedReject = (reason) => {
3003
3164
  reject(reason);
3004
3165
  this.abortController.abort(reason);