diff --git a/src/mapInteractor.js b/src/mapInteractor.js index a9843dc66f..414651a1e8 100644 --- a/src/mapInteractor.js +++ b/src/mapInteractor.js @@ -25,8 +25,8 @@ var Mousetrap = require('mousetrap'); * event must occur within this time in milliseconds of the mouse down * event for it to be considered a click. * @property {boolean|number} [click.cancelOnMove=true] If true, don't generate - * click events if the mouse moved at all. If a positive number, the distance - * at which to cancel click events when the mouse moves. + * click events if the mouse moved at all. If a positive number, the + * distance at which to cancel click events when the mouse moves. * @property {object} [keyboard] An object describing which keyboard events are * handled. * @property {object} [keyboard.actions] An object with different actions that @@ -39,6 +39,9 @@ var Mousetrap = require('mousetrap'); * an object with keys of the meta keys that are required to be down or * up for that scale action to trigger. If the value of the meta key is * truthy, it must be down. If `false`, it must be up. + * @property {string[]} [keyboard.metakeyMouseEvents] A list of meta keys + * * that, when typed singly, trigger a repeat of the last mousemove or + * actionmove event so that listeners can update metakey information. * @property {boolean} [keyboard.focusHighlight=true] If truthy, when the map * gains focus, a highlight style is shown around it. This gives an * indicator that keyboard events will affect the map, but may not be @@ -294,6 +297,10 @@ var mapInteractor = function (args) { 1: {shift: true, ctrl: true}, 2: {shift: true, ctrl: false} }, + /* This is a list of meta keys that when typed singly trigger a repeat + * of the last mousemove or actionmove event so that listeners can + * update metakey information. */ + metakeyMouseEvents: ['shift', 'ctrl', 'shift+ctrl', 'shift+alt', 'shift+meta'], /* if focusHighlight is truthy, then a class is added to the map such * that when the map gets focus, it is indicated inside the border of * the map -- browsers usually show focus on the outside, which isn't @@ -648,6 +655,35 @@ var mapInteractor = function (args) { } }; + /** + * Repeat a mousemove or actionmove event with new metakey information. + * + * @param {event} [evt] A Mousetrap event with the key change. + * @fires geo.event.mousemove + * @fires geo.event.actionmove + */ + this._repeatMouseMoveEvent = function (evt) { + if (!m_mouse || !m_mouse.modifiers) { + return; + } + const old = { + alt: m_mouse.modifiers.alt, + ctrl: m_mouse.modifiers.ctrl, + meta: m_mouse.modifiers.meta, + shift: m_mouse.modifiers.shift + }; + m_this._getMouseModifiers(evt); + if (m_mouse.modifiers.alt === old.alt && m_mouse.modifiers.ctrl === old.ctrl && m_mouse.modifiers.meta === old.meta && m_mouse.modifiers.shift === old.shift) { + return; + } + if (m_state.boundDocumentHandlers) { + m_this.map().geoTrigger(geo_event.actionmove, { + state: m_this.state(), mouse: m_this.mouse(), event: evt}); + } else { + m_this.map().geoTrigger(geo_event.mousemove, m_this.mouse()); + } + }; + /** * Retrigger a mouse movement with the current mouse state. * @fires geo.event.mousemove @@ -711,6 +747,13 @@ var mapInteractor = function (args) { bound = bound.concat(m_options.keyboard.actions[keyAction]); } } + if (m_options.keyboard.metakeyMouseEvents) { + m_options.keyboard.metakeyMouseEvents.forEach((meta) => { + m_keyHandler.bind(meta, m_this._repeatMouseMoveEvent, 'keydown'); + m_keyHandler.bind(meta, m_this._repeatMouseMoveEvent, 'keyup'); + bound.push(meta); + }); + } m_boundKeys = bound; } $node.toggleClass( @@ -2095,7 +2138,7 @@ var mapInteractor = function (args) { preventDefault: function () {}, simulated: true }; - m_keyHandler.trigger(keys); + m_keyHandler.trigger(keys, options.event); return; } diff --git a/tests/cases/mapInteractor.js b/tests/cases/mapInteractor.js index 173563a84c..f128f76763 100644 --- a/tests/cases/mapInteractor.js +++ b/tests/cases/mapInteractor.js @@ -1296,6 +1296,32 @@ describe('mapInteractor', function () { }); }); + describe('metakey events', function () { + it('metakey events', function () { + var map = mockedMap('#mapNode1'), + interactor = geo.mapInteractor({ + map: map, + click: { + enabled: false + }, + throttle: false + }), triggered = 0; + map.geoOn(geo.event.mousemove, function () { + triggered += 1; + }); + expect(triggered).toBe(0); + interactor.simulateEvent('keyboard', {keys: 'a', event: 'keydown'}); + expect(triggered).toBe(0); + interactor.simulateEvent('keyboard', {keys: 'shift', shift: true, event: 'keydown'}); + expect(triggered).toBe(1); + interactor.simulateEvent( + 'mousemove', {map: {x: 199, y: 0}, button: 'left'}); + expect(triggered).toBe(2); + interactor.simulateEvent('keyboard', {keys: 'ctrl', ctrl: true, event: 'keydown'}); + expect(triggered).toBe(3); + }); + }); + describe('Public utility methods', function () { it('options', function () { var interactor = geo.mapInteractor();