diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 694464a28..6c66802ae 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -19,13 +19,14 @@ export function behaviorSelect(context) { var _showMenu = false; var _p1 = null; var _downPointerId = null; - var _longPressTimeout; + var _longPressTimeout = null; + var _lastInteractionType = null; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; - function point() { - return utilFastMouse(context.container().node())(d3_event); + function point(event) { + return utilFastMouse(context.container().node())(event || d3_event); } @@ -40,7 +41,6 @@ export function behaviorSelect(context) { if (e && e.keyCode === 93) { // context menu e.preventDefault(); - e.stopPropagation(); } } @@ -57,7 +57,7 @@ export function behaviorSelect(context) { if (e && e.keyCode === 93) { // context menu e.preventDefault(); - e.stopPropagation(); + _lastInteractionType = 'menukey'; contextmenu(); } } @@ -70,21 +70,7 @@ export function behaviorSelect(context) { _downPointerId = d3_event.pointerId || 'mouse'; if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - - var node = this; - - _longPressTimeout = window.setTimeout(function didLongPress() { - // simulate context menu event - if (window.CustomEvent) { - node.dispatchEvent(new CustomEvent('contextmenu')); - } else if (document.createEvent) { - var e = document.createEvent('HTMLEvents'); - e.initEvent('contextmenu', true, false); - node.dispatchEvent(e); - } else { // IE - node.fireEvent('oncontextmenu'); - } - }, 500); + _longPressTimeout = window.setTimeout(didLongPress, 500, d3_event.pointerType || 'mouse'); _lastMouse = d3_event; @@ -95,6 +81,14 @@ export function behaviorSelect(context) { } + function didLongPress(pointerType) { + _longPressTimeout = null; + _lastInteractionType = 'longdown-' + pointerType; + _showMenu = true; + click(); + } + + function pointermove() { if (_downPointerId && _downPointerId !== (d3_event.pointerId || 'mouse')) return; @@ -116,7 +110,6 @@ export function behaviorSelect(context) { function contextmenu() { var e = d3_event; e.preventDefault(); - e.stopPropagation(); if (!+e.clientX && !+e.clientY) { if (_lastMouse) { @@ -124,6 +117,9 @@ export function behaviorSelect(context) { } else { return; } + } else { + _lastMouse = d3_event; + _lastInteractionType = 'rightclick'; } if (!_p1) { @@ -138,13 +134,13 @@ export function behaviorSelect(context) { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); if (!_p1) return; - var p2 = point(); + var p2 = point(_lastMouse); var dist = geoVecLength(_p1, p2); _p1 = null; if (dist > _tolerancePx) return; - var datum = d3_event.target.__data__ || (_lastMouse && _lastMouse.target.__data__); - var isMultiselect = d3_event.shiftKey || context.surface().select('.lasso').node(); + var datum = (d3_event && d3_event.target.__data__) || (_lastMouse && _lastMouse.target.__data__); + var isMultiselect = (d3_event && d3_event.shiftKey) || context.surface().select('.lasso').node(); processClick(datum, isMultiselect); } @@ -170,14 +166,14 @@ export function behaviorSelect(context) { if (!isMultiselect) { if (selectedIDs.length > 1 && (_showMenu && !_alwaysShowMenu)) { // multiple things already selected, just show the menu... - mode.reselect().showMenu(); + mode.reselect().showMenu(_lastInteractionType); } else { // always enter modeSelect even if the entity is already // selected since listeners may expect `context.enter` events, // e.g. in the walkthrough newMode = modeSelect(context, [datum.id]); context.enter(newMode); - if (_showMenu) newMode.showMenu(); + if (_showMenu) newMode.showMenu(_lastInteractionType); } } else { @@ -185,7 +181,7 @@ export function behaviorSelect(context) { // clicked entity is already in the selectedIDs list.. if (_showMenu && !_alwaysShowMenu) { // don't deselect clicked entity, just show the menu. - mode.reselect().showMenu(); + mode.reselect().showMenu(_lastInteractionType); } else { // deselect clicked entity, then reenter select mode or return to browse mode.. selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; }); @@ -197,7 +193,7 @@ export function behaviorSelect(context) { selectedIDs = selectedIDs.concat([datum.id]); newMode = modeSelect(context, selectedIDs); context.enter(newMode); - if (_showMenu) newMode.showMenu(); + if (_showMenu) newMode.showMenu(_lastInteractionType); } } @@ -234,6 +230,8 @@ export function behaviorSelect(context) { _showMenu = false; _p1 = null; _downPointerId = null; + _longPressTimeout = null; + _lastInteractionType = null; d3_select(window) .on('keydown.select', keydown) @@ -245,7 +243,6 @@ export function behaviorSelect(context) { var e = d3_event; if (+e.clientX === 0 && +e.clientY === 0) { d3_event.preventDefault(); - d3_event.stopPropagation(); } }); @@ -262,6 +259,8 @@ export function behaviorSelect(context) { behavior.off = function(selection) { + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + d3_select(window) .on('keydown.select', null) .on('keyup.select', null) diff --git a/modules/modes/select.js b/modules/modes/select.js index 6a8e03c60..358f7db08 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -149,7 +149,7 @@ export function modeSelect(context, selectedIDs) { .select('.edit-menu').remove(); } - mode.showMenu = function() { + mode.showMenu = function(triggerType) { // remove any displayed menu closeMenu(); @@ -178,6 +178,7 @@ export function modeSelect(context, selectedIDs) { _editMenu .anchorLoc(point) + .triggerType(triggerType) .operations(operations); // render the menu @@ -185,9 +186,9 @@ export function modeSelect(context, selectedIDs) { }; - function toggleMenu() { + function spacebar() { // toggle the menu if (context.map().supersurface.select('.edit-menu').empty()) { - mode.showMenu(); + mode.showMenu('spacebar'); } else { closeMenu(); } @@ -271,7 +272,7 @@ export function modeSelect(context, selectedIDs) { .on(['}', uiCmd('⌘]'), 'end'], lastVertex) .on(['\\', 'pause'], nextParent) .on('⎋', esc, true) - .on('space', toggleMenu); + .on('space', spacebar); d3_select(document) .call(keybinding); diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index f4cfdb739..c9861a353 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -11,7 +11,10 @@ export function uiEditMenu(context) { var _operations = []; // the position the menu should be displayed relative to var _anchorLoc = [0, 0]; + // a string indicating how the menu was opened + var _triggerType = ''; + var _vpTopMargin = 85; // viewport top margin var _vpBottomMargin = 45; // viewport bottom margin var _vpSideMargin = 35; // viewport side margin @@ -33,15 +36,30 @@ export function uiEditMenu(context) { var offset = [0, 0]; var viewport = context.surfaceRect(); + // position the menu above the anchor for stylus and finger input + // since the mapper's hand likely obscures the screen below the anchor + var menuTop = _triggerType.includes('touch') || _triggerType.includes('pen'); + var menuLeft = displayOnLeft(viewport); offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin; var menuHeight = _verticalPadding * 2 + _operations.length * _buttonHeight; - if (_anchorLoc[1] + menuHeight > (viewport.height - _vpBottomMargin)) { - // menu is near bottom viewport edge, shift upwards - offset[1] = -1 * (_anchorLoc[1] + menuHeight - viewport.height + _vpBottomMargin); + if (menuTop) { + if (_anchorLoc[1] - menuHeight < _vpTopMargin) { + // menu is near top viewport edge, shift downward + offset[1] = -_anchorLoc[1] + _vpTopMargin; + } else { + offset[1] = -menuHeight; + } + } else { + if (_anchorLoc[1] + menuHeight > (viewport.height - _vpBottomMargin)) { + // menu is near bottom viewport edge, shift upwards + offset[1] = -_anchorLoc[1] - menuHeight + viewport.height - _vpBottomMargin; + } else { + offset[1] = 0; + } } var origin = geoVecAdd(_anchorLoc, offset); @@ -153,6 +171,12 @@ export function uiEditMenu(context) { return editMenu; }; + editMenu.triggerType = function(val) { + if (!arguments.length) return _triggerType; + _triggerType = val; + return editMenu; + }; + editMenu.operations = function(val) { if (!arguments.length) return _operations; _operations = val;