Skip to content

Commit

Permalink
fix(router): navigate when browser triggers the popstate event
Browse files Browse the repository at this point in the history
  • Loading branch information
smalluban committed Apr 5, 2024
1 parent 2812967 commit eaa1bc6
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 4 deletions.
32 changes: 28 additions & 4 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,30 @@ function connectRootRouter(host, invalidate, options) {
}
}

function handlePopstate() {
// URL have changed externally, eg. chrome.tabs.update API
if (!globalThis.history.state) {
const url = new URL(globalThis.location.href);
const entry = getEntryFromURL(url);

if (entry) {
globalThis.removeEventListener("popstate", handlePopstate);
globalThis.addEventListener(
"popstate",
() => {
globalThis.addEventListener("popstate", handlePopstate);
navigate(entry);
},
{ once: true },
);

globalThis.history.back();
}
} else {
flush();
}
}

function navigateBack(offset, entry, nextUrl) {
const state = globalThis.history.state;
const targetEntry = globalThis.history.state[offset];
Expand All @@ -916,7 +940,7 @@ function connectRootRouter(host, invalidate, options) {
const replace = (popStateEvent) => {
if (popStateEvent) {
globalThis.removeEventListener("popstate", replace);
globalThis.addEventListener("popstate", flush);
globalThis.addEventListener("popstate", handlePopstate);
}

const method = pushOffset ? "pushState" : "replaceState";
Expand All @@ -928,7 +952,7 @@ function connectRootRouter(host, invalidate, options) {
};

if (offset) {
globalThis.removeEventListener("popstate", flush);
globalThis.removeEventListener("popstate", handlePopstate);
globalThis.addEventListener("popstate", replace);

globalThis.history.go(-offset);
Expand Down Expand Up @@ -1029,14 +1053,14 @@ function connectRootRouter(host, invalidate, options) {
}
}

globalThis.addEventListener("popstate", flush);
globalThis.addEventListener("popstate", handlePopstate);

host.addEventListener("click", handleNavigate);
host.addEventListener("submit", handleNavigate);
host.addEventListener("navigate", executeNavigate);

return () => {
globalThis.removeEventListener("popstate", flush);
globalThis.removeEventListener("popstate", handlePopstate);

host.removeEventListener("click", handleNavigate);
host.removeEventListener("submit", handleNavigate);
Expand Down
53 changes: 53 additions & 0 deletions test/spec/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,59 @@ describe("router:", () => {
).toBe("");
}));

it("navigates when browser pops a new url within the router", () =>
resolveTimeout(() => {
expect(hybrids(host.views[0])).toBe(RootView);

window.history.pushState(
null,
"",
window.location.pathname + "#@test-router-child-view",
);
window.history.pushState(
null,
"",
window.location.pathname + "#@test-router-child-view",
);

return resolveTimeout(() => {
history.back();

return resolveTimeout(() => {
expect(hybrids(host.views[0])).toBe(ChildView);
history.back();
return resolveTimeout(() => {
expect(hybrids(host.views[0])).toBe(RootView);
});
});
});
}));

it("navigates when browser pops a new url outside the router", () =>
resolveTimeout(() => {
expect(hybrids(host.views[0])).toBe(RootView);

window.history.pushState(
null,
"",
window.location.pathname + "#nope",
);

window.history.pushState(
null,
"",
window.location.pathname + "#nope",
);

history.back();

return resolveTimeout(() => {
expect(hybrids(host.views[0])).toBe(RootView);
history.back();
return resolveTimeout(() => {});
});
}));

it("saves and restores scroll position preserving focused element", () => {
const input = host.querySelector("#input");
const root = document.scrollingElement;
Expand Down

0 comments on commit eaa1bc6

Please sign in to comment.