More convenience with condition chaining

This commit is contained in:
Andreas Hocevar
2020-06-10 13:05:19 +02:00
parent aa5de5db60
commit 3de2ea0624
5 changed files with 105 additions and 35 deletions

View File

@@ -13,6 +13,29 @@ import {assert} from '../asserts.js';
* @typedef {function(this: ?, import("../MapBrowserEvent.js").default): boolean} Condition * @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 * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when
* additionally the shift-key is pressed). * additionally the shift-key is pressed).

View File

@@ -3,9 +3,7 @@
*/ */
import Collection from './Collection.js'; import Collection from './Collection.js';
import DoubleClickZoom from './interaction/DoubleClickZoom.js'; import DoubleClickZoom from './interaction/DoubleClickZoom.js';
import DragPan, { import DragPan from './interaction/DragPan.js';
defaultCondition as dragPanDefaultCondition,
} from './interaction/DragPan.js';
import DragRotate from './interaction/DragRotate.js'; import DragRotate from './interaction/DragRotate.js';
import DragZoom from './interaction/DragZoom.js'; import DragZoom from './interaction/DragZoom.js';
import KeyboardPan from './interaction/KeyboardPan.js'; import KeyboardPan from './interaction/KeyboardPan.js';
@@ -14,7 +12,6 @@ import Kinetic from './Kinetic.js';
import MouseWheelZoom from './interaction/MouseWheelZoom.js'; import MouseWheelZoom from './interaction/MouseWheelZoom.js';
import PinchRotate from './interaction/PinchRotate.js'; import PinchRotate from './interaction/PinchRotate.js';
import PinchZoom from './interaction/PinchZoom.js'; import PinchZoom from './interaction/PinchZoom.js';
import {focusWithTabindex} from './events/condition.js';
export {default as DoubleClickZoom} from './interaction/DoubleClickZoom.js'; export {default as DoubleClickZoom} from './interaction/DoubleClickZoom.js';
export {default as DragAndDrop} from './interaction/DragAndDrop.js'; export {default as DragAndDrop} from './interaction/DragAndDrop.js';
@@ -114,11 +111,7 @@ export function defaults(opt_options) {
if (dragPan) { if (dragPan) {
interactions.push( interactions.push(
new DragPan({ new DragPan({
condition: options.onFocusOnly onFocusOnly: options.onFocusOnly,
? function (event) {
return focusWithTabindex(event) && dragPanDefaultCondition(event);
}
: undefined,
kinetic: kinetic, kinetic: kinetic,
}) })
); );
@@ -155,7 +148,7 @@ export function defaults(opt_options) {
if (mouseWheelZoom) { if (mouseWheelZoom) {
interactions.push( interactions.push(
new MouseWheelZoom({ new MouseWheelZoom({
condition: options.onFocusOnly ? focusWithTabindex : undefined, onFocusOnly: options.onFocusOnly,
duration: options.zoomDuration, duration: options.zoomDuration,
}) })
); );

View File

@@ -5,8 +5,13 @@ import PointerInteraction, {
centroid as centroidFromPointers, centroid as centroidFromPointers,
} from './Pointer.js'; } from './Pointer.js';
import {FALSE} from '../functions.js'; import {FALSE} from '../functions.js';
import {
chain,
focusWithTabindex,
noModifierKeys,
primaryAction,
} from '../events/condition.js';
import {easeOut} from '../easing.js'; import {easeOut} from '../easing.js';
import {noModifierKeys, primaryAction} from '../events/condition.js';
import { import {
rotate as rotateCoordinate, rotate as rotateCoordinate,
scale as scaleCoordinate, 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 * @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. * to indicate whether that event should be handled.
* Default is {@link module:ol/events/condition~noModifierKeys} and {@link module:ol/events/condition~primaryAction}. * 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, * @property {boolean} [onFocusOnly=false] When the map's target has a `tabindex` attribute set,
* {@link module:ol/events/condition~focus} will also be applied. * 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. * @property {import("../Kinetic.js").default} [kinetic] Kinetic inertia to apply to the pan.
*/ */
@@ -59,11 +64,17 @@ class DragPan extends PointerInteraction {
*/ */
this.panning_ = false; this.panning_ = false;
const condition = options.condition
? options.condition
: chain(noModifierKeys, primaryAction);
/** /**
* @private * @private
* @type {import("../events/condition.js").Condition} * @type {import("../events/condition.js").Condition}
*/ */
this.condition_ = options.condition ? options.condition : defaultCondition; this.condition_ = options.onFocusOnly
? chain(focusWithTabindex, condition)
: condition;
/** /**
* @private * @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; export default DragPan;

View File

@@ -4,7 +4,7 @@
import EventType from '../events/EventType.js'; import EventType from '../events/EventType.js';
import Interaction, {zoomByDelta} from './Interaction.js'; import Interaction, {zoomByDelta} from './Interaction.js';
import {DEVICE_PIXEL_RATIO, FIREFOX} from '../has.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'; import {clamp} from '../math.js';
/** /**
@@ -21,8 +21,8 @@ export const Mode = {
* takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a * takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
* boolean to indicate whether that event should be handled. Default is * boolean to indicate whether that event should be handled. Default is
* {@link module:ol/events/condition~always}. * {@link module:ol/events/condition~always}.
* In addition, if there is a `tabindex` attribute on the map element, * @property {boolean} [onFocusOnly=false] When the map's target has a `tabindex` attribute set,
* {@link module:ol/events/condition~focus} will also be applied. * the interaction will only handle events when the map has the focus.
* @property {number} [maxDelta=1] Maximum mouse wheel delta. * @property {number} [maxDelta=1] Maximum mouse wheel delta.
* @property {number} [duration=250] Animation duration in milliseconds. * @property {number} [duration=250] Animation duration in milliseconds.
* @property {number} [timeout=80] Mouse wheel timeout duration in milliseconds. * @property {number} [timeout=80] Mouse wheel timeout duration in milliseconds.
@@ -96,11 +96,15 @@ class MouseWheelZoom extends Interaction {
? options.constrainResolution ? options.constrainResolution
: false; : false;
const condition = options.condition ? options.condition : always;
/** /**
* @private * @private
* @type {import("../events/condition.js").Condition} * @type {import("../events/condition.js").Condition}
*/ */
this.condition_ = options.condition ? options.condition : always; this.condition_ = options.onFocusOnly
? chain(focusWithTabindex, condition)
: condition;
/** /**
* @private * @private

View File

@@ -1,7 +1,5 @@
import DoubleClickZoom from '../../../src/ol/interaction/DoubleClickZoom.js'; import DoubleClickZoom from '../../../src/ol/interaction/DoubleClickZoom.js';
import DragPan, { import DragPan from '../../../src/ol/interaction/DragPan.js';
defaultCondition,
} from '../../../src/ol/interaction/DragPan.js';
import Feature from '../../../src/ol/Feature.js'; import Feature from '../../../src/ol/Feature.js';
import GeoJSON from '../../../src/ol/format/GeoJSON.js'; import GeoJSON from '../../../src/ol/format/GeoJSON.js';
import ImageLayer from '../../../src/ol/layer/Image.js'; import ImageLayer from '../../../src/ol/layer/Image.js';
@@ -29,7 +27,6 @@ import {
useGeographic, useGeographic,
} from '../../../src/ol/proj.js'; } from '../../../src/ol/proj.js';
import {defaults as defaultInteractions} from '../../../src/ol/interaction.js'; import {defaults as defaultInteractions} from '../../../src/ol/interaction.js';
import {focusWithTabindex} from '../../../src/ol/events/condition.js';
describe('ol.Map', function () { describe('ol.Map', function () {
describe('constructor', function () { describe('constructor', function () {
@@ -705,7 +702,7 @@ describe('ol.Map', function () {
}); });
describe('create interactions', function () { describe('create interactions', function () {
let options; let options, event, hasTabIndex, hasFocus, isPrimary;
beforeEach(function () { beforeEach(function () {
options = { options = {
@@ -718,6 +715,33 @@ describe('ol.Map', function () {
pinchRotate: false, pinchRotate: false,
pinchZoom: 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 () { 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).useAnchor_).to.eql(false);
expect(interactions.item(0).condition_).to.be(TRUE); 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.onFocusOnly = true;
options.mouseWheelZoom = true; options.mouseWheelZoom = true;
const interactions = defaultInteractions(options); 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); const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1); expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(DragPan); 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 () { it('does not use the default condition when onFocusOnly option is set', function () {
options.onFocusOnly = true; options.onFocusOnly = true;
options.dragPan = true; options.dragPan = true;
const interactions = defaultInteractions(options); 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);
}); });
}); });