diff --git a/internal/events/dispatch-hooks.ts b/internal/events/dispatch-hooks.ts index 9f27cab2a7..ae8b55db7e 100644 --- a/internal/events/dispatch-hooks.ts +++ b/internal/events/dispatch-hooks.ts @@ -154,6 +154,15 @@ export function setupDispatchHooks( // Re-dispatch the event. We can't reuse `redispatchEvent()` since we // need to add the hooks to the copy before it's dispatched. isRedispatching = true; + const composedPathIncludesAnchor = event + .composedPath() + .some((el) => (el as Partial)?.matches?.('a')); + if (event.type === 'click' && composedPathIncludesAnchor) { + // For legacy reasons, synthetic click events dispatching on + // HTMLAnchorElement will trigger link behavior. Prevent this since + // we will dispatch a copy of the same click event. + event.preventDefault(); + } const dispatched = event.composedPath()[0].dispatchEvent(eventCopy); isRedispatching = false; if (!dispatched) { diff --git a/internal/events/dispatch-hooks_test.ts b/internal/events/dispatch-hooks_test.ts index 3cb40da93b..7fc1c98cf6 100644 --- a/internal/events/dispatch-hooks_test.ts +++ b/internal/events/dispatch-hooks_test.ts @@ -54,6 +54,27 @@ describe('dispatch hooks', () => { .withContext('innerClickListener') .toHaveBeenCalledTimes(1); }); + + it('should not trigger activation behavior for clicks coming from inner elements', () => { + const shadowRoot = element.attachShadow({mode: 'open'}); + const anchorElement = document.createElement('a'); + anchorElement.href = '#'; + shadowRoot.appendChild(anchorElement); + + setupDispatchHooks(element, 'click'); + + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + composed: true, + }); + + anchorElement.dispatchEvent(clickEvent); + + expect(clickEvent.defaultPrevented) + .withContext('clickEvent.defaultPrevented') + .toBeTrue(); + }); }); describe('afterDispatch()', () => {