From 96e97682e05f9cf6c3f155a891a59b6e50d06518 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 12 Apr 2023 14:33:09 +0200 Subject: [PATCH] handle event listener registration before mount; handle unregister --- src/runtime/internal/Component.ts | 28 ++++++++++++++++++++- test/custom-elements/samples/events/test.js | 19 ++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 0772d98f6c..b30ca481c5 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -152,6 +152,8 @@ if (typeof HTMLElement === 'function') { private $$data = {}; private $$reflecting = false; private $$props_definition: Record = {}; + private $$listeners: Record = {}; + private $$listener_unsubscribe_fns = new Map(); constructor( private $$componentCtor: ComponentType, @@ -165,10 +167,26 @@ if (typeof HTMLElement === 'function') { // We can't determine upfront if the event is a custom event or not, so we have to // listen to both. If someone uses a custom event with the same name as a regular // browser event, this fires twice - we can't avoid that. - this.$$component!.$on(type, listener); + this.$$listeners[type] = this.$$listeners[type] || []; + this.$$listeners[type].push(listener); + if (this.$$component) { + const unsub = this.$$component!.$on(type, listener); + this.$$listener_unsubscribe_fns.set(listener, unsub); + } super.addEventListener(type, listener, options); } + removeEventListener(type: string, listener: any, options?: any): void { + super.removeEventListener(type, listener, options); + if (this.$$component) { + const unsub = this.$$listener_unsubscribe_fns.get(listener); + if (unsub) { + unsub(); + this.$$listener_unsubscribe_fns.delete(listener); + } + } + } + async connectedCallback() { this.$$connected = true; if (!this.$$component) { @@ -228,6 +246,14 @@ if (typeof HTMLElement === 'function') { } } }); + + for (const type in this.$$listeners) { + for (const listener of this.$$listeners[type]) { + const unsub = this.$$component!.$on(type, listener); + this.$$listener_unsubscribe_fns.set(listener, unsub); + } + } + this.$$listeners = {}; } } diff --git a/test/custom-elements/samples/events/test.js b/test/custom-elements/samples/events/test.js index ec4fcd2796..ba87fe8f57 100644 --- a/test/custom-elements/samples/events/test.js +++ b/test/custom-elements/samples/events/test.js @@ -4,10 +4,20 @@ import './main.svelte'; export default async function (target) { target.innerHTML = ''; - await tick(); const el = target.querySelector('custom-element'); const events = []; + const custom_before = () => { + events.push('before'); + }; + const click_before = () => { + events.push('click_before'); + }; + el.addEventListener('custom', custom_before); + el.addEventListener('click', click_before); + + await tick(); + el.addEventListener('custom', e => { events.push(e.detail); }); @@ -16,5 +26,10 @@ export default async function (target) { }); el.shadowRoot.querySelector('button').click(); - assert.deepEqual(events, ['foo', 'click']); + assert.deepEqual(events, ['before', 'foo', 'click_before', 'click']); + + el.removeEventListener('custom', custom_before); + el.removeEventListener('click', click_before); + el.shadowRoot.querySelector('button').click(); + assert.deepEqual(events, ['before', 'foo', 'click_before', 'click', 'foo', 'click']); }