UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

565 lines (551 loc) • 18.1 kB
/** * DevExtreme (cjs/viz/vector_map/tracker.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; exports.Tracker = Tracker; var _events_engine = _interopRequireDefault(require("../../common/core/events/core/events_engine")); var _window = require("../../core/utils/window"); var _dom_adapter = _interopRequireDefault(require("../../core/dom_adapter")); var _event_emitter = require("./event_emitter"); var _index = require("../../common/core/events/utils/index"); var _wheel = require("../../common/core/events/core/wheel"); var _utils = require("../core/utils"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } const navigator = (0, _window.getNavigator)(); const _math = Math; const _abs = _math.abs; const _sqrt = _math.sqrt; const _round = _math.round; const _addNamespace = _index.addNamespace; const _NAME = "dxVectorMap"; const EVENT_START = "start"; const EVENT_MOVE = "move"; const EVENT_END = "end"; const EVENT_ZOOM = "zoom"; const EVENT_HOVER_ON = "hover-on"; const EVENT_HOVER_OFF = "hover-off"; const EVENT_CLICK = "click"; const EVENT_FOCUS_ON = "focus-on"; const EVENT_FOCUS_MOVE = "focus-move"; const EVENT_FOCUS_OFF = "focus-off"; const CLICK_TIME_THRESHOLD = 500; const CLICK_COORD_THRESHOLD_MOUSE = 5; const CLICK_COORD_THRESHOLD_TOUCH = 20; const DRAG_COORD_THRESHOLD_MOUSE = 5; const DRAG_COORD_THRESHOLD_TOUCH = 10; const WHEEL_COOLDOWN = 50; const WHEEL_DIRECTION_COOLDOWN = 300; let EVENTS; let Focus; setupEvents(); function Tracker(parameters) { const 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: ["start", "move", "end", "zoom", "click", "hover-on", "hover-off", "focus-on", "focus-off", "focus-move"], _startClick: function(event, data) { if (!data) { return } const coords = getEventCoords(event); this._clickState = { x: coords.x, y: coords.y, threshold: isTouchEvent(event) ? 20 : 5, time: Date.now() } }, _endClick: function(event, data) { const state = this._clickState; let threshold; let coords; if (!state) { return } if (data && Date.now() - state.time <= 500) { threshold = state.threshold; coords = getEventCoords(event); if (_abs(coords.x - state.x) <= threshold && _abs(coords.y - state.y) <= threshold) { this._fire("click", { data: data, x: coords.x, y: coords.y, $event: event }) } } this._clickState = null }, _startDrag: function(event, data) { if (!data) { return } const coords = getEventCoords(event); const state = this._dragState = { x: coords.x, y: coords.y, data: data }; this._fire("start", { x: state.x, y: state.y, data: state.data }) }, _moveDrag: function(event, data) { const state = this._dragState; if (!state) { return } const coords = getEventCoords(event); const threshold = isTouchEvent(event) ? 10 : 5; 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("move", { x: state.x, y: state.y, data: state.data }) } }, _endDrag: function() { const state = this._dragState; if (!state) { return } this._dragState = null; this._fire("end", { x: state.x, y: state.y, data: state.data }) }, _wheelZoom: function(event, data) { if (!data) { return } const lock = this._wheelLock; const time = Date.now(); if (time - lock.time <= 50) { return } if (time - lock.dirTime > 300) { lock.dir = 0 } const delta = adjustWheelDelta(event.delta / 120 || 0, lock); if (0 === delta) { return } const coords = getEventCoords(event); this._fire("zoom", { delta: delta, x: coords.x, y: coords.y }); lock.time = lock.dirTime = time }, _startZoom: function(event, data) { if (!isTouchEvent(event) || !data) { return } const state = this._zoomState = this._zoomState || {}; let coords; let 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) { const state = this._zoomState; let 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) { const state = this._zoomState; let startDistance; let 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("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) { const 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("hover-on", { data: data }) } that._hoverTarget = event.target }, _cancelHover: function() { const state = this._hoverState; this._hoverState = this._hoverTarget = null; if (state) { this._fire("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) { const that = this; if (that._dragState && that._dragState.active || that._zoomState && that._zoomState.ready) { that._cancelFocus(); return } if (isTouchEvent(event) !== isTouch) { return } that._focus.turnOff(); data && that._focus.turnOn(data, getEventCoords(event)) }, _cancelFocus: function() { this._focus.cancel() }, _createEventHandlers: function(DATA_KEY) { const that = this; that._docHandlers = {}; that._rootHandlers = {}; that._docHandlers[EVENTS.start] = function(event) { const isTouch = isTouchEvent(event); const data = getData(event); if (isTouch && !that._isTouchEnabled) { return } if (data) { event.preventDefault() } 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) { const isTouch = isTouchEvent(event); const 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) { const isTouch = isTouchEvent(event); const 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 } const data = getData(event); if (data) { event.preventDefault(); event.stopPropagation(); that._wheelZoom(event, data) } }; that._wheelLock = { dir: 0 }; function getData(event) { const target = event.target; return ("tspan" === target.tagName ? target.parentNode : target)[DATA_KEY] } }, _createProjectionHandlers: function(projection) { const 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 = !!(0, _utils.parseScalar)(options.touchEnabled, true); this._isWheelEnabled = !!(0, _utils.parseScalar)(options.wheelEnabled, true); this._attachHandlers() }, _detachHandlers: function() { const that = this; if (that._isTouchEnabled) { that._root.css({ "touch-action": "", "-webkit-user-select": "" }).off(_addNamespace("MSHoldVisual", _NAME)).off(_addNamespace("contextmenu", _NAME)) } _events_engine.default.off(_dom_adapter.default.getDocument(), that._docHandlers); that._root.off(that._rootHandlers) }, _attachHandlers: function() { const that = this; if (that._isTouchEnabled) { that._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() })) } _events_engine.default.on(_dom_adapter.default.getDocument(), that._docHandlers); that._root.on(that._rootHandlers) } }; Focus = function(fire) { let that = this; let _activeData = null; let _data = null; let _disabled = false; let _x; let _y; that.dispose = function() { that.turnOn = that.turnOff = that.cancel = that.dispose = that = fire = _activeData = _data = 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("focus-move", { data: _data, x: _x, y: _y }); onCheck(true) } else { fire("focus-on", { data: _data, x: _x, y: _y, done: onCheck }) } } else { _x = coords.x; _y = coords.y; fire("focus-on", { data: _data, x: _x, y: _y, done: onCheck }) } function onCheck(result) { _disabled = !result; if (result) { _activeData = _data } } }; that.turnOff = function() { _data = null; if (_activeData && !_disabled) { fire("focus-off", { data: _activeData }); _activeData = null } }; that.cancel = function() { if (_activeData) { fire("focus-off", { data: _activeData }) } _activeData = _data = null } }; (0, _event_emitter.makeEventEmitter)(Tracker); function getDistance(x1, y1, x2, y2) { return _sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) } function isTouchEvent(event) { const type = event.originalEvent.type; const pointerType = event.originalEvent.pointerType; return /^touch/.test(type) || /^MSPointer/.test(type) && 4 !== pointerType || /^pointer/.test(type) && "mouse" !== pointerType } function selectItem(flags, items) { let i = 0; const ii = flags.length; let item; for (; i < ii; ++i) { if (flags[i]) { item = items[i]; break } } return _addNamespace(item || items[i], _NAME) } function setupEvents() { let flags = [navigator.pointerEnabled, navigator.msPointerEnabled, (0, _window.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(_wheel.name, _NAME) } } function getEventCoords(event) { const originalEvent = event.originalEvent; const 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) { let 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 } let _delta = _abs(delta); const 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 }