Skip to content

Conversation

MichaelWest22
Copy link
Collaborator

@MichaelWest22 MichaelWest22 commented Sep 3, 2025

Description

Fix htmx:abort Event Handling in Shadow DOM

Problem

The htmx abort functionality was broken when used inside Shadow DOM due to event retargeting. When events bubble up from inside Shadow DOM, the browser automatically retargets evt.target to point to the shadow host element instead of the original element that triggered the event. This caused the abort handler to try to abort the wrong element's request.

Root Cause

The htmx:abort event handler was using evt.target to identify which element's request to abort:

In Shadow DOM, evt.target points to the shadow host element, not the actual button inside the shadow DOM that triggered the event.

Solution
htmx events include the original triggering element in evt.detail.elt. The fix uses this property with a fallback to maintain backward compatibility:

body.addEventListener('htmx:abort', function(evt) {
  const target = (/** @type {CustomEvent} */(evt)).detail.elt || evt.target
  const internalData = getInternalData(target)
  if (internalData && internalData.xhr) {
    internalData.xhr.abort()
  }
})

Corresponding issue:
#3419

Testing

Added comprehensive tests: Added Shadow DOM tests for:

  • Basic hx-sync functionality
  • Programmatic abort via htmx.trigger()
  • hx-sync abort strategy

Checklist

  • I have read the contribution guidelines
  • I have targeted this PR against the correct branch (master for website changes, dev for
    source changes)
  • This is either a bugfix, a documentation update, or a new feature that has been explicitly
    approved via an issue
  • I ran the test suite locally (npm run test) and verified that it succeeded

@MichaelWest22 MichaelWest22 added bug Something isn't working ready for review Issues that are ready to be considered for merging labels Sep 3, 2025
)
body.addEventListener('htmx:abort', function(evt) {
const target = evt.target
const target = (/** @type {CustomEvent} */(evt)).detail.elt || evt.target
Copy link
Contributor

Choose a reason for hiding this comment

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

should we try evt.target first? This seems like it could break behavior people that were using detail.elt for other reasons as it is

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the issue is the evt.target is not reliable if used inside shadow dom as it tries to protect any shadowDOM element encapsulation by replacing it with the host wrapping elt.

If we look at the triggerEvent logic used by all proper htmx raised events you have this:

  function triggerEvent(elt, eventName, detail) {
    elt = resolveTarget(elt)
    if (detail == null) {
      detail = {}
    }
    detail.elt = elt
    const event = makeEvent(eventName, detail)

detail.elt is always overridden to be the triggered on elt which is the ideal elt we want to use for both shadow dom and normal use cases. It can never be user overridden to anything else in a htmx custom event.

It could be possible for someone to create htmx;abort events manually without using htmx and these would not have a valid detail.elt which is why i've left a || evt.target as a fallback just to be safe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working ready for review Issues that are ready to be considered for merging
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants