Skip to content

Commit 8601543

Browse files
committed
fix: add global click handler for navigation
When using React router, clicks on anchors are intercepted only if Flow client has been initialized. In Hilla or hybrid application, if a Flow view is never navigated, clicking on such links cause the browser to reload the page instead of navigating to the expected view. This change registers a global click handler that intercepts click events and triggers a router navigation if necessary. The new behavior is aligned with vaadin-router. Fixes #20939
1 parent 6fe93c4 commit 8601543

File tree

3 files changed

+51
-5
lines changed
  • flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-resources/com/vaadin/flow/server/frontend
  • flow-server/src/main/resources/com/vaadin/flow/server/frontend

3 files changed

+51
-5
lines changed

flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-resources/com/vaadin/flow/server/frontend/Flow.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
*/
1616

1717
// Resource loaded from project dependency
18-
export const serverSideRoutes = []
18+
export const serverSideRoutes = [];
19+
export const registerGlobalClickHandler = () => {};

flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx

+47-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const router = {
2929
}
3030
};
3131

32+
const flowReact : { active: boolean } = {
33+
active: false,
34+
}
35+
3236
// ClickHandler for vaadin-router-go event is copied from vaadin/router click.js
3337
// @ts-ignore
3438
function getAnchorOrigin(anchor) {
@@ -55,7 +59,7 @@ function normalizeURL(url: URL): void | string {
5559
return '/' + url.href.slice(document.baseURI.length);
5660
}
5761

58-
function extractPath(event: MouseEvent): void | string {
62+
function extractURL(event: MouseEvent): void | URL {
5963
// ignore the click if the default action is prevented
6064
if (event.defaultPrevented) {
6165
return;
@@ -131,9 +135,45 @@ function extractPath(event: MouseEvent): void | string {
131135
return;
132136
}
133137

134-
return normalizeURL(new URL(anchor.href, anchor.baseURI));
138+
return new URL(anchor.href, anchor.baseURI);
135139
}
136140

141+
function extractPath(event: MouseEvent): void | string {
142+
const url = extractURL(event);
143+
if (!url) {
144+
return;
145+
}
146+
return normalizeURL(url);
147+
}
148+
149+
export const registerGlobalClickHandler = () => {
150+
window.addEventListener('click', (event: MouseEvent) => {
151+
if (flowReact.active) {
152+
return;
153+
}
154+
const url = extractURL(event);
155+
if (!url) {
156+
return;
157+
}
158+
// ignore click if baseURI does not match the document (external)
159+
if (!url.href.startsWith(document.baseURI)) {
160+
return;
161+
}
162+
if (event && event.preventDefault) {
163+
event.preventDefault();
164+
}
165+
166+
// Normalize path against baseURI
167+
const path = url.pathname + url.search + url.hash;
168+
const state = {...window.history.state}
169+
if (state.idx !== undefined) {
170+
state.idx = state.idx + 1;
171+
}
172+
window.history.pushState(state, '', path);
173+
window.dispatchEvent(new PopStateEvent('popstate'));
174+
}, { capture: false });
175+
};
176+
137177
/**
138178
* Fire 'vaadin-navigated' event to inform components of navigation.
139179
* @param pathname pathname of navigation
@@ -352,7 +392,6 @@ function Flow() {
352392
if (event && event.preventDefault) {
353393
event.preventDefault();
354394
}
355-
356395
navigated.current = false;
357396
// When navigation is triggered by click on a link, fromAnchor is set to true
358397
// in order to get a server round-trip even when navigating to the same URL again
@@ -417,10 +456,15 @@ function Flow() {
417456
}, [vaadinRouterGoEventHandler, vaadinNavigateEventHandler]);
418457

419458
useEffect(() => {
459+
window.addEventListener('click', navigateEventHandler);
460+
flowReact.active = true;
461+
420462
return () => {
421463
containerRef.current?.parentNode?.removeChild(containerRef.current);
422464
containerRef.current?.removeEventListener('flow-portal-add', addPortalEventHandler as EventListener);
423465
containerRef.current = undefined;
466+
window.removeEventListener('click', navigateEventHandler);
467+
flowReact.active = false;
424468
};
425469
}, []);
426470

@@ -533,7 +577,6 @@ function Flow() {
533577
if (outlet && outlet !== container.parentNode) {
534578
outlet.append(container);
535579
container.addEventListener('flow-portal-add', addPortalEventHandler as EventListener);
536-
window.addEventListener('click', navigateEventHandler);
537580
containerRef.current = container;
538581
}
539582
return container.onBeforeEnter?.call(

flow-server/src/main/resources/com/vaadin/flow/server/frontend/vaadin-react.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { routes } from "%routesJsImportPath%";
2+
import { registerGlobalClickHandler } from "Frontend/generated/flow/Flow.js";
23

34
(window as any).Vaadin ??= {};
45
(window as any).Vaadin.routesConfig = routes;
6+
registerGlobalClickHandler();
57

68
export { routes as forHMROnly };
79

0 commit comments

Comments
 (0)