mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 17:23:02 +00:00
235 lines
7.6 KiB
JavaScript
235 lines
7.6 KiB
JavaScript
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
|
|
import {
|
|
select as d3_select
|
|
} from 'd3-selection';
|
|
|
|
import { presetManager } from '../presets';
|
|
import { osmEntity, osmNote, QAItem } from '../osm';
|
|
import { utilKeybinding, utilRebind } from '../util';
|
|
|
|
/*
|
|
The hover behavior adds the `.hover` class on pointerover to all elements to which
|
|
the identical datum is bound, and removes it on pointerout.
|
|
|
|
The :hover pseudo-class is insufficient for iD's purposes because a datum's visual
|
|
representation may consist of several elements scattered throughout the DOM hierarchy.
|
|
Only one of these elements can have the :hover pseudo-class, but all of them will
|
|
have the .hover class.
|
|
*/
|
|
export function behaviorHover(context) {
|
|
var dispatch = d3_dispatch('hover');
|
|
var _selection = d3_select(null);
|
|
var _newNodeId = null;
|
|
var _initialNodeID = null;
|
|
var _altDisables;
|
|
var _ignoreVertex;
|
|
var _targets = [];
|
|
|
|
// use pointer events on supported platforms; fallback to mouse events
|
|
var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
|
|
|
|
|
|
function keydown(d3_event) {
|
|
if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
|
|
_selection.selectAll('.hover')
|
|
.classed('hover-suppressed', true)
|
|
.classed('hover', false);
|
|
|
|
_selection
|
|
.classed('hover-disabled', true);
|
|
|
|
dispatch.call('hover', this, null);
|
|
}
|
|
}
|
|
|
|
|
|
function keyup(d3_event) {
|
|
if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
|
|
_selection.selectAll('.hover-suppressed')
|
|
.classed('hover-suppressed', false)
|
|
.classed('hover', true);
|
|
|
|
_selection
|
|
.classed('hover-disabled', false);
|
|
|
|
dispatch.call('hover', this, _targets);
|
|
}
|
|
}
|
|
|
|
|
|
function behavior(selection) {
|
|
_selection = selection;
|
|
|
|
_targets = [];
|
|
|
|
if (_initialNodeID) {
|
|
_newNodeId = _initialNodeID;
|
|
_initialNodeID = null;
|
|
} else {
|
|
_newNodeId = null;
|
|
}
|
|
|
|
_selection
|
|
.on(_pointerPrefix + 'over.hover', pointerover)
|
|
.on(_pointerPrefix + 'out.hover', pointerout)
|
|
// treat pointerdown as pointerover for touch devices
|
|
.on(_pointerPrefix + 'down.hover', pointerover);
|
|
|
|
d3_select(window)
|
|
.on(_pointerPrefix + 'up.hover pointercancel.hover', pointerout, true)
|
|
.on('keydown.hover', keydown)
|
|
.on('keyup.hover', keyup);
|
|
|
|
|
|
function eventTarget(d3_event) {
|
|
var datum = d3_event.target && d3_event.target.__data__;
|
|
if (typeof datum !== 'object') return null;
|
|
if (!(datum instanceof osmEntity) && datum.properties && (datum.properties.entity instanceof osmEntity)) {
|
|
return datum.properties.entity;
|
|
}
|
|
return datum;
|
|
}
|
|
|
|
function pointerover(d3_event) {
|
|
// ignore mouse hovers with buttons pressed unless dragging
|
|
if (context.mode().id.indexOf('drag') === -1 &&
|
|
(!d3_event.pointerType || d3_event.pointerType === 'mouse') &&
|
|
d3_event.buttons) return;
|
|
|
|
var target = eventTarget(d3_event);
|
|
if (target && _targets.indexOf(target) === -1) {
|
|
_targets.push(target);
|
|
updateHover(d3_event, _targets);
|
|
}
|
|
}
|
|
|
|
function pointerout(d3_event) {
|
|
|
|
var target = eventTarget(d3_event);
|
|
var index = _targets.indexOf(target);
|
|
if (index !== -1) {
|
|
_targets.splice(index);
|
|
updateHover(d3_event, _targets);
|
|
}
|
|
}
|
|
|
|
function allowsVertex(d) {
|
|
return d.geometry(context.graph()) === 'vertex' || presetManager.allowsVertex(d, context.graph());
|
|
}
|
|
|
|
function modeAllowsHover(target) {
|
|
var mode = context.mode();
|
|
if (mode.id === 'add-point') {
|
|
return mode.preset.matchGeometry('vertex') ||
|
|
(target.type !== 'way' && target.geometry(context.graph()) !== 'vertex');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function updateHover(d3_event, targets) {
|
|
|
|
_selection.selectAll('.hover')
|
|
.classed('hover', false);
|
|
_selection.selectAll('.hover-suppressed')
|
|
.classed('hover-suppressed', false);
|
|
|
|
var mode = context.mode();
|
|
|
|
if (!_newNodeId && (mode.id === 'draw-line' || mode.id === 'draw-area')) {
|
|
var node = targets.find(function(target) {
|
|
return target instanceof osmEntity && target.type === 'node';
|
|
});
|
|
_newNodeId = node && node.id;
|
|
}
|
|
|
|
targets = targets.filter(function(datum) {
|
|
if (datum instanceof osmEntity) {
|
|
// If drawing a way, don't hover on a node that was just placed. #3974
|
|
return datum.id !== _newNodeId &&
|
|
(datum.type !== 'node' || !_ignoreVertex || allowsVertex(datum)) &&
|
|
modeAllowsHover(datum);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
var selector = '';
|
|
|
|
for (var i in targets) {
|
|
var datum = targets[i];
|
|
|
|
// What are we hovering over?
|
|
if (datum.__featurehash__) {
|
|
// hovering custom data
|
|
selector += ', .data' + datum.__featurehash__;
|
|
|
|
} else if (datum instanceof QAItem) {
|
|
selector += ', .' + datum.service + '.itemId-' + datum.id;
|
|
|
|
} else if (datum instanceof osmNote) {
|
|
selector += ', .note-' + datum.id;
|
|
|
|
} else if (datum instanceof osmEntity) {
|
|
selector += ', .' + datum.id;
|
|
if (datum.type === 'relation') {
|
|
for (var j in datum.members) {
|
|
selector += ', .' + datum.members[j].id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var suppressed = _altDisables && d3_event && d3_event.altKey;
|
|
|
|
if (selector.trim().length) {
|
|
// remove the first comma
|
|
selector = selector.slice(1);
|
|
_selection.selectAll(selector)
|
|
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
|
|
}
|
|
|
|
dispatch.call('hover', this, !suppressed && targets);
|
|
}
|
|
}
|
|
|
|
|
|
behavior.off = function(selection) {
|
|
selection.selectAll('.hover')
|
|
.classed('hover', false);
|
|
selection.selectAll('.hover-suppressed')
|
|
.classed('hover-suppressed', false);
|
|
selection
|
|
.classed('hover-disabled', false);
|
|
|
|
selection
|
|
.on(_pointerPrefix + 'over.hover', null)
|
|
.on(_pointerPrefix + 'out.hover', null)
|
|
.on(_pointerPrefix + 'down.hover', null);
|
|
|
|
d3_select(window)
|
|
.on(_pointerPrefix + 'up.hover pointercancel.hover', null, true)
|
|
.on('keydown.hover', null)
|
|
.on('keyup.hover', null);
|
|
};
|
|
|
|
|
|
behavior.altDisables = function(val) {
|
|
if (!arguments.length) return _altDisables;
|
|
_altDisables = val;
|
|
return behavior;
|
|
};
|
|
|
|
behavior.ignoreVertex = function(val) {
|
|
if (!arguments.length) return _ignoreVertex;
|
|
_ignoreVertex = val;
|
|
return behavior;
|
|
};
|
|
|
|
behavior.initialNodeID = function(nodeId) {
|
|
_initialNodeID = nodeId;
|
|
return behavior;
|
|
};
|
|
|
|
return utilRebind(behavior, dispatch, 'on');
|
|
}
|