Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function isLocalURL(url) {
export function bootstrap() {
console.assert(
!!window,
"boostrap error: must be called from within a browser context",
"bootstrap error: must be called from within a browser context",
);
const link = window.document.querySelector(
'head link[rel*="alternate"][type="application/vnd.api+json"]',
Expand All @@ -93,7 +93,35 @@ export function bootstrap() {
)
: new URL(link.getAttribute("href"));
const client = new Client(initialURL.href);
// Register a global listener for history updates.

// Add click event listener to div#app
const appDiv = document.getElementById("app");
console.assert(!!appDiv, "bootstrap error: missing div#app element");
appDiv.addEventListener("click", (event) => {
// Check if the target is an anchor element
if (!(event.target instanceof HTMLAnchorElement)) return;

const anchor = event.target;
Comment on lines +102 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too strict, for example the markup could be something like <a href=""><div class="title">Click me</div></a> Then the event.target would be the div.title instead of the anchor and this will return.

Suggested change
if (!(event.target instanceof HTMLAnchorElement)) return;
const anchor = event.target;
const anchor = event.target instanceof HTMLAnchorElement ? event.target : event.target.closest('a');
if (!anchor) return;

const href = anchor.href; // Safe to access href directly
Copy link
Contributor

@zrpnr zrpnr Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the href property seems to always be absolute, let's use

Suggested change
const href = anchor.href; // Safe to access href directly
const href = anchor.getAttribute("href");


// Check if href is a relative URL
const isRelative = href &&
(href.startsWith("./") || href.startsWith("/") ||
!/^(?:[a-z]+:)?\/\//i.test(href));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this last regex probably could be simpler and just be http / https right? Is there some other protocol we need to match for?

Copy link
Member

@gabesullice gabesullice Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to check for more than just http, e.g. tel:3035559999or mailto:foo@example.com.

But, I agree that we could simplify. According to MDN, the href should already be parsed by the browser, so this might be all we need:

const isExternal = !href.startsWith(appDiv.baseURI);
if (isExternal) return;

(using appDiv.baseURI is intentional)

If relative href attributes are not already expanded to absolute URLs, we can normalize it ourselves with the browser's help:

const normalizedHREF = new URL(href, anchor.baseURI).href;

https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI

if (!isRelative) return;

// Check if anchor has a type attribute (opt-out)
if (anchor.hasAttribute("type")) return;

// Prevent default behavior and stop propagation
event.preventDefault();
event.stopPropagation();

// Call the client's follow function
client.follow(href, {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need to pass the default value IMO

Suggested change
client.follow(href, {});
client.follow(href);

});

// Register a global listener for history updates
addEventListener("popstate", (event) => {
// Not navigating without a state.
if (event.state) {
Expand All @@ -106,6 +134,7 @@ export function bootstrap() {
}
}
});

return client;
}

Expand Down