devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
397 lines (395 loc) • 14.4 kB
JavaScript
/**
* DevExtreme (cjs/common/core/animation/position.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.default = void 0;
var _size = require("../../../core/utils/size");
var _renderer = _interopRequireDefault(require("../../../core/renderer"));
var _common = require("../../../core/utils/common");
var _iterator = require("../../../core/utils/iterator");
var _window = require("../../../core/utils/window");
var _dom_adapter = _interopRequireDefault(require("../../../core/dom_adapter"));
var _type = require("../../../core/utils/type");
var _extend = require("../../../core/utils/extend");
var _position = require("../../../core/utils/position");
var _browser = _interopRequireDefault(require("../../../core/utils/browser"));
var _translator = require("./translator");
var _support = require("../../../core/utils/support");
var _devices = _interopRequireDefault(require("../../../core/devices"));
var _style = require("../../../core/utils/style");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
const window = (0, _window.getWindow)();
const horzRe = /left|right/;
const vertRe = /top|bottom/;
const collisionRe = /fit|flip|none/;
const scaleRe = /scale\(.+?\)/;
const IS_SAFARI = _browser.default.safari;
const normalizeAlign = function(raw) {
const result = {
h: "center",
v: "center"
};
const pair = (0, _common.splitPair)(raw);
if (pair) {
(0, _iterator.each)(pair, (function() {
const w = String(this).toLowerCase();
if (horzRe.test(w)) {
result.h = w
} else if (vertRe.test(w)) {
result.v = w
}
}))
}
return result
};
const normalizeOffset = function(raw, preventRound) {
return (0, _common.pairToObject)(raw, preventRound)
};
const normalizeCollision = function(raw) {
const pair = (0, _common.splitPair)(raw);
let h = String(pair && pair[0]).toLowerCase();
let v = String(pair && pair[1]).toLowerCase();
if (!collisionRe.test(h)) {
h = "none"
}
if (!collisionRe.test(v)) {
v = h
}
return {
h: h,
v: v
}
};
const getAlignFactor = function(align) {
switch (align) {
case "center":
return .5;
case "right":
case "bottom":
return 1;
default:
return 0
}
};
const inverseAlign = function(align) {
switch (align) {
case "left":
return "right";
case "right":
return "left";
case "top":
return "bottom";
case "bottom":
return "top";
default:
return align
}
};
const calculateOversize = function(data, bounds) {
let oversize = 0;
if (data.myLocation < bounds.min) {
oversize += bounds.min - data.myLocation
}
if (data.myLocation > bounds.max) {
oversize += data.myLocation - bounds.max
}
return oversize
};
const collisionSide = function(direction, data, bounds) {
if (data.myLocation < bounds.min) {
return "h" === direction ? "left" : "top"
}
if (data.myLocation > bounds.max) {
return "h" === direction ? "right" : "bottom"
}
return "none"
};
const initMyLocation = function(data) {
data.myLocation = data.atLocation + getAlignFactor(data.atAlign) * data.atSize - getAlignFactor(data.myAlign) * data.mySize + data.offset
};
const collisionResolvers = {
fit: function(data, bounds) {
let result = false;
if (data.myLocation > bounds.max) {
data.myLocation = bounds.max;
result = true
}
if (data.myLocation < bounds.min) {
data.myLocation = bounds.min;
result = true
}
data.fit = result
},
flip: function(data, bounds) {
data.flip = false;
if ("center" === data.myAlign && "center" === data.atAlign) {
return
}
if (data.myLocation < bounds.min || data.myLocation > bounds.max) {
const inverseData = (0, _extend.extend)({}, data, {
myAlign: inverseAlign(data.myAlign),
atAlign: inverseAlign(data.atAlign),
offset: -data.offset
});
initMyLocation(inverseData);
inverseData.oversize = calculateOversize(inverseData, bounds);
if (inverseData.myLocation >= bounds.min && inverseData.myLocation <= bounds.max || data.oversize > inverseData.oversize) {
data.myLocation = inverseData.myLocation;
data.oversize = inverseData.oversize;
data.flip = true
}
}
},
flipfit: function(data, bounds) {
this.flip(data, bounds);
this.fit(data, bounds)
},
none: function(data) {
data.oversize = 0
}
};
let scrollbarWidth;
const calculateScrollbarWidth = function() {
const $scrollDiv = (0, _renderer.default)("<div>").css({
width: 100,
height: 100,
overflow: "scroll",
position: "absolute",
top: -9999
}).appendTo((0, _renderer.default)("body"));
const result = $scrollDiv.get(0).offsetWidth - $scrollDiv.get(0).clientWidth;
$scrollDiv.remove();
scrollbarWidth = result
};
const defaultPositionResult = {
h: {
location: 0,
flip: false,
fit: false,
oversize: 0
},
v: {
location: 0,
flip: false,
fit: false,
oversize: 0
}
};
const calculatePosition = function(what, options) {
const $what = (0, _renderer.default)(what);
const currentOffset = $what.offset();
const result = (0, _extend.extend)(true, {}, defaultPositionResult, {
h: {
location: currentOffset.left
},
v: {
location: currentOffset.top
}
});
if (!options) {
return result
}
const my = normalizeAlign(options.my);
const at = normalizeAlign(options.at);
let of = (0, _renderer.default)(options.of).length && options.of || window;
const offset = normalizeOffset(options.offset, options.precise);
const collision = normalizeCollision(options.collision);
const boundary = options.boundary;
const boundaryOffset = normalizeOffset(options.boundaryOffset, options.precise);
const h = {
mySize: (0, _size.getOuterWidth)($what),
myAlign: my.h,
atAlign: at.h,
offset: offset.h,
collision: collision.h,
boundaryOffset: boundaryOffset.h
};
const v = {
mySize: (0, _size.getOuterHeight)($what),
myAlign: my.v,
atAlign: at.v,
offset: offset.v,
collision: collision.v,
boundaryOffset: boundaryOffset.v
};
if (of.preventDefault) {
h.atLocation = of.pageX;
v.atLocation = of.pageY;
h.atSize = 0;
v.atSize = 0
} else {
of = (0, _renderer.default)(of);
if ((0, _type.isWindow)(of [0])) {
h.atLocation = of.scrollLeft();
v.atLocation = of.scrollTop();
if ("phone" === _devices.default.real().deviceType && of [0].visualViewport) {
h.atLocation = Math.max(h.atLocation, of [0].visualViewport.offsetLeft);
v.atLocation = Math.max(v.atLocation, of [0].visualViewport.offsetTop);
h.atSize = of [0].visualViewport.width;
v.atSize = of [0].visualViewport.height
} else {
h.atSize = of [0].innerWidth > of [0].outerWidth ? of [0].innerWidth : (0, _size.getWidth)(of);
v.atSize = of [0].innerHeight > of [0].outerHeight || IS_SAFARI ? of [0].innerHeight : (0, _size.getHeight)(of)
}
} else if (9 === of [0].nodeType) {
h.atLocation = 0;
v.atLocation = 0;
h.atSize = (0, _size.getWidth)(of);
v.atSize = (0, _size.getHeight)(of)
} else {
const ofRect = (0, _position.getBoundingRect)(of.get(0));
const o = getOffsetWithoutScale(of);
h.atLocation = o.left;
v.atLocation = o.top;
h.atSize = Math.max(ofRect.width, (0, _size.getOuterWidth)(of));
v.atSize = Math.max(ofRect.height, (0, _size.getOuterHeight)(of))
}
}
initMyLocation(h);
initMyLocation(v);
const bounds = function() {
const win = (0, _renderer.default)(window);
const windowWidth = (0, _size.getWidth)(win);
const windowHeight = (0, _size.getHeight)(win);
let left = win.scrollLeft();
let top = win.scrollTop();
const documentElement = _dom_adapter.default.getDocumentElement();
const hZoomLevel = _support.touch ? documentElement.clientWidth / windowWidth : 1;
const vZoomLevel = _support.touch ? documentElement.clientHeight / windowHeight : 1;
if (void 0 === scrollbarWidth) {
calculateScrollbarWidth()
}
let boundaryWidth = windowWidth;
let boundaryHeight = windowHeight;
if (boundary && !(0, _type.isWindow)(boundary)) {
const $boundary = (0, _renderer.default)(boundary);
const boundaryPosition = $boundary.offset();
left = boundaryPosition.left;
top = boundaryPosition.top;
boundaryWidth = (0, _size.getWidth)($boundary);
boundaryHeight = (0, _size.getHeight)($boundary)
}
return {
h: {
min: left + h.boundaryOffset,
max: left + boundaryWidth / hZoomLevel - h.mySize - h.boundaryOffset
},
v: {
min: top + v.boundaryOffset,
max: top + boundaryHeight / vZoomLevel - v.mySize - v.boundaryOffset
}
}
}();
h.oversize = calculateOversize(h, bounds.h);
v.oversize = calculateOversize(v, bounds.v);
h.collisionSide = collisionSide("h", h, bounds.h);
v.collisionSide = collisionSide("v", v, bounds.v);
if (collisionResolvers[h.collision]) {
collisionResolvers[h.collision](h, bounds.h)
}
if (collisionResolvers[v.collision]) {
collisionResolvers[v.collision](v, bounds.v)
}
const preciser = function(number) {
return options.precise ? number : Math.round(number)
};
(0, _extend.extend)(true, result, {
h: {
location: preciser(h.myLocation),
oversize: preciser(h.oversize),
fit: h.fit,
flip: h.flip,
collisionSide: h.collisionSide
},
v: {
location: preciser(v.myLocation),
oversize: preciser(v.oversize),
fit: v.fit,
flip: v.flip,
collisionSide: v.collisionSide
},
precise: options.precise
});
return result
};
const setScaleProperty = function(element, scale, styleAttr, isEmpty) {
const stylePropIsValid = (0, _type.isDefined)(element.style) && !_dom_adapter.default.isNode(element.style);
const newStyleValue = isEmpty ? styleAttr.replace(scale, "") : styleAttr;
if (stylePropIsValid) {
(0, _style.setStyle)(element, newStyleValue, false)
} else {
const styleAttributeNode = _dom_adapter.default.createAttribute("style");
styleAttributeNode.value = newStyleValue;
element.setAttributeNode(styleAttributeNode)
}
};
const getOffsetWithoutScale = function($startElement) {
var _currentElement$getAt, _style$match;
let $currentElement = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : $startElement;
const currentElement = $currentElement.get(0);
if (!currentElement) {
return $startElement.offset()
}
const style = (null === (_currentElement$getAt = currentElement.getAttribute) || void 0 === _currentElement$getAt ? void 0 : _currentElement$getAt.call(currentElement, "style")) || "";
const scale = null === (_style$match = style.match(scaleRe)) || void 0 === _style$match ? void 0 : _style$match[0];
let offset;
if (scale) {
setScaleProperty(currentElement, scale, style, true);
offset = getOffsetWithoutScale($startElement, $currentElement.parent());
setScaleProperty(currentElement, scale, style, false)
} else {
offset = getOffsetWithoutScale($startElement, $currentElement.parent())
}
return offset
};
const position = function(what, options) {
const $what = (0, _renderer.default)(what);
if (!options) {
return $what.offset()
}(0, _translator.resetPosition)($what, true);
const offset = getOffsetWithoutScale($what);
const targetPosition = options.h && options.v ? options : calculatePosition($what, options);
const preciser = function(number) {
return options.precise ? number : Math.round(number)
};
(0, _translator.move)($what, {
left: targetPosition.h.location - preciser(offset.left),
top: targetPosition.v.location - preciser(offset.top)
});
return targetPosition
};
const offset = function(element) {
element = (0, _renderer.default)(element).get(0);
if ((0, _type.isWindow)(element)) {
return null
} else if (element && "pageY" in element && "pageX" in element) {
return {
top: element.pageY,
left: element.pageX
}
}
return (0, _renderer.default)(element).offset()
};
if (!position.inverseAlign) {
position.inverseAlign = inverseAlign
}
if (!position.normalizeAlign) {
position.normalizeAlign = normalizeAlign
}
var _default = exports.default = {
calculateScrollbarWidth: calculateScrollbarWidth,
calculate: calculatePosition,
setup: position,
offset: offset
};
module.exports = exports.default;
module.exports.default = exports.default;