UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

619 lines (540 loc) • 19.6 kB
"use strict"; var eventsEngine = require("../../events/core/events_engine"), windowUtils = require("../../core/utils/window"), domAdapter = require("../../core/dom_adapter"), navigator = windowUtils.getNavigator(), _math = Math, _abs = _math.abs, _sqrt = _math.sqrt, _round = _math.round, eventEmitterModule = require("./event_emitter"), eventUtils = require("../../events/utils"), wheelEventName = require("../../events/core/wheel").name, _addNamespace = eventUtils.addNamespace, _parseScalar = require("../core/utils").parseScalar, _now = Date.now, _NAME = "dxVectorMap", EVENTS = {}; setupEvents(); var EVENT_START = "start", EVENT_MOVE = "move", EVENT_END = "end", EVENT_ZOOM = "zoom", EVENT_HOVER_ON = "hover-on", EVENT_HOVER_OFF = "hover-off", EVENT_CLICK = "click", EVENT_FOCUS_ON = "focus-on", EVENT_FOCUS_MOVE = "focus-move", EVENT_FOCUS_OFF = "focus-off", CLICK_TIME_THRESHOLD = 500, CLICK_COORD_THRESHOLD_MOUSE = 5, CLICK_COORD_THRESHOLD_TOUCH = 20, DRAG_COORD_THRESHOLD_MOUSE = 5, DRAG_COORD_THRESHOLD_TOUCH = 10, FOCUS_ON_DELAY_MOUSE = 300, FOCUS_OFF_DELAY_MOUSE = 300, FOCUS_ON_DELAY_TOUCH = 300, FOCUS_OFF_DELAY_TOUCH = 400, FOCUS_COORD_THRESHOLD_MOUSE = 5, WHEEL_COOLDOWN = 50, WHEEL_DIRECTION_COOLDOWN = 300; function Tracker(parameters) { var that = this; that._root = parameters.root; that._createEventHandlers(parameters.dataKey); that._createProjectionHandlers(parameters.projection); that._initEvents(); that._focus = new Focus(function (name, arg) { that._fire(name, arg); }); that._attachHandlers(); } Tracker.prototype = { constructor: Tracker, dispose: function dispose() { var that = this; that._detachHandlers(); that._disposeEvents(); that._focus.dispose(); that._root = that._focus = that._docHandlers = that._rootHandlers = null; }, _eventNames: [EVENT_START, EVENT_MOVE, EVENT_END, EVENT_ZOOM, EVENT_CLICK, EVENT_HOVER_ON, EVENT_HOVER_OFF, EVENT_FOCUS_ON, EVENT_FOCUS_OFF, EVENT_FOCUS_MOVE], _startClick: function _startClick(event, data) { if (!data) { return; } var coords = getEventCoords(event); this._clickState = { x: coords.x, y: coords.y, threshold: isTouchEvent(event) ? CLICK_COORD_THRESHOLD_TOUCH : CLICK_COORD_THRESHOLD_MOUSE, time: _now() }; }, _endClick: function _endClick(event, data) { var state = this._clickState, threshold, coords; if (!state) { return; } if (data && _now() - state.time <= CLICK_TIME_THRESHOLD) { threshold = state.threshold; coords = getEventCoords(event); if (_abs(coords.x - state.x) <= threshold && _abs(coords.y - state.y) <= threshold) { this._fire(EVENT_CLICK, { data: data, x: coords.x, y: coords.y, $event: event }); } } this._clickState = null; }, _startDrag: function _startDrag(event, data) { if (!data) { return; } var coords = getEventCoords(event), state = this._dragState = { x: coords.x, y: coords.y, data: data }; this._fire(EVENT_START, { x: state.x, y: state.y, data: state.data }); }, _moveDrag: function _moveDrag(event, data) { var state = this._dragState, coords, threshold; if (!state) { return; } coords = getEventCoords(event); threshold = isTouchEvent(event) ? DRAG_COORD_THRESHOLD_TOUCH : DRAG_COORD_THRESHOLD_MOUSE; if (state.active || _abs(coords.x - state.x) > threshold || _abs(coords.y - state.y) > threshold) { state.x = coords.x; state.y = coords.y; state.active = true; state.data = data || {}; this._fire(EVENT_MOVE, { x: state.x, y: state.y, data: state.data }); } }, _endDrag: function _endDrag() { var state = this._dragState; if (!state) { return; } this._dragState = null; this._fire(EVENT_END, { x: state.x, y: state.y, data: state.data }); }, _wheelZoom: function _wheelZoom(event, data) { if (!data) { return; } var that = this, lock = that._wheelLock, time = _now(), delta, coords; if (time - lock.time <= WHEEL_COOLDOWN) { return; } // T136650 if (time - lock.dirTime > WHEEL_DIRECTION_COOLDOWN) { lock.dir = 0; } // T107589, T136650 delta = adjustWheelDelta(event.delta / 120 || 0, lock); if (delta === 0) { return; } coords = getEventCoords(event); that._fire(EVENT_ZOOM, { delta: delta, x: coords.x, y: coords.y }); lock.time = lock.dirTime = time; }, _startZoom: function _startZoom(event, data) { if (!isTouchEvent(event) || !data) { return; } var state = this._zoomState = this._zoomState || {}, coords, pointer2; if (state.pointer1 && state.pointer2) { return; } if (state.pointer1 === undefined) { state.pointer1 = getPointerId(event) || 0; coords = getMultitouchEventCoords(event, state.pointer1); state.x1 = state.x1_0 = coords.x; state.y1 = state.y1_0 = coords.y; } if (state.pointer2 === undefined) { pointer2 = getPointerId(event) || 1; if (pointer2 !== state.pointer1) { coords = getMultitouchEventCoords(event, pointer2); if (coords) { state.x2 = state.x2_0 = coords.x; state.y2 = state.y2_0 = coords.y; state.pointer2 = pointer2; state.ready = true; this._endDrag(); } } } }, _moveZoom: function _moveZoom(event) { var state = this._zoomState, coords; if (!state || !isTouchEvent(event)) { return; } if (state.pointer1 !== undefined) { coords = getMultitouchEventCoords(event, state.pointer1); if (coords) { state.x1 = coords.x; state.y1 = coords.y; } } if (state.pointer2 !== undefined) { coords = getMultitouchEventCoords(event, state.pointer2); if (coords) { state.x2 = coords.x; state.y2 = coords.y; } } }, _endZoom: function _endZoom(event) { var state = this._zoomState, startDistance, currentDistance; if (!state || !isTouchEvent(event)) { return; } if (state.ready) { startDistance = getDistance(state.x1_0, state.y1_0, state.x2_0, state.y2_0); currentDistance = getDistance(state.x1, state.y1, state.x2, state.y2); this._fire(EVENT_ZOOM, { ratio: currentDistance / startDistance, x: (state.x1_0 + state.x2_0) / 2, y: (state.y1_0 + state.y2_0) / 2 }); } this._zoomState = null; }, _startHover: function _startHover(event, data) { this._doHover(event, data, true); }, _moveHover: function _moveHover(event, data) { this._doHover(event, data, false); }, _doHover: function _doHover(event, data, isTouch) { var that = this; if (that._dragState && that._dragState.active || that._zoomState && that._zoomState.ready) { that._cancelHover(); return; } if (isTouchEvent(event) !== isTouch || that._hoverTarget === event.target || that._hoverState && that._hoverState.data === data) { return; } that._cancelHover(); if (data) { that._hoverState = { data: data }; that._fire(EVENT_HOVER_ON, { data: data }); } that._hoverTarget = event.target; }, _cancelHover: function _cancelHover() { var state = this._hoverState; this._hoverState = this._hoverTarget = null; if (state) { this._fire(EVENT_HOVER_OFF, { data: state.data }); } }, _startFocus: function _startFocus(event, data) { this._doFocus(event, data, true); }, _moveFocus: function _moveFocus(event, data) { this._doFocus(event, data, false); }, _doFocus: function _doFocus(event, data, isTouch) { var that = this; if (that._dragState && that._dragState.active || that._zoomState && that._zoomState.ready) { that._cancelFocus(); return; } if (isTouchEvent(event) !== isTouch) { return; } that._focus.turnOff(isTouch ? FOCUS_OFF_DELAY_TOUCH : FOCUS_OFF_DELAY_MOUSE); data && that._focus.turnOn(data, getEventCoords(event), isTouch ? FOCUS_ON_DELAY_TOUCH : FOCUS_ON_DELAY_MOUSE, isTouch); }, _endFocus: function _endFocus(event) { if (!isTouchEvent(event)) { return; } this._focus.cancelOn(); }, _cancelFocus: function _cancelFocus() { this._focus.cancel(); }, _createEventHandlers: function _createEventHandlers(DATA_KEY) { var that = this; that._docHandlers = {}; that._rootHandlers = {}; // Because of "stopPropagation" at any time only one of two handlers will be fully executed that._rootHandlers[EVENTS.start] /* T322560 */ = that._docHandlers[EVENTS.start] = function (event) { var isTouch = isTouchEvent(event), data = getData(event); if (isTouch && !that._isTouchEnabled) { return; } if (data) { event.preventDefault(); event.stopPropagation(); // T322560 } that._startClick(event, data); that._startDrag(event, data); that._startZoom(event, data); that._startHover(event, data); that._startFocus(event, data); }; that._docHandlers[EVENTS.move] = function (event) { var isTouch = isTouchEvent(event), data = getData(event); if (isTouch && !that._isTouchEnabled) { return; } that._moveDrag(event, data); that._moveZoom(event, data); that._moveHover(event, data); that._moveFocus(event, data); }; that._docHandlers[EVENTS.end] = function (event) { var isTouch = isTouchEvent(event), data = getData(event); if (isTouch && !that._isTouchEnabled) { return; } that._endClick(event, data); that._endDrag(event, data); that._endZoom(event, data); that._endFocus(event, data); }; that._rootHandlers[EVENTS.wheel] = function (event) { that._cancelFocus(); if (!that._isWheelEnabled) { return; } var data = getData(event); if (data) { event.preventDefault(); event.stopPropagation(); // T249548 that._wheelZoom(event, data); } }; that._wheelLock = { dir: 0 }; // Actually it is responsibility of the text element wrapper to handle "data" to its span elements (if there are any). // Now to avoid not so necessary complication of renderer text-span issue is handled on the side of the tracker. function getData(event) { var target = event.target; return (target.tagName === "tspan" ? target.parentNode : target)[DATA_KEY]; } }, _createProjectionHandlers: function _createProjectionHandlers(projection) { var that = this; projection.on({ "center": handler, "zoom": handler }); // T247841 function handler() { // `_cancelHover` probably should also be called here but for now let it not be so that._cancelFocus(); } }, reset: function reset() { var that = this; that._clickState = null; that._endDrag(); that._cancelHover(); that._cancelFocus(); }, setOptions: function setOptions(options) { var that = this; that.reset(); that._detachHandlers(); that._isTouchEnabled = !!_parseScalar(options.touchEnabled, true); that._isWheelEnabled = !!_parseScalar(options.wheelEnabled, true); that._attachHandlers(); }, _detachHandlers: function _detachHandlers() { var that = this; if (that._isTouchEnabled) { that._root.css({ "touch-action": "", "-ms-touch-action": "", "-webkit-user-select": "" }).off(_addNamespace("MSHoldVisual", _NAME)).off(_addNamespace("contextmenu", _NAME)); } eventsEngine.off(domAdapter.getDocument(), that._docHandlers); that._root.off(that._rootHandlers); }, _attachHandlers: function _attachHandlers() { var that = this; if (that._isTouchEnabled) { that._root.css({ "touch-action": "none", "-ms-touch-action": "none", "-webkit-user-select": "none" }).on(_addNamespace("MSHoldVisual", _NAME), function (event) { event.preventDefault(); }).on(_addNamespace("contextmenu", _NAME), function (event) { isTouchEvent(event) && event.preventDefault(); }); } eventsEngine.on(domAdapter.getDocument(), that._docHandlers); that._root.on(that._rootHandlers); } }; var Focus = function Focus(fire) { var that = this, _activeData = null, _data = null, _disabled = false, _onTimer = null, _offTimer = null, _x, _y; that.dispose = function () { clearTimeout(_onTimer); clearTimeout(_offTimer); that.turnOn = that.turnOff = that.cancel = that.cancelOn = that.dispose = that = fire = _activeData = _data = _onTimer = _offTimer = null; }; that.turnOn = function (data, coords, timeout, forceTimeout) { if (data === _data && _disabled) { return; } _disabled = false; _data = data; if (_activeData) { _x = coords.x; _y = coords.y; clearTimeout(_onTimer); _onTimer = setTimeout(function () { _onTimer = null; if (_data === _activeData) { fire(EVENT_FOCUS_MOVE, { data: _data, x: _x, y: _y }); onCheck(true); } else { fire(EVENT_FOCUS_ON, { data: _data, x: _x, y: _y, done: onCheck }); } }, forceTimeout ? timeout : 0); } else { if (!_onTimer || _abs(coords.x - _x) > FOCUS_COORD_THRESHOLD_MOUSE || _abs(coords.y - _y) > FOCUS_COORD_THRESHOLD_MOUSE || forceTimeout) { _x = coords.x; _y = coords.y; clearTimeout(_onTimer); _onTimer = setTimeout(function () { _onTimer = null; fire(EVENT_FOCUS_ON, { data: _data, x: _x, y: _y, done: onCheck }); }, timeout); } } function onCheck(result) { _disabled = !result; if (result) { _activeData = _data; clearTimeout(_offTimer); _offTimer = null; } } }; that.turnOff = function (timeout) { clearTimeout(_onTimer); _onTimer = null; _data = null; if (_activeData && !_disabled) { _offTimer = _offTimer || setTimeout(function () { _offTimer = null; fire(EVENT_FOCUS_OFF, { data: _activeData }); _activeData = null; }, timeout); } }; that.cancel = function () { clearTimeout(_onTimer); clearTimeout(_offTimer); if (_activeData) { fire(EVENT_FOCUS_OFF, { data: _activeData }); } _activeData = _data = _onTimer = _offTimer = null; }; that.cancelOn = function () { clearTimeout(_onTimer); _onTimer = null; }; }; eventEmitterModule.makeEventEmitter(Tracker); exports.Tracker = Tracker; ///#DEBUG var originFocus = Focus; exports._DEBUG_forceEventMode = function (mode) { setupEvents(mode); }; exports.Focus = Focus; exports._DEBUG_stubFocusType = function (focusType) { Focus = focusType; }; exports._DEBUG_restoreFocusType = function () { Focus = originFocus; }; ///#ENDDEBUG function getDistance(x1, y1, x2, y2) { return _sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } function isTouchEvent(event) { var type = event.originalEvent.type, pointerType = event.originalEvent.pointerType; return (/^touch/.test(type) || /^MSPointer/.test(type) && pointerType !== 4 || /^pointer/.test(type) && pointerType !== "mouse" ); } function selectItem(flags, items) { var i = 0, ii = flags.length, item; for (; i < ii; ++i) { if (flags[i]) { item = items[i]; break; } } return _addNamespace(item || items[i], _NAME); } function setupEvents() { var flags = [navigator.pointerEnabled, navigator.msPointerEnabled, windowUtils.hasProperty("ontouchstart")]; ///#DEBUG if (arguments.length) { flags = [arguments[0] === "pointer", arguments[0] === "MSPointer", arguments[0] === "touch"]; } ///#ENDDEBUG EVENTS = { start: selectItem(flags, ["pointerdown", "MSPointerDown", "touchstart mousedown", "mousedown"]), move: selectItem(flags, ["pointermove", "MSPointerMove", "touchmove mousemove", "mousemove"]), end: selectItem(flags, ["pointerup", "MSPointerUp", "touchend mouseup", "mouseup"]), wheel: _addNamespace(wheelEventName, _NAME) }; } function getEventCoords(event) { var originalEvent = event.originalEvent, touch = originalEvent.touches && originalEvent.touches[0] || {}; return { x: touch.pageX || originalEvent.pageX || event.pageX, y: touch.pageY || originalEvent.pageY || event.pageY }; } function getPointerId(event) { return event.originalEvent.pointerId; } function getMultitouchEventCoords(event, pointerId) { var originalEvent = event.originalEvent; if (originalEvent.pointerId !== undefined) { originalEvent = originalEvent.pointerId === pointerId ? originalEvent : null; } else { originalEvent = originalEvent.touches[pointerId]; } return originalEvent ? { x: originalEvent.pageX || event.pageX, y: originalEvent.pageY || event.pageY } : null; } function adjustWheelDelta(delta, lock) { if (delta === 0) { return 0; } var _delta = _abs(delta), sign = _round(delta / _delta); if (lock.dir && sign !== lock.dir) { return 0; } lock.dir = sign; if (_delta < 0.1) { _delta = 0; } else if (_delta < 1) { _delta = 1; } else if (_delta > 4) { _delta = 4; } else { _delta = _round(_delta); } return sign * _delta; }