diff --git a/src/ol/events/condition.js b/src/ol/events/condition.js index 573cab3b3d..624cdf2e7c 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 is only fulfilled when a chain of conditions pass. + * @param {...Condition} var_args Conditions to check. + * @return {Condition} Condition function that checks a chain of conditions. + */ +export function chain(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 2b9651ebce..285a966c9b 100644 --- a/src/ol/interaction.js +++ b/src/ol/interaction.js @@ -3,9 +3,7 @@ */ import Collection from './Collection.js'; import DoubleClickZoom from './interaction/DoubleClickZoom.js'; -import DragPan, { - defaultCondition as dragPanDefaultCondition, -} from './interaction/DragPan.js'; +import DragPan from './interaction/DragPan.js'; import DragRotate from './interaction/DragRotate.js'; import DragZoom from './interaction/DragZoom.js'; import KeyboardPan from './interaction/KeyboardPan.js'; @@ -14,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'; @@ -114,11 +111,7 @@ export function defaults(opt_options) { if (dragPan) { interactions.push( new DragPan({ - condition: options.onFocusOnly - ? function (event) { - return focusWithTabindex(event) && dragPanDefaultCondition(event); - } - : undefined, + onFocusOnly: options.onFocusOnly, kinetic: kinetic, }) ); @@ -155,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 145b870bc0..432f271f8a 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 { + chain, + 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 + : chain(noModifierKeys, primaryAction); + /** * @private * @type {import("../events/condition.js").Condition} */ - this.condition_ = options.condition ? options.condition : defaultCondition; + this.condition_ = options.onFocusOnly + ? chain(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. - */ -export 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..4f23429cdd 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 {always, chain, 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 + ? chain(focusWithTabindex, condition) + : condition; /** * @private diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 9b32772315..fa7dec5949 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -1,7 +1,5 @@ import DoubleClickZoom from '../../../src/ol/interaction/DoubleClickZoom.js'; -import DragPan, { - defaultCondition, -} from '../../../src/ol/interaction/DragPan.js'; +import DragPan from '../../../src/ol/interaction/DragPan.js'; import Feature from '../../../src/ol/Feature.js'; import GeoJSON from '../../../src/ol/format/GeoJSON.js'; import ImageLayer from '../../../src/ol/layer/Image.js'; @@ -29,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 () { @@ -705,7 +702,7 @@ describe('ol.Map', function () { }); describe('create interactions', function () { - let options; + let options, event, hasTabIndex, hasFocus, isPrimary; beforeEach(function () { options = { @@ -718,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 () { @@ -731,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); }); }); @@ -745,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.be(defaultCondition); + 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('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.not.be(defaultCondition); + 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); }); });