UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

573 lines (561 loc) • 18.3 kB
/** * DevExtreme (esm/viz/vector_map/tracker.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import eventsEngine from "../../events/core/events_engine"; import { getNavigator, hasProperty } from "../../core/utils/window"; import domAdapter from "../../core/dom_adapter"; import { makeEventEmitter } from "./event_emitter"; import { addNamespace } from "../../events/utils/index"; import { name as wheelEventName } from "../../events/core/wheel"; import { parseScalar } from "../core/utils"; var navigator = getNavigator(); var _math = Math; var _abs = _math.abs; var _sqrt = _math.sqrt; var _round = _math.round; var _addNamespace = addNamespace; var _NAME = "dxVectorMap"; var EVENT_START = "start"; var EVENT_MOVE = "move"; var EVENT_END = "end"; var EVENT_ZOOM = "zoom"; var EVENT_HOVER_ON = "hover-on"; var EVENT_HOVER_OFF = "hover-off"; var EVENT_CLICK = "click"; var EVENT_FOCUS_ON = "focus-on"; var EVENT_FOCUS_MOVE = "focus-move"; var EVENT_FOCUS_OFF = "focus-off"; var CLICK_TIME_THRESHOLD = 500; var CLICK_COORD_THRESHOLD_MOUSE = 5; var CLICK_COORD_THRESHOLD_TOUCH = 20; var DRAG_COORD_THRESHOLD_MOUSE = 5; var DRAG_COORD_THRESHOLD_TOUCH = 10; var FOCUS_OFF_DELAY = 100; var WHEEL_COOLDOWN = 50; var WHEEL_DIRECTION_COOLDOWN = 300; var EVENTS; var Focus; setupEvents(); export 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() { this._detachHandlers(); this._disposeEvents(); this._focus.dispose(); this._root = this._focus = this._docHandlers = this._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(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: Date.now() } }, _endClick: function(event, data) { var state = this._clickState; var threshold; var coords; if (!state) { return } if (data && Date.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(event, data) { if (!data) { return } var coords = getEventCoords(event); var 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(event, data) { var state = this._dragState; if (!state) { return } var coords = getEventCoords(event); var 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() { 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(event, data) { if (!data) { return } var lock = this._wheelLock; var time = Date.now(); if (time - lock.time <= WHEEL_COOLDOWN) { return } if (time - lock.dirTime > WHEEL_DIRECTION_COOLDOWN) { lock.dir = 0 } var delta = adjustWheelDelta(event.delta / 120 || 0, lock); if (0 === delta) { return } var coords = getEventCoords(event); this._fire(EVENT_ZOOM, { delta: delta, x: coords.x, y: coords.y }); lock.time = lock.dirTime = time }, _startZoom: function(event, data) { if (!isTouchEvent(event) || !data) { return } var state = this._zoomState = this._zoomState || {}; var coords; var pointer2; if (state.pointer1 && state.pointer2) { return } if (void 0 === state.pointer1) { 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 (void 0 === state.pointer2) { 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(event) { var state = this._zoomState; var coords; if (!state || !isTouchEvent(event)) { return } if (void 0 !== state.pointer1) { coords = getMultitouchEventCoords(event, state.pointer1); if (coords) { state.x1 = coords.x; state.y1 = coords.y } } if (void 0 !== state.pointer2) { coords = getMultitouchEventCoords(event, state.pointer2); if (coords) { state.x2 = coords.x; state.y2 = coords.y } } }, _endZoom: function(event) { var state = this._zoomState; var startDistance; var 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(event, data) { this._doHover(event, data, true) }, _moveHover: function(event, data) { this._doHover(event, data, false) }, _doHover: function(event, data, isTouch) { if (this._dragState && this._dragState.active || this._zoomState && this._zoomState.ready) { this._cancelHover(); return } if (isTouchEvent(event) !== isTouch || this._hoverTarget === event.target || this._hoverState && this._hoverState.data === data) { return } this._cancelHover(); if (data) { this._hoverState = { data: data }; this._fire(EVENT_HOVER_ON, { data: data }) } this._hoverTarget = event.target }, _cancelHover: function() { var state = this._hoverState; this._hoverState = this._hoverTarget = null; if (state) { this._fire(EVENT_HOVER_OFF, { data: state.data }) } }, _startFocus: function(event, data) { this._doFocus(event, data, true) }, _moveFocus: function(event, data) { this._doFocus(event, data, false) }, _doFocus: function(event, data, isTouch) { if (this._dragState && this._dragState.active || this._zoomState && this._zoomState.ready) { this._cancelFocus(); return } if (isTouchEvent(event) !== isTouch) { return } this._focus.turnOff(); data && this._focus.turnOn(data, getEventCoords(event)) }, _cancelFocus: function() { this._focus.cancel() }, _createEventHandlers: function(DATA_KEY) { var that = this; that._docHandlers = {}; that._rootHandlers = {}; that._rootHandlers[EVENTS.start] = that._docHandlers[EVENTS.start] = function(event) { var isTouch = isTouchEvent(event); var data = getData(event); if (isTouch && !that._isTouchEnabled) { return } if (data) { event.preventDefault(); event.stopPropagation() } 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); var 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); var data = getData(event); if (isTouch && !that._isTouchEnabled) { return } that._endClick(event, data); that._endDrag(event, data); that._endZoom(event, data) }; that._rootHandlers[EVENTS.wheel] = function(event) { that._cancelFocus(); if (!that._isWheelEnabled) { return } var data = getData(event); if (data) { event.preventDefault(); event.stopPropagation(); that._wheelZoom(event, data) } }; that._wheelLock = { dir: 0 }; function getData(event) { var target = event.target; return ("tspan" === target.tagName ? target.parentNode : target)[DATA_KEY] } }, _createProjectionHandlers: function(projection) { var that = this; projection.on({ center: handler, zoom: handler }); function handler() { that._cancelFocus() } }, reset: function() { this._clickState = null; this._endDrag(); this._cancelHover(); this._cancelFocus() }, setOptions: function(options) { this.reset(); this._detachHandlers(); this._isTouchEnabled = !!parseScalar(options.touchEnabled, true); this._isWheelEnabled = !!parseScalar(options.wheelEnabled, true); this._attachHandlers() }, _detachHandlers: function() { if (this._isTouchEnabled) { this._root.css({ "touch-action": "", "-webkit-user-select": "" }).off(_addNamespace("MSHoldVisual", _NAME)).off(_addNamespace("contextmenu", _NAME)) } eventsEngine.off(domAdapter.getDocument(), this._docHandlers); this._root.off(this._rootHandlers) }, _attachHandlers: function() { if (this._isTouchEnabled) { this._root.css({ "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(), this._docHandlers); this._root.on(this._rootHandlers) } }; Focus = function(fire) { var that = this; var _activeData = null; var _data = null; var _disabled = false; var _offTimer = null; var _x; var _y; that.dispose = function() { clearTimeout(_offTimer); that.turnOn = that.turnOff = that.cancel = that.dispose = that = fire = _activeData = _data = _offTimer = null }; that.turnOn = function(data, coords) { if (data === _data && _disabled) { return } _disabled = false; _data = data; if (_activeData) { _x = coords.x; _y = coords.y; 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 }) } } else { _x = coords.x; _y = coords.y; fire(EVENT_FOCUS_ON, { data: _data, x: _x, y: _y, done: onCheck }) } function onCheck(result) { _disabled = !result; if (result) { _activeData = _data; clearTimeout(_offTimer); _offTimer = null } } }; that.turnOff = function() { _data = null; if (_activeData && !_disabled) { _offTimer = _offTimer || setTimeout((function() { _offTimer = null; fire(EVENT_FOCUS_OFF, { data: _activeData }); _activeData = null }), FOCUS_OFF_DELAY) } }; that.cancel = function() { clearTimeout(_offTimer); if (_activeData) { fire(EVENT_FOCUS_OFF, { data: _activeData }) } _activeData = _data = _offTimer = null } }; makeEventEmitter(Tracker); function getDistance(x1, y1, x2, y2) { return _sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) } function isTouchEvent(event) { var type = event.originalEvent.type; var pointerType = event.originalEvent.pointerType; return /^touch/.test(type) || /^MSPointer/.test(type) && 4 !== pointerType || /^pointer/.test(type) && "mouse" !== pointerType } function selectItem(flags, items) { var i = 0; var ii = flags.length; var 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, hasProperty("ontouchstart")]; 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; var 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 (void 0 !== originalEvent.pointerId) { 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 (0 === delta) { return 0 } var _delta = _abs(delta); var sign = _round(delta / _delta); if (lock.dir && sign !== lock.dir) { return 0 } lock.dir = sign; if (_delta < .1) { _delta = 0 } else if (_delta < 1) { _delta = 1 } else if (_delta > 4) { _delta = 4 } else { _delta = _round(_delta) } return sign * _delta }