diff --git a/cypress/integration/mixins.spec.js b/cypress/integration/mixins.spec.js new file mode 100644 index 00000000..e2a49564 --- /dev/null +++ b/cypress/integration/mixins.spec.js @@ -0,0 +1,25 @@ +describe('KeyboardMixin', () => { + it('Should unbind event listeners that bound by the KeyboardMixin after the map is destroyed', () => { + cy.window().then((window) => { + const { map, document } = window; + + map.remove(); + + const isWindowBlurEventUnbound = !Object.entries( + window._leaflet_events + ).some(([name, handler]) => name.startsWith('blur') && handler); + expect( + isWindowBlurEventUnbound, + 'window blur event listener is not unbound' + ).to.eq(true); + + const isKeyUpDownEventUnbound = !Object.entries( + document._leaflet_events + ).some(([name, handler]) => name.startsWith('key') && handler); + expect( + isKeyUpDownEventUnbound, + 'document keyboard event listener is not unbound' + ).to.eq(true); + }); + }); +}); diff --git a/src/js/L.PM.Map.js b/src/js/L.PM.Map.js index 4566f2d9..fe76cbcb 100644 --- a/src/js/L.PM.Map.js +++ b/src/js/L.PM.Map.js @@ -5,7 +5,7 @@ import GlobalDragMode from './Mixins/Modes/Mode.Drag'; import GlobalRemovalMode from './Mixins/Modes/Mode.Removal'; import GlobalRotateMode from './Mixins/Modes/Mode.Rotate'; import EventMixin from './Mixins/Events'; -import KeyboardMixins from './Mixins/Keyboard'; +import createKeyboardMixins from './Mixins/Keyboard'; import { getRenderer } from './helpers'; const Map = L.Class.extend({ @@ -20,7 +20,7 @@ const Map = L.Class.extend({ this.map = map; this.Draw = new L.PM.Draw(map); this.Toolbar = new L.PM.Toolbar(map); - this.Keyboard = KeyboardMixins; + this.Keyboard = createKeyboardMixins(); this.globalOptions = { snappable: true, diff --git a/src/js/Mixins/Keyboard.js b/src/js/Mixins/Keyboard.js index 2908dfea..ff2c5f16 100644 --- a/src/js/Mixins/Keyboard.js +++ b/src/js/Mixins/Keyboard.js @@ -1,9 +1,17 @@ -const KeyboardMixins = { +// use function to create a new mixin object for keeping isolation +// to make it work for multiple map instances +const createKeyboardMixins = () => ({ _lastEvents: { keydown: undefined, keyup: undefined, current: undefined }, _initKeyListener(map) { this.map = map; L.DomEvent.on(document, 'keydown keyup', this._onKeyListener, this); L.DomEvent.on(window, 'blur', this._onBlur, this); + // clean up global listeners when current map instance is destroyed + map.once('unload', this._unbindKeyListenerEvents, this); + }, + _unbindKeyListenerEvents() { + L.DomEvent.off(document, 'keydown keyup', this._onKeyListener, this); + L.DomEvent.off(window, 'blur', this._onBlur, this); }, _onKeyListener(e) { let focusOn = 'document'; @@ -44,6 +52,6 @@ const KeyboardMixins = { getPressedKey() { return this._lastEvents.current?.event.key; }, -}; +}); -export default KeyboardMixins; +export default createKeyboardMixins;