@takazudo/zfb-runtime 2.10.1 → 2.12.0
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.
- package/dist/client-router/events.d.ts +7 -1
- package/dist/client-router/events.js +9 -2
- package/dist/client-router/events.js.map +1 -1
- package/dist/client-router/router.js +67 -26
- package/dist/client-router/router.js.map +1 -1
- package/dist/client-router/swap-functions.js +14 -8
- package/dist/client-router/swap-functions.js.map +1 -1
- package/dist/router.js +18 -38
- package/dist/router.js.map +1 -1
- package/package.json +3 -3
|
@@ -37,5 +37,11 @@ export declare const updateScrollPosition: (positions: {
|
|
|
37
37
|
scrollX: number;
|
|
38
38
|
scrollY: number;
|
|
39
39
|
}) => void;
|
|
40
|
-
export declare function doSwap(afterPreparation: BeforeEvent, viewTransition: ViewTransition, afterDispatch?: () => Promise<void
|
|
40
|
+
export declare function doSwap(afterPreparation: BeforeEvent, viewTransition: ViewTransition, afterDispatch?: () => Promise<void>, beforeSwap?: (event: TransitionBeforeSwapEvent) => void): Promise<{
|
|
41
|
+
swapped: false;
|
|
42
|
+
event: TransitionBeforeSwapEvent;
|
|
43
|
+
} | {
|
|
44
|
+
swapped: true;
|
|
45
|
+
event: TransitionBeforeSwapEvent;
|
|
46
|
+
}>;
|
|
41
47
|
export {};
|
|
@@ -103,13 +103,20 @@ export const updateScrollPosition = (positions) => {
|
|
|
103
103
|
safeReplaceState({ ...history.state, ...positions }, "");
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
|
-
export async function doSwap(afterPreparation, viewTransition, afterDispatch) {
|
|
106
|
+
export async function doSwap(afterPreparation, viewTransition, afterDispatch, beforeSwap) {
|
|
107
107
|
const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition);
|
|
108
108
|
document.dispatchEvent(event);
|
|
109
109
|
if (afterDispatch) {
|
|
110
110
|
await afterDispatch();
|
|
111
111
|
}
|
|
112
|
+
if (event.signal.aborted) {
|
|
113
|
+
return { swapped: false, event };
|
|
114
|
+
}
|
|
115
|
+
// This callback and event.swap() deliberately form one synchronous commit
|
|
116
|
+
// section. Once teardown starts, the navigation must finish even if an
|
|
117
|
+
// observer aborts its signal from inside an overridden swap().
|
|
118
|
+
beforeSwap?.(event);
|
|
112
119
|
event.swap();
|
|
113
|
-
return event;
|
|
120
|
+
return { swapped: true, event };
|
|
114
121
|
}
|
|
115
122
|
//# sourceMappingURL=events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/client-router/events.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\nimport { safeReplaceState } from \"./history-safe.js\";\nimport { swap } from \"./swap-functions.js\";\nimport type { Direction, NavigationTypeString } from \"./types.js\";\n\nexport const TRANSITION_BEFORE_PREPARATION = \"zfb:before-preparation\";\nexport const TRANSITION_AFTER_PREPARATION = \"zfb:after-preparation\";\nexport const TRANSITION_BEFORE_SWAP = \"zfb:before-swap\";\nexport const TRANSITION_AFTER_SWAP = \"zfb:after-swap\";\nexport const TRANSITION_PAGE_LOAD = \"zfb:page-load\";\nexport const TRANSITION_NAVIGATION_ABORTED = \"zfb:navigation-aborted\";\n\ntype Events =\n | \"zfb:after-preparation\"\n | \"zfb:after-swap\"\n | \"zfb:page-load\"\n | \"zfb:navigation-aborted\";\nexport const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));\nexport const onPageLoad = () => triggerEvent(\"zfb:page-load\");\n\n/*\n * Common stuff\n */\nclass BeforeEvent extends Event {\n readonly from: URL;\n to: URL;\n direction: Direction | string;\n readonly navigationType: NavigationTypeString;\n readonly sourceElement: Element | undefined;\n readonly info: any;\n newDocument: Document;\n readonly signal: AbortSignal;\n\n constructor(\n type: string,\n eventInitDict: EventInit | undefined,\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n newDocument: Document,\n signal: AbortSignal,\n ) {\n super(type, eventInitDict);\n this.from = from;\n this.to = to;\n this.direction = direction;\n this.navigationType = navigationType;\n this.sourceElement = sourceElement;\n this.info = info;\n this.newDocument = newDocument;\n this.signal = signal;\n\n Object.defineProperties(this, {\n from: { enumerable: true },\n to: { enumerable: true, writable: true },\n direction: { enumerable: true, writable: true },\n navigationType: { enumerable: true },\n sourceElement: { enumerable: true },\n info: { enumerable: true },\n newDocument: { enumerable: true, writable: true },\n signal: { enumerable: true },\n });\n }\n}\n\n/*\n * TransitionBeforePreparationEvent\n\n */\nexport const isTransitionBeforePreparationEvent = (\n value: any,\n): value is TransitionBeforePreparationEvent =>\n typeof value === \"object\" && value !== null && value.type === TRANSITION_BEFORE_PREPARATION;\nexport class TransitionBeforePreparationEvent extends BeforeEvent {\n formData: FormData | undefined;\n loader: () => Promise<void>;\n constructor(\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n newDocument: Document,\n signal: AbortSignal,\n formData: FormData | undefined,\n loader: (event: TransitionBeforePreparationEvent) => Promise<void>,\n ) {\n super(\n TRANSITION_BEFORE_PREPARATION,\n { cancelable: true },\n from,\n to,\n direction,\n navigationType,\n sourceElement,\n info,\n newDocument,\n signal,\n );\n this.formData = formData;\n this.loader = loader.bind(this, this);\n Object.defineProperties(this, {\n formData: { enumerable: true },\n loader: { enumerable: true, writable: true },\n });\n }\n}\n\n/*\n * TransitionBeforeSwapEvent\n */\nexport const isTransitionBeforeSwapEvent = (value: any): value is TransitionBeforeSwapEvent =>\n typeof value === \"object\" && value !== null && value.type === TRANSITION_BEFORE_SWAP;\nexport class TransitionBeforeSwapEvent extends BeforeEvent {\n override readonly direction: Direction | string;\n readonly viewTransition: ViewTransition;\n swap: () => void;\n\n constructor(afterPreparation: BeforeEvent, viewTransition: ViewTransition) {\n super(\n TRANSITION_BEFORE_SWAP,\n undefined,\n afterPreparation.from,\n afterPreparation.to,\n afterPreparation.direction,\n afterPreparation.navigationType,\n afterPreparation.sourceElement,\n afterPreparation.info,\n afterPreparation.newDocument,\n afterPreparation.signal,\n );\n this.direction = afterPreparation.direction;\n this.viewTransition = viewTransition;\n this.swap = () => swap(this.newDocument);\n\n Object.defineProperties(this, {\n direction: { enumerable: true },\n viewTransition: { enumerable: true },\n swap: { enumerable: true, writable: true },\n });\n }\n}\n\nexport async function doPreparation(\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n signal: AbortSignal,\n formData: FormData | undefined,\n defaultLoader: (event: TransitionBeforePreparationEvent) => Promise<void>,\n) {\n const event = new TransitionBeforePreparationEvent(\n from,\n to,\n direction,\n navigationType,\n sourceElement,\n info,\n window.document,\n signal,\n formData,\n defaultLoader,\n );\n if (document.dispatchEvent(event)) {\n await event.loader();\n if (!event.defaultPrevented) {\n triggerEvent(\"zfb:after-preparation\");\n if (event.navigationType !== \"traverse\") {\n // save the current scroll position before we change the DOM and transition to the new page\n updateScrollPosition({ scrollX, scrollY });\n }\n }\n }\n return event;\n}\n\n// only update history entries that are managed by us\n// leave other entries alone and do not accidentally add state.\nexport const updateScrollPosition = (positions: { scrollX: number; scrollY: number }) => {\n if (history.state) {\n history.scrollRestoration = \"manual\";\n safeReplaceState({ ...history.state, ...positions }, \"\");\n }\n};\n\nexport async function doSwap(\n afterPreparation: BeforeEvent,\n viewTransition: ViewTransition,\n afterDispatch?: () => Promise<void>,\n) {\n const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition);\n document.dispatchEvent(event);\n if (afterDispatch) {\n await afterDispatch();\n }\n event.swap();\n return event;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/client-router/events.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\nimport { safeReplaceState } from \"./history-safe.js\";\nimport { swap } from \"./swap-functions.js\";\nimport type { Direction, NavigationTypeString } from \"./types.js\";\n\nexport const TRANSITION_BEFORE_PREPARATION = \"zfb:before-preparation\";\nexport const TRANSITION_AFTER_PREPARATION = \"zfb:after-preparation\";\nexport const TRANSITION_BEFORE_SWAP = \"zfb:before-swap\";\nexport const TRANSITION_AFTER_SWAP = \"zfb:after-swap\";\nexport const TRANSITION_PAGE_LOAD = \"zfb:page-load\";\nexport const TRANSITION_NAVIGATION_ABORTED = \"zfb:navigation-aborted\";\n\ntype Events =\n | \"zfb:after-preparation\"\n | \"zfb:after-swap\"\n | \"zfb:page-load\"\n | \"zfb:navigation-aborted\";\nexport const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));\nexport const onPageLoad = () => triggerEvent(\"zfb:page-load\");\n\n/*\n * Common stuff\n */\nclass BeforeEvent extends Event {\n readonly from: URL;\n to: URL;\n direction: Direction | string;\n readonly navigationType: NavigationTypeString;\n readonly sourceElement: Element | undefined;\n readonly info: any;\n newDocument: Document;\n readonly signal: AbortSignal;\n\n constructor(\n type: string,\n eventInitDict: EventInit | undefined,\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n newDocument: Document,\n signal: AbortSignal,\n ) {\n super(type, eventInitDict);\n this.from = from;\n this.to = to;\n this.direction = direction;\n this.navigationType = navigationType;\n this.sourceElement = sourceElement;\n this.info = info;\n this.newDocument = newDocument;\n this.signal = signal;\n\n Object.defineProperties(this, {\n from: { enumerable: true },\n to: { enumerable: true, writable: true },\n direction: { enumerable: true, writable: true },\n navigationType: { enumerable: true },\n sourceElement: { enumerable: true },\n info: { enumerable: true },\n newDocument: { enumerable: true, writable: true },\n signal: { enumerable: true },\n });\n }\n}\n\n/*\n * TransitionBeforePreparationEvent\n\n */\nexport const isTransitionBeforePreparationEvent = (\n value: any,\n): value is TransitionBeforePreparationEvent =>\n typeof value === \"object\" && value !== null && value.type === TRANSITION_BEFORE_PREPARATION;\nexport class TransitionBeforePreparationEvent extends BeforeEvent {\n formData: FormData | undefined;\n loader: () => Promise<void>;\n constructor(\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n newDocument: Document,\n signal: AbortSignal,\n formData: FormData | undefined,\n loader: (event: TransitionBeforePreparationEvent) => Promise<void>,\n ) {\n super(\n TRANSITION_BEFORE_PREPARATION,\n { cancelable: true },\n from,\n to,\n direction,\n navigationType,\n sourceElement,\n info,\n newDocument,\n signal,\n );\n this.formData = formData;\n this.loader = loader.bind(this, this);\n Object.defineProperties(this, {\n formData: { enumerable: true },\n loader: { enumerable: true, writable: true },\n });\n }\n}\n\n/*\n * TransitionBeforeSwapEvent\n */\nexport const isTransitionBeforeSwapEvent = (value: any): value is TransitionBeforeSwapEvent =>\n typeof value === \"object\" && value !== null && value.type === TRANSITION_BEFORE_SWAP;\nexport class TransitionBeforeSwapEvent extends BeforeEvent {\n override readonly direction: Direction | string;\n readonly viewTransition: ViewTransition;\n swap: () => void;\n\n constructor(afterPreparation: BeforeEvent, viewTransition: ViewTransition) {\n super(\n TRANSITION_BEFORE_SWAP,\n undefined,\n afterPreparation.from,\n afterPreparation.to,\n afterPreparation.direction,\n afterPreparation.navigationType,\n afterPreparation.sourceElement,\n afterPreparation.info,\n afterPreparation.newDocument,\n afterPreparation.signal,\n );\n this.direction = afterPreparation.direction;\n this.viewTransition = viewTransition;\n this.swap = () => swap(this.newDocument);\n\n Object.defineProperties(this, {\n direction: { enumerable: true },\n viewTransition: { enumerable: true },\n swap: { enumerable: true, writable: true },\n });\n }\n}\n\nexport async function doPreparation(\n from: URL,\n to: URL,\n direction: Direction | string,\n navigationType: NavigationTypeString,\n sourceElement: Element | undefined,\n info: any,\n signal: AbortSignal,\n formData: FormData | undefined,\n defaultLoader: (event: TransitionBeforePreparationEvent) => Promise<void>,\n) {\n const event = new TransitionBeforePreparationEvent(\n from,\n to,\n direction,\n navigationType,\n sourceElement,\n info,\n window.document,\n signal,\n formData,\n defaultLoader,\n );\n if (document.dispatchEvent(event)) {\n await event.loader();\n if (!event.defaultPrevented) {\n triggerEvent(\"zfb:after-preparation\");\n if (event.navigationType !== \"traverse\") {\n // save the current scroll position before we change the DOM and transition to the new page\n updateScrollPosition({ scrollX, scrollY });\n }\n }\n }\n return event;\n}\n\n// only update history entries that are managed by us\n// leave other entries alone and do not accidentally add state.\nexport const updateScrollPosition = (positions: { scrollX: number; scrollY: number }) => {\n if (history.state) {\n history.scrollRestoration = \"manual\";\n safeReplaceState({ ...history.state, ...positions }, \"\");\n }\n};\n\nexport async function doSwap(\n afterPreparation: BeforeEvent,\n viewTransition: ViewTransition,\n afterDispatch?: () => Promise<void>,\n beforeSwap?: (event: TransitionBeforeSwapEvent) => void,\n) {\n const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition);\n document.dispatchEvent(event);\n if (afterDispatch) {\n await afterDispatch();\n }\n if (event.signal.aborted) {\n return { swapped: false as const, event };\n }\n // This callback and event.swap() deliberately form one synchronous commit\n // section. Once teardown starts, the navigation must finish even if an\n // observer aborts its signal from inside an overridden swap().\n beforeSwap?.(event);\n event.swap();\n return { swapped: true as const, event };\n}\n"]}
|
|
@@ -56,8 +56,8 @@ import { safePushState, safeReplaceState } from "./history-safe.js";
|
|
|
56
56
|
import { detectScriptExecuted } from "./swap-functions.js";
|
|
57
57
|
// Island re-bootstrap and deferred-cancel after body swap (W1B §12.2, §12.5).
|
|
58
58
|
// mountNewIslands() is called after runScripts() and before onPageLoad().
|
|
59
|
-
// cancelPendingIslands()
|
|
60
|
-
//
|
|
59
|
+
// cancelPendingIslands() and unmountIslands() run inside doSwap()'s synchronous
|
|
60
|
+
// commit section so deferred callbacks cannot fire against orphan elements.
|
|
61
61
|
import { cancelPendingIslands, mountNewIslands, unmountIslands } from "@takazudo/zfb/runtime";
|
|
62
62
|
// Adapter-specific internal fetch headers. zfb adapters do not currently expose this
|
|
63
63
|
// contract — the empty object preserves the call-shape so the loop in fetchHTML is a
|
|
@@ -460,7 +460,7 @@ function preloadStyleLinks(newDocument) {
|
|
|
460
460
|
// if !popstate, update the history entry and scroll position according to toLocation
|
|
461
461
|
// if popState is given, this holds the scroll position for history navigation
|
|
462
462
|
// if fallback === "animate" then simulate view transitions
|
|
463
|
-
async function updateDOM(preparationEvent, options, currentTransition, historyState, fallback,
|
|
463
|
+
async function updateDOM(preparationEvent, options, currentNavigation, currentTransition, historyState, fallback,
|
|
464
464
|
// Forwarded to moveToLocation() — see its parameter doc. #1398.
|
|
465
465
|
historyCommittedEarly = false) {
|
|
466
466
|
async function animate(phase) {
|
|
@@ -493,40 +493,71 @@ historyCommittedEarly = false) {
|
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
495
|
};
|
|
496
|
+
const finishAbortedUpdate = () => {
|
|
497
|
+
notifyNavigationAborted(currentNavigation);
|
|
498
|
+
// Simulation owns its `finished` promise explicitly. Resolve it on the
|
|
499
|
+
// no-swap path so transition cleanup still runs.
|
|
500
|
+
currentTransition.viewTransitionFinished?.();
|
|
501
|
+
return "aborted";
|
|
502
|
+
};
|
|
503
|
+
// A newer navigation or popstate may have won after preparation/history
|
|
504
|
+
// commit but before the update callback started.
|
|
505
|
+
if (preparationEvent.signal.aborted)
|
|
506
|
+
return finishAbortedUpdate();
|
|
496
507
|
const pageTitleForBrowserHistory = document.title; // document.title will be overridden by swap()
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
unmountIslands(document.body, preparationEvent.newDocument.body);
|
|
512
|
-
const swapEvent = await doSwap(preparationEvent, currentTransition.viewTransition, animateFallbackOld);
|
|
508
|
+
const swapResult = await doSwap(preparationEvent, currentTransition.viewTransition, animateFallbackOld, () => {
|
|
509
|
+
// Teardown and swap are a synchronous point-of-no-return. doSwap checks
|
|
510
|
+
// the signal after the animation await and invokes this callback only for
|
|
511
|
+
// a navigation that still owns the commit.
|
|
512
|
+
currentNavigation.domCommitStarted = true;
|
|
513
|
+
cancelPendingIslands();
|
|
514
|
+
// Unmount mounted islands on the OLD body before the swap so Preact/React
|
|
515
|
+
// trees receive render(null, element) / root.unmount() and their useEffect
|
|
516
|
+
// cleanups fire. Pass the incoming body so persisted islands are kept.
|
|
517
|
+
unmountIslands(document.body, preparationEvent.newDocument.body);
|
|
518
|
+
});
|
|
519
|
+
if (!swapResult.swapped)
|
|
520
|
+
return finishAbortedUpdate();
|
|
521
|
+
const swapEvent = swapResult.event;
|
|
513
522
|
moveToLocation(swapEvent.to, swapEvent.from, options, pageTitleForBrowserHistory, historyState, historyCommittedEarly);
|
|
514
523
|
triggerEvent("zfb:after-swap");
|
|
515
524
|
// Resolve the finished promise of the simulation's ViewTransition.
|
|
516
525
|
// For 'animate', wait for the new-page animation to complete first.
|
|
517
526
|
// For other fallback modes (e.g. 'swap'), resolve immediately — no animation needed.
|
|
518
|
-
if (fallback === "animate" && !currentTransition.transitionSkipped
|
|
527
|
+
if (fallback === "animate" && !currentTransition.transitionSkipped) {
|
|
519
528
|
animate("new").finally(() => currentTransition.viewTransitionFinished());
|
|
520
529
|
}
|
|
521
530
|
else {
|
|
522
531
|
currentTransition.viewTransitionFinished?.();
|
|
523
532
|
}
|
|
533
|
+
return "swapped";
|
|
534
|
+
}
|
|
535
|
+
function notifyNavigationAborted(navigation) {
|
|
536
|
+
// From island teardown onward the navigation is committed and must finish,
|
|
537
|
+
// even if an overridden event.swap() synchronously starts a newer one.
|
|
538
|
+
if (navigation.domCommitStarted || navigation.abortEventEmitted)
|
|
539
|
+
return;
|
|
540
|
+
navigation.abortEventEmitted = true;
|
|
541
|
+
triggerEvent("zfb:navigation-aborted");
|
|
524
542
|
}
|
|
525
543
|
function abortAndRecreateMostRecentNavigation() {
|
|
526
|
-
mostRecentNavigation
|
|
527
|
-
|
|
544
|
+
const previousNavigation = mostRecentNavigation;
|
|
545
|
+
const nextNavigation = {
|
|
528
546
|
controller: new AbortController(),
|
|
529
|
-
|
|
547
|
+
domCommitStarted: false,
|
|
548
|
+
abortEventEmitted: false,
|
|
549
|
+
};
|
|
550
|
+
// Install the new owner before abort/event dispatch. Either operation can
|
|
551
|
+
// synchronously re-enter navigate(); that newer call must remain the winner.
|
|
552
|
+
mostRecentNavigation = nextNavigation;
|
|
553
|
+
if (previousNavigation) {
|
|
554
|
+
previousNavigation.controller.abort();
|
|
555
|
+
// Native View Transitions may settle updateCallbackDone without ever
|
|
556
|
+
// invoking the skipped update callback. Emit here so updateDOM is not the
|
|
557
|
+
// only observer of a real pre-commit abort.
|
|
558
|
+
notifyNavigationAborted(previousNavigation);
|
|
559
|
+
}
|
|
560
|
+
return nextNavigation;
|
|
530
561
|
}
|
|
531
562
|
async function transition(direction, from, to, options, historyState, hasUAVisualTransition = false) {
|
|
532
563
|
// The most recent navigation always has precedence
|
|
@@ -535,6 +566,10 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
535
566
|
// e.g. by calling navigate() from a transition event.
|
|
536
567
|
// Invariant: all but the most recent navigation are already aborted.
|
|
537
568
|
const currentNavigation = abortAndRecreateMostRecentNavigation();
|
|
569
|
+
// Emitting the previous navigation's abort event is synchronous. A listener
|
|
570
|
+
// may have started an even newer navigation before this call resumes.
|
|
571
|
+
if (currentNavigation.controller.signal.aborted)
|
|
572
|
+
return;
|
|
538
573
|
// not ours
|
|
539
574
|
if (!transitionEnabledOnThisPage() || location.origin !== to.origin) {
|
|
540
575
|
if (currentNavigation === mostRecentNavigation)
|
|
@@ -567,7 +602,7 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
567
602
|
if (prepEvent.defaultPrevented || prepEvent.signal.aborted) {
|
|
568
603
|
if (currentNavigation === mostRecentNavigation)
|
|
569
604
|
mostRecentNavigation = undefined;
|
|
570
|
-
|
|
605
|
+
notifyNavigationAborted(currentNavigation);
|
|
571
606
|
if (!prepEvent.signal.aborted) {
|
|
572
607
|
// not aborted -> delegate to browser
|
|
573
608
|
location.href = to.href;
|
|
@@ -671,6 +706,7 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
671
706
|
if (prepEvent.signal.aborted) {
|
|
672
707
|
if (currentNavigation === mostRecentNavigation)
|
|
673
708
|
mostRecentNavigation = undefined;
|
|
709
|
+
notifyNavigationAborted(currentNavigation);
|
|
674
710
|
return;
|
|
675
711
|
}
|
|
676
712
|
document.documentElement.setAttribute(DIRECTION_ATTR, prepEvent.direction);
|
|
@@ -722,10 +758,13 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
722
758
|
safePushState({ ...options.state, index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 }, "", prepEvent.to.href);
|
|
723
759
|
}
|
|
724
760
|
}
|
|
761
|
+
let domUpdateOutcome;
|
|
725
762
|
if (supportsViewTransitions && !hasUAVisualTransition) {
|
|
726
763
|
// This automatically cancels any previous transition
|
|
727
764
|
// We also already took care that the earlier update callback got through
|
|
728
|
-
currentTransition.viewTransition = document.startViewTransition(async () =>
|
|
765
|
+
currentTransition.viewTransition = document.startViewTransition(async () => {
|
|
766
|
+
domUpdateOutcome = await updateDOM(prepEvent, options, currentNavigation, currentTransition, historyState, undefined, historyCommittedEarly);
|
|
767
|
+
});
|
|
729
768
|
}
|
|
730
769
|
else {
|
|
731
770
|
// Simulation mode requires a bit more manual work.
|
|
@@ -735,7 +774,7 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
735
774
|
const updateDone = (async () => {
|
|
736
775
|
// Immediately paused to set up the ViewTransition object for Fallback mode
|
|
737
776
|
await Promise.resolve(); // hop through the micro task queue
|
|
738
|
-
await updateDOM(prepEvent, options, currentTransition, historyState, hasUAVisualTransition ? "swap" : getFallback(), historyCommittedEarly);
|
|
777
|
+
domUpdateOutcome = await updateDOM(prepEvent, options, currentNavigation, currentTransition, historyState, hasUAVisualTransition ? "swap" : getFallback(), historyCommittedEarly);
|
|
739
778
|
return undefined;
|
|
740
779
|
})();
|
|
741
780
|
// When the updateDone promise is settled,
|
|
@@ -766,6 +805,8 @@ async function transition(direction, from, to, options, historyState, hasUAVisua
|
|
|
766
805
|
// In earlier versions was then'ed on viewTransition.ready which would not execute
|
|
767
806
|
// if the visual part of the transition has errors or was skipped
|
|
768
807
|
currentTransition.viewTransition?.updateCallbackDone.finally(async () => {
|
|
808
|
+
if (domUpdateOutcome !== "swapped")
|
|
809
|
+
return;
|
|
769
810
|
await runScripts();
|
|
770
811
|
// Mount new island markers introduced by the body swap. Fire-and-forget;
|
|
771
812
|
// each island's scheduleHydrate call is async (idle / visible). Called after
|