Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/fix-shared-event-listener-removal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@remote-dom/core': patch
---

Fix `RemoteElement` not removing host event listeners when the same listener was added for more than one remote event.
25 changes: 9 additions & 16 deletions packages/core/source/elements/RemoteElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,6 @@ export abstract class RemoteElement<
return super.addEventListener(type, listener, options);
}

const remoteEvents = getRemoteEvents(this);
const remoteEvent = getRemoteEventRecord.call(this, type, {
property,
definition: listenerDefinition,
Expand All @@ -745,8 +744,7 @@ export abstract class RemoteElement<
remoteEvent,
];

remoteEvent.listeners.add(listener);
remoteEvents.listeners.set(listener, listenerRecord);
remoteEvent.listeners.set(listener, listenerRecord);

super.addEventListener(type, normalizedListener, options);

Expand All @@ -772,7 +770,9 @@ export abstract class RemoteElement<
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
) {
const listenerRecord = REMOTE_EVENTS.get(this)?.listeners.get(listener);
const listenerRecord = REMOTE_EVENTS.get(this)
?.events.get(type)
?.listeners.get(listener);
const normalizedListener = listenerRecord ? listenerRecord[0] : listener;

super.removeEventListener(type, normalizedListener, options);
Expand Down Expand Up @@ -828,17 +828,16 @@ const REMOTE_EVENTS = new WeakMap<
interface RemoteElementEventCache {
readonly events: Map<string, RemoteEventRecord>;
readonly properties: Map<string, ((event: any) => void) | null>;
readonly listeners: WeakMap<
EventListenerOrEventListenerObject,
RemoteEventListenerRecord
>;
}

interface RemoteEventRecord {
readonly name: string;
readonly property?: string;
readonly definition?: RemoteElementEventListenerDefinition;
readonly listeners: Set<EventListenerOrEventListenerObject>;
readonly listeners: Map<
EventListenerOrEventListenerObject,
RemoteEventListenerRecord
>;
dispatch(...args: any[]): unknown;
}

Expand All @@ -850,10 +849,6 @@ type RemoteEventListenerRecord = [
function getRemoteEvents(element: RemoteElement<any, any, any, any>): {
events: Map<string, RemoteEventRecord>;
properties: Map<string, ((event: any) => void) | null>;
listeners: WeakMap<
EventListenerOrEventListenerObject,
RemoteEventListenerRecord
>;
} {
let events = REMOTE_EVENTS.get(element);

Expand All @@ -862,7 +857,6 @@ function getRemoteEvents(element: RemoteElement<any, any, any, any>): {
events = {
events: new Map(),
properties: new Map(),
listeners: new WeakMap(),
};

REMOTE_EVENTS.set(element, events);
Expand All @@ -883,7 +877,7 @@ function getRemoteEventRecord(
name: type,
property,
definition,
listeners: new Set(),
listeners: new Map(),
dispatch: (...args: any[]) => {
const event =
definition?.dispatchEvent?.apply(this, args) ??
Expand Down Expand Up @@ -914,7 +908,6 @@ function removeRemoteListener(

const remoteEvent = listenerRecord[1];
remoteEvent.listeners.delete(listener);
remoteEvents.listeners.delete(listener);

if (remoteEvent.listeners.size > 0) return;

Expand Down
37 changes: 37 additions & 0 deletions packages/core/source/tests/elements.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,43 @@ describe('RemoteElement', () => {
expect(secondListener).not.toHaveBeenCalled();
});

it('removes an event listener that is shared between multiple events', () => {
const ButtonElement = createRemoteElement({
events: ['press', 'hover'],
});

const {element, receiver} = createAndConnectRemoteElement(ButtonElement);

const listener = vi.fn();

element.addEventListener('press', listener);
element.addEventListener('hover', listener);

element.removeEventListener('press', listener);

expect(receiver.connection.mutate).toHaveBeenLastCalledWith([
[
MUTATION_TYPE_UPDATE_PROPERTY,
remoteId(element),
'press',
undefined,
UPDATE_PROPERTY_TYPE_EVENT_LISTENER,
],
]);

element.removeEventListener('hover', listener);

expect(receiver.connection.mutate).toHaveBeenLastCalledWith([
[
MUTATION_TYPE_UPDATE_PROPERTY,
remoteId(element),
'hover',
undefined,
UPDATE_PROPERTY_TYPE_EVENT_LISTENER,
],
]);
});

it('removes an event listener declared with once', () => {
const ButtonElement = createRemoteElement({
events: ['press'],
Expand Down
Loading