Skip to content

Commit e159c3a

Browse files
asynclizcopybara-github
authored andcommitted
chore: fix link buttons open twice
PiperOrigin-RevId: 879906530
1 parent 005b4b8 commit e159c3a

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

iconbutton/internal/icon-button.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,9 @@ export class IconButton extends iconButtonBaseClass implements FormSubmitter {
267267

268268
private renderRipple() {
269269
const isRippleDisabled = !this.href && (this.disabled || this.softDisabled);
270+
// TODO(b/310046938): use the same id for both elements
270271
return html`<md-ripple
271-
.control=${this}
272+
for=${this.href ? 'link' : nothing}
272273
?disabled="${isRippleDisabled}"></md-ripple>`;
273274
}
274275

internal/events/dispatch-hooks.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,17 @@ export function setupDispatchHooks(
154154
// Re-dispatch the event. We can't reuse `redispatchEvent()` since we
155155
// need to add the hooks to the copy before it's dispatched.
156156
isRedispatching = true;
157-
const dispatched = element.dispatchEvent(eventCopy);
157+
const composedTarget = event.composedPath()[0];
158+
if (
159+
event.type === 'click' &&
160+
(composedTarget as Partial<HTMLElement>)?.matches?.('a')
161+
) {
162+
// For legacy reasons, synthetic click events dispatching on
163+
// HTMLAnchorElement will trigger link behavior. Prevent this since
164+
// we will dispatch a copy of the same click event.
165+
event.preventDefault();
166+
}
167+
const dispatched = event.composedPath()[0].dispatchEvent(eventCopy);
158168
isRedispatching = false;
159169
if (!dispatched) {
160170
event.preventDefault();

internal/events/dispatch-hooks_test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,42 @@ describe('dispatch hooks', () => {
3939
.withContext('element.addEventListener')
4040
.toHaveBeenCalledTimes(3);
4141
});
42+
43+
it('triggers internal event listeners when a composed element is the source of the event', () => {
44+
const shadowRoot = element.attachShadow({mode: 'open'});
45+
const composedElement = document.createElement('button');
46+
shadowRoot.appendChild(composedElement);
47+
const innerClickListener = jasmine.createSpy('innerClickListener');
48+
composedElement.addEventListener('click', innerClickListener);
49+
50+
setupDispatchHooks(element, 'click');
51+
composedElement.click();
52+
53+
expect(innerClickListener)
54+
.withContext('innerClickListener')
55+
.toHaveBeenCalledTimes(1);
56+
});
57+
58+
it('should not trigger activation behavior for clicks coming from inner <a> elements', () => {
59+
const shadowRoot = element.attachShadow({mode: 'open'});
60+
const anchorElement = document.createElement('a');
61+
anchorElement.href = '#';
62+
shadowRoot.appendChild(anchorElement);
63+
64+
setupDispatchHooks(element, 'click');
65+
66+
const clickEvent = new MouseEvent('click', {
67+
bubbles: true,
68+
cancelable: true,
69+
composed: true,
70+
});
71+
72+
anchorElement.dispatchEvent(clickEvent);
73+
74+
expect(clickEvent.defaultPrevented)
75+
.withContext('clickEvent.defaultPrevented')
76+
.toBeTrue();
77+
});
4278
});
4379

4480
describe('afterDispatch()', () => {

0 commit comments

Comments
 (0)