Skip to content

Comments

Same-page anchor: Account for <a> nested in <svg>#1495

Open
seanpdoyle wants to merge 1 commit intohotwired:mainfrom
seanpdoyle:1285-follow-up
Open

Same-page anchor: Account for <a> nested in <svg>#1495
seanpdoyle wants to merge 1 commit intohotwired:mainfrom
seanpdoyle:1285-follow-up

Conversation

@seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Feb 2, 2026

Follow-up to #1285

While an <a> element is typically represented by an HTMLAnchorElement, an <a> nested inside an <svg> element is instead an SVGAElement.

Similarly, while HTMLAnchorElement.href returns a String, the SVGAElement.href returns an instance of SVGAnimatedString, which cannot be supported by neither calls to String.startsWith nor RegExp.test.

This commit adds explicit test coverage for a same-page navigation from an <a> nested within an <svg>. To ensure that the value is a String, replace .href with getAttribute("href"). This restores the implementation to be closer to what was defined in e591ea9e.

Note

It's worth mentioning that both of these new tests pass without the page.on("pageerror") parts are omitted from the coverage. That signals to me that either:

  1. the intended behavior still functions when JavaScript errors are raised,
  2. the test coverage does not genuinely reflect real-world use cases

I'm hopeful that it's 1., and that the test coverage with the explicit JavaScript error tracking sufficiently guards against both 1. and 2. being true.

@domchristie
Copy link
Contributor

@seanpdoyle
Copy link
Contributor Author

@domchristie interesting! Thanks for sharing that context. I'll copy-paste it here to keep those details as part of this conversation:

if (
  !(link instanceof HTMLAnchorElement) &&
  !(link instanceof SVGAElement) &&
  !(link instanceof HTMLAreaElement)
)
  return

// …

const linkTarget = link instanceof HTMLElement ? link.target : link.target.baseVal;
const href = link instanceof HTMLElement ? link.href : link.href.baseVal;

I like that approach in that there are some class-level checks. However, I worry about a future need of adding additional supported classes (in case we miss one now, or in case the "link" is an <a>-like custom HTML element of some kind).

For now, I think I prefer the getAttribute-based approach for two reasons: 1) it's closer to the original code, 2) it doesn't limit the classes supported.

Am I missing out on something about the alternative?

@domchristie
Copy link
Contributor

Am I missing out on something about the alternative?

No I don't think so. The use of baseVal was interesting to me, but I think getAttribute works well for both (as it does for the target attribute. I feel like some of the Astro code is to satisfy TypeScript, particularly the instanceof guards.

Copy link
Contributor

@packagethief packagethief left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this @seanpdoyle 🙏

src/util.js Outdated
const linkTarget = link.getAttribute("target")
if (linkTarget && linkTarget !== "_self") return null

if (/\A#/.test(link.getAttribute("href"))) return null
Copy link

@fschwahn fschwahn Feb 3, 2026

Choose a reason for hiding this comment

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

@seanpdoyle I tried to test this, but it seems JS regexp does not support \A as a start of string anchor, that's a ruby thing. (I think tests are failing because of this too).

/\A#/.test("#foo") // false
/\A#/.test("A#") // true

@fschwahn
Copy link

fschwahn commented Feb 3, 2026

I'm hopeful that it's 1., and that the test coverage with the explicit JavaScript error tracking sufficiently guards against both 1. and 2. being true.

I went back and tested this, and the claim that this broke links in SVGs was wrong, I'm sorry about that. In fact, the links still behave as intended, there's just noise from the errors in the browser console.

So I think (at least in my case) it is indeed 1.

@seanpdoyle seanpdoyle force-pushed the 1285-follow-up branch 2 times, most recently from f4419bb to 54de754 Compare February 3, 2026 19:07
Follow-up to [hotwired#1285][]

While an `<a>` element is typically represented by an
[HTMLAnchorElement][], an `<a>` nested inside an `<svg>` element is
instead an [SVGAElement][].

Similarly, while [HTMLAnchorElement.href][] returns a `String`, the
[SVGAElement.href][] returns an instance of [SVGAnimatedString][], which
cannot be supported by neither calls to [String.startsWith][] nor
[RegExp.test][].

This commit adds explicit test coverage for a same-page navigation from
an `<a>` nested within an `<svg>`. To ensure that the value is a
`String`, replace `.href` with `getAttribute("href")`. This restores the
implementation to be closer to what was defined in [e591ea9][].

[hotwired#1285]: hotwired#1285
[HTMLAnchorElement]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
[SVGAElement]: https://developer.mozilla.org/en-US/docs/Web/API/SVGAElement
[HTMLAnchorElement.href]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/href
[SVGAElement.href]: https://developer.mozilla.org/en-US/docs/Web/API/SVGAElement/href
[SVGAnimatedString]: https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedString
[String.startsWith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
[RegExp.test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
[e591ea9]: hotwired@e591ea9#diff-9c0ec1b0a889e599f3ff81590864dd9dc65684a86b41f1da75acc53fafed12e3R251-R256
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants