position.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /**
  2. * DevExtreme (animation/position.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../core/renderer");
  11. var commonUtils = require("../core/utils/common");
  12. var each = require("../core/utils/iterator").each;
  13. var windowUtils = require("../core/utils/window");
  14. var window = windowUtils.getWindow();
  15. var domAdapter = require("../core/dom_adapter");
  16. var isWindow = require("../core/utils/type").isWindow;
  17. var extend = require("../core/utils/extend").extend;
  18. var browser = require("../core/utils/browser");
  19. var translator = require("./translator");
  20. var support = require("../core/utils/support");
  21. var horzRe = /left|right/;
  22. var vertRe = /top|bottom/;
  23. var collisionRe = /fit|flip|none/;
  24. var IS_SAFARI = browser.safari;
  25. var normalizeAlign = function(raw) {
  26. var result = {
  27. h: "center",
  28. v: "center"
  29. };
  30. var pair = commonUtils.splitPair(raw);
  31. if (pair) {
  32. each(pair, function() {
  33. var w = String(this).toLowerCase();
  34. if (horzRe.test(w)) {
  35. result.h = w
  36. } else {
  37. if (vertRe.test(w)) {
  38. result.v = w
  39. }
  40. }
  41. })
  42. }
  43. return result
  44. };
  45. var normalizeOffset = function(raw) {
  46. return commonUtils.pairToObject(raw)
  47. };
  48. var normalizeCollision = function(raw) {
  49. var pair = commonUtils.splitPair(raw);
  50. var h = String(pair && pair[0]).toLowerCase();
  51. var v = String(pair && pair[1]).toLowerCase();
  52. if (!collisionRe.test(h)) {
  53. h = "none"
  54. }
  55. if (!collisionRe.test(v)) {
  56. v = h
  57. }
  58. return {
  59. h: h,
  60. v: v
  61. }
  62. };
  63. var getAlignFactor = function(align) {
  64. switch (align) {
  65. case "center":
  66. return .5;
  67. case "right":
  68. case "bottom":
  69. return 1;
  70. default:
  71. return 0
  72. }
  73. };
  74. var inverseAlign = function(align) {
  75. switch (align) {
  76. case "left":
  77. return "right";
  78. case "right":
  79. return "left";
  80. case "top":
  81. return "bottom";
  82. case "bottom":
  83. return "top";
  84. default:
  85. return align
  86. }
  87. };
  88. var calculateOversize = function(data, bounds) {
  89. var oversize = 0;
  90. if (data.myLocation < bounds.min) {
  91. oversize += bounds.min - data.myLocation
  92. }
  93. if (data.myLocation > bounds.max) {
  94. oversize += data.myLocation - bounds.max
  95. }
  96. return oversize
  97. };
  98. var collisionSide = function(direction, data, bounds) {
  99. if (data.myLocation < bounds.min) {
  100. return "h" === direction ? "left" : "top"
  101. }
  102. if (data.myLocation > bounds.max) {
  103. return "h" === direction ? "right" : "bottom"
  104. }
  105. return "none"
  106. };
  107. var initMyLocation = function(data) {
  108. data.myLocation = data.atLocation + getAlignFactor(data.atAlign) * data.atSize - getAlignFactor(data.myAlign) * data.mySize + data.offset
  109. };
  110. var collisionResolvers = {
  111. fit: function(data, bounds) {
  112. var result = false;
  113. if (data.myLocation > bounds.max) {
  114. data.myLocation = bounds.max;
  115. result = true
  116. }
  117. if (data.myLocation < bounds.min) {
  118. data.myLocation = bounds.min;
  119. result = true
  120. }
  121. data.fit = result
  122. },
  123. flip: function(data, bounds) {
  124. data.flip = false;
  125. if ("center" === data.myAlign && "center" === data.atAlign) {
  126. return
  127. }
  128. if (data.myLocation < bounds.min || data.myLocation > bounds.max) {
  129. var inverseData = extend({}, data, {
  130. myAlign: inverseAlign(data.myAlign),
  131. atAlign: inverseAlign(data.atAlign),
  132. offset: -data.offset
  133. });
  134. initMyLocation(inverseData);
  135. inverseData.oversize = calculateOversize(inverseData, bounds);
  136. if (inverseData.myLocation >= bounds.min && inverseData.myLocation <= bounds.max || data.oversize > inverseData.oversize) {
  137. data.myLocation = inverseData.myLocation;
  138. data.oversize = inverseData.oversize;
  139. data.flip = true
  140. }
  141. }
  142. },
  143. flipfit: function(data, bounds) {
  144. this.flip(data, bounds);
  145. this.fit(data, bounds)
  146. },
  147. none: function(data) {
  148. data.oversize = 0
  149. }
  150. };
  151. var scrollbarWidth;
  152. var calculateScrollbarWidth = function() {
  153. var $scrollDiv = $("<div>").css({
  154. width: 100,
  155. height: 100,
  156. overflow: "scroll",
  157. position: "absolute",
  158. top: -9999
  159. }).appendTo($("body"));
  160. var result = $scrollDiv.get(0).offsetWidth - $scrollDiv.get(0).clientWidth;
  161. $scrollDiv.remove();
  162. scrollbarWidth = result
  163. };
  164. var defaultPositionResult = {
  165. h: {
  166. location: 0,
  167. flip: false,
  168. fit: false,
  169. oversize: 0
  170. },
  171. v: {
  172. location: 0,
  173. flip: false,
  174. fit: false,
  175. oversize: 0
  176. }
  177. };
  178. var calculatePosition = function(what, options) {
  179. var $what = $(what);
  180. var currentOffset = $what.offset();
  181. var result = extend(true, {}, defaultPositionResult, {
  182. h: {
  183. location: currentOffset.left
  184. },
  185. v: {
  186. location: currentOffset.top
  187. }
  188. });
  189. if (!options) {
  190. return result
  191. }
  192. var my = normalizeAlign(options.my);
  193. var at = normalizeAlign(options.at);
  194. var of = $(options.of).length && options.of || window;
  195. var offset = normalizeOffset(options.offset);
  196. var collision = normalizeCollision(options.collision);
  197. var boundary = options.boundary;
  198. var boundaryOffset = normalizeOffset(options.boundaryOffset);
  199. var h = {
  200. mySize: $what.outerWidth(),
  201. myAlign: my.h,
  202. atAlign: at.h,
  203. offset: offset.h,
  204. collision: collision.h,
  205. boundaryOffset: boundaryOffset.h
  206. };
  207. var v = {
  208. mySize: $what.outerHeight(),
  209. myAlign: my.v,
  210. atAlign: at.v,
  211. offset: offset.v,
  212. collision: collision.v,
  213. boundaryOffset: boundaryOffset.v
  214. };
  215. if (of.preventDefault) {
  216. h.atLocation = of.pageX;
  217. v.atLocation = of.pageY;
  218. h.atSize = 0;
  219. v.atSize = 0
  220. } else {
  221. of = $(of);
  222. if (isWindow(of [0])) {
  223. h.atLocation = of.scrollLeft();
  224. v.atLocation = of.scrollTop();
  225. h.atSize = of [0].innerWidth >= of [0].outerWidth ? of [0].innerWidth : of.width();
  226. v.atSize = of [0].innerHeight >= of [0].outerHeight || IS_SAFARI ? of [0].innerHeight : of.height()
  227. } else {
  228. if (9 === of [0].nodeType) {
  229. h.atLocation = 0;
  230. v.atLocation = 0;
  231. h.atSize = of.width();
  232. v.atSize = of.height()
  233. } else {
  234. var o = of.offset();
  235. h.atLocation = o.left;
  236. v.atLocation = o.top;
  237. h.atSize = of.outerWidth();
  238. v.atSize = of.outerHeight()
  239. }
  240. }
  241. }
  242. initMyLocation(h);
  243. initMyLocation(v);
  244. var bounds = function() {
  245. var win = $(window);
  246. var windowWidth = win.width();
  247. var windowHeight = win.height();
  248. var left = win.scrollLeft();
  249. var top = win.scrollTop();
  250. var documentElement = domAdapter.getDocumentElement();
  251. var hZoomLevel = support.touch ? documentElement.clientWidth / windowWidth : 1;
  252. var vZoomLevel = support.touch ? documentElement.clientHeight / windowHeight : 1;
  253. if (void 0 === scrollbarWidth) {
  254. calculateScrollbarWidth()
  255. }
  256. var boundaryWidth = windowWidth;
  257. var boundaryHeight = windowHeight;
  258. if (boundary) {
  259. var $boundary = $(boundary);
  260. var boundaryPosition = $boundary.offset();
  261. left = boundaryPosition.left;
  262. top = boundaryPosition.top;
  263. boundaryWidth = $boundary.width();
  264. boundaryHeight = $boundary.height()
  265. }
  266. return {
  267. h: {
  268. min: left + h.boundaryOffset,
  269. max: left + boundaryWidth / hZoomLevel - h.mySize - h.boundaryOffset
  270. },
  271. v: {
  272. min: top + v.boundaryOffset,
  273. max: top + boundaryHeight / vZoomLevel - v.mySize - v.boundaryOffset
  274. }
  275. }
  276. }();
  277. h.oversize = calculateOversize(h, bounds.h);
  278. v.oversize = calculateOversize(v, bounds.v);
  279. h.collisionSide = collisionSide("h", h, bounds.h);
  280. v.collisionSide = collisionSide("v", v, bounds.v);
  281. if (collisionResolvers[h.collision]) {
  282. collisionResolvers[h.collision](h, bounds.h)
  283. }
  284. if (collisionResolvers[v.collision]) {
  285. collisionResolvers[v.collision](v, bounds.v)
  286. }
  287. var preciser = function(number) {
  288. return options.precise ? number : Math.round(number)
  289. };
  290. extend(true, result, {
  291. h: {
  292. location: preciser(h.myLocation),
  293. oversize: preciser(h.oversize),
  294. fit: h.fit,
  295. flip: h.flip,
  296. collisionSide: h.collisionSide
  297. },
  298. v: {
  299. location: preciser(v.myLocation),
  300. oversize: preciser(v.oversize),
  301. fit: v.fit,
  302. flip: v.flip,
  303. collisionSide: v.collisionSide
  304. },
  305. precise: options.precise
  306. });
  307. return result
  308. };
  309. var position = function(what, options) {
  310. var $what = $(what);
  311. if (!options) {
  312. return $what.offset()
  313. }
  314. translator.resetPosition($what, true);
  315. var offset = $what.offset();
  316. var targetPosition = options.h && options.v ? options : calculatePosition($what, options);
  317. var preciser = function(number) {
  318. return options.precise ? number : Math.round(number)
  319. };
  320. translator.move($what, {
  321. left: targetPosition.h.location - preciser(offset.left),
  322. top: targetPosition.v.location - preciser(offset.top)
  323. });
  324. return targetPosition
  325. };
  326. var offset = function(element) {
  327. element = $(element).get(0);
  328. if (isWindow(element)) {
  329. return null
  330. } else {
  331. if (element && "pageY" in element && "pageX" in element) {
  332. return {
  333. top: element.pageY,
  334. left: element.pageX
  335. }
  336. }
  337. }
  338. return $(element).offset()
  339. };
  340. if (!position.inverseAlign) {
  341. position.inverseAlign = inverseAlign
  342. }
  343. if (!position.normalizeAlign) {
  344. position.normalizeAlign = normalizeAlign
  345. }
  346. module.exports = {
  347. calculateScrollbarWidth: calculateScrollbarWidth,
  348. calculate: calculatePosition,
  349. setup: position,
  350. offset: offset
  351. };