diff --git a/src/ol/events/condition.js b/src/ol/events/condition.js index 573cab3b3d..f6551ac0fd 100644 --- a/src/ol/events/condition.js +++ b/src/ol/events/condition.js @@ -13,6 +13,29 @@ import {assert} from '../asserts.js'; * @typedef {function(this: ?, import("../MapBrowserEvent.js").default): boolean} Condition */ +/** + * Creates a condition function that passes when all provided conditions pass. + * @param {...Condition} var_args Conditions to check. + * @return {Condition} Condition function. + */ +export function all(var_args) { + const conditions = arguments; + /** + * @param {import("../MapBrowserEvent.js").default} event Event. + * @return {boolean} All conditions passed. + */ + return function (event) { + let pass = true; + for (let i = 0, ii = conditions.length; i < ii; ++i) { + pass = pass && conditions[i](event); + if (!pass) { + break; + } + } + return pass; + }; +} + /** * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when * additionally the shift-key is pressed). diff --git a/src/ol/interaction.js b/src/ol/interaction.js index feb41be8ff..285a966c9b 100644 --- a/src/ol/interaction.js +++ b/src/ol/interaction.js @@ -12,7 +12,6 @@ import Kinetic from './Kinetic.js'; import MouseWheelZoom from './interaction/MouseWheelZoom.js'; import PinchRotate from './interaction/PinchRotate.js'; import PinchZoom from './interaction/PinchZoom.js'; -import {focusWithTabindex} from './events/condition.js'; export {default as DoubleClickZoom} from './interaction/DoubleClickZoom.js'; export {default as DragAndDrop} from './interaction/DragAndDrop.js'; @@ -112,7 +111,7 @@ export function defaults(opt_options) { if (dragPan) { interactions.push( new DragPan({ - condition: options.onFocusOnly ? focusWithTabindex : undefined, + onFocusOnly: options.onFocusOnly, kinetic: kinetic, }) ); @@ -149,7 +148,7 @@ export function defaults(opt_options) { if (mouseWheelZoom) { interactions.push( new MouseWheelZoom({ - condition: options.onFocusOnly ? focusWithTabindex : undefined, + onFocusOnly: options.onFocusOnly, duration: options.zoomDuration, }) ); diff --git a/src/ol/interaction/DragPan.js b/src/ol/interaction/DragPan.js index 1d4f4daabb..e4a7d78efc 100644 --- a/src/ol/interaction/DragPan.js +++ b/src/ol/interaction/DragPan.js @@ -5,8 +5,13 @@ import PointerInteraction, { centroid as centroidFromPointers, } from './Pointer.js'; import {FALSE} from '../functions.js'; +import { + all, + focusWithTabindex, + noModifierKeys, + primaryAction, +} from '../events/condition.js'; import {easeOut} from '../easing.js'; -import {noModifierKeys, primaryAction} from '../events/condition.js'; import { rotate as rotateCoordinate, scale as scaleCoordinate, @@ -17,8 +22,8 @@ import { * @property {import("../events/condition.js").Condition} [condition] A function that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a boolean * to indicate whether that event should be handled. * Default is {@link module:ol/events/condition~noModifierKeys} and {@link module:ol/events/condition~primaryAction}. - * In addition, if there is a `tabindex` attribute on the map element, - * {@link module:ol/events/condition~focus} will also be applied. + * @property {boolean} [onFocusOnly=false] When the map's target has a `tabindex` attribute set, + * the interaction will only handle events when the map has the focus. * @property {import("../Kinetic.js").default} [kinetic] Kinetic inertia to apply to the pan. */ @@ -59,11 +64,17 @@ class DragPan extends PointerInteraction { */ this.panning_ = false; + const condition = options.condition + ? options.condition + : all(noModifierKeys, primaryAction); + /** * @private * @type {import("../events/condition.js").Condition} */ - this.condition_ = options.condition ? options.condition : defaultCondition; + this.condition_ = options.onFocusOnly + ? all(focusWithTabindex, condition) + : condition; /** * @private @@ -175,12 +186,4 @@ class DragPan extends PointerInteraction { } } -/** - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Browser event. - * @return {boolean} Combined condition result. - */ -function defaultCondition(mapBrowserEvent) { - return noModifierKeys(mapBrowserEvent) && primaryAction(mapBrowserEvent); -} - export default DragPan; diff --git a/src/ol/interaction/MouseWheelZoom.js b/src/ol/interaction/MouseWheelZoom.js index 0a69ee7ee6..3b9167674b 100644 --- a/src/ol/interaction/MouseWheelZoom.js +++ b/src/ol/interaction/MouseWheelZoom.js @@ -4,7 +4,7 @@ import EventType from '../events/EventType.js'; import Interaction, {zoomByDelta} from './Interaction.js'; import {DEVICE_PIXEL_RATIO, FIREFOX} from '../has.js'; -import {always} from '../events/condition.js'; +import {all, always, focusWithTabindex} from '../events/condition.js'; import {clamp} from '../math.js'; /** @@ -21,8 +21,8 @@ export const Mode = { * takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a * boolean to indicate whether that event should be handled. Default is * {@link module:ol/events/condition~always}. - * In addition, if there is a `tabindex` attribute on the map element, - * {@link module:ol/events/condition~focus} will also be applied. + * @property {boolean} [onFocusOnly=false] When the map's target has a `tabindex` attribute set, + * the interaction will only handle events when the map has the focus. * @property {number} [maxDelta=1] Maximum mouse wheel delta. * @property {number} [duration=250] Animation duration in milliseconds. * @property {number} [timeout=80] Mouse wheel timeout duration in milliseconds. @@ -96,11 +96,15 @@ class MouseWheelZoom extends Interaction { ? options.constrainResolution : false; + const condition = options.condition ? options.condition : always; + /** * @private * @type {import("../events/condition.js").Condition} */ - this.condition_ = options.condition ? options.condition : always; + this.condition_ = options.onFocusOnly + ? all(focusWithTabindex, condition) + : condition; /** * @private diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index bd73e397aa..fa7dec5949 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -27,7 +27,6 @@ import { useGeographic, } from '../../../src/ol/proj.js'; import {defaults as defaultInteractions} from '../../../src/ol/interaction.js'; -import {focusWithTabindex} from '../../../src/ol/events/condition.js'; describe('ol.Map', function () { describe('constructor', function () { @@ -703,7 +702,7 @@ describe('ol.Map', function () { }); describe('create interactions', function () { - let options; + let options, event, hasTabIndex, hasFocus, isPrimary; beforeEach(function () { options = { @@ -716,6 +715,33 @@ describe('ol.Map', function () { pinchRotate: false, pinchZoom: false, }; + hasTabIndex = true; + hasFocus = true; + isPrimary = true; + event = { + map: { + getTargetElement: function () { + return { + hasAttribute: function (attribute) { + return hasTabIndex; + }, + }; + }, + }, + originalEvent: { + isPrimary: isPrimary, + button: 0, + }, + target: { + getTargetElement: function () { + return { + contains: function () { + return hasFocus; + }, + }; + }, + }, + }; }); describe('create mousewheel interaction', function () { @@ -729,11 +755,19 @@ describe('ol.Map', function () { expect(interactions.item(0).useAnchor_).to.eql(false); expect(interactions.item(0).condition_).to.be(TRUE); }); - it('uses the focus condition when onFocusOnly option is set', function () { + it('does not use the default condition when onFocusOnly option is set', function () { options.onFocusOnly = true; options.mouseWheelZoom = true; const interactions = defaultInteractions(options); - expect(interactions.item(0).condition_).to.be(focusWithTabindex); + expect(interactions.item(0).condition_).to.not.be(TRUE); + hasTabIndex = true; + hasFocus = true; + expect(interactions.item(0).condition_(event)).to.be(true); + hasTabIndex = true; + hasFocus = false; + expect(interactions.item(0).condition_(event)).to.be(false); + hasTabIndex = false; + expect(interactions.item(0).condition_(event)).to.be(true); }); }); @@ -743,13 +777,28 @@ describe('ol.Map', function () { const interactions = defaultInteractions(options); expect(interactions.getLength()).to.eql(1); expect(interactions.item(0)).to.be.a(DragPan); - expect(interactions.item(0).condition_).to.not.be(focusWithTabindex); + expect(interactions.item(0).condition_(event)).to.be(true); + hasTabIndex = true; + hasFocus = false; + expect(interactions.item(0).condition_(event)).to.be(true); + event.originalEvent.altKey = true; + expect(interactions.item(0).condition_(event)).to.be(false); + delete event.originalEvent.altKey; + event.originalEvent.button = 1; + expect(interactions.item(0).condition_(event)).to.be(false); }); - it('uses the focus condition when onFocusOnly option is set', function () { + it('does not use the default condition when onFocusOnly option is set', function () { options.onFocusOnly = true; options.dragPan = true; const interactions = defaultInteractions(options); - expect(interactions.item(0).condition_).to.be(focusWithTabindex); + hasTabIndex = true; + hasFocus = true; + expect(interactions.item(0).condition_(event)).to.be(true); + hasTabIndex = true; + hasFocus = false; + expect(interactions.item(0).condition_(event)).to.be(false); + hasTabIndex = false; + expect(interactions.item(0).condition_(event)).to.be(true); }); });