resizable.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /**
  2. * DevExtreme (ui/resizable.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 eventsEngine = require("../events/core/events_engine");
  12. var registerComponent = require("../core/component_registrator");
  13. var commonUtils = require("../core/utils/common");
  14. var extend = require("../core/utils/extend").extend;
  15. var inArray = require("../core/utils/array").inArray;
  16. var each = require("../core/utils/iterator").each;
  17. var typeUtils = require("../core/utils/type");
  18. var windowUtils = require("../core/utils/window");
  19. var translator = require("../animation/translator");
  20. var fitIntoRange = require("../core/utils/math").fitIntoRange;
  21. var DOMComponent = require("../core/dom_component");
  22. var eventUtils = require("../events/utils");
  23. var dragEvents = require("../events/drag");
  24. var isPlainObject = typeUtils.isPlainObject;
  25. var isFunction = typeUtils.isFunction;
  26. var domUtils = require("../core/utils/dom");
  27. var RESIZABLE = "dxResizable";
  28. var RESIZABLE_CLASS = "dx-resizable";
  29. var RESIZABLE_RESIZING_CLASS = "dx-resizable-resizing";
  30. var RESIZABLE_HANDLE_CLASS = "dx-resizable-handle";
  31. var RESIZABLE_HANDLE_TOP_CLASS = "dx-resizable-handle-top";
  32. var RESIZABLE_HANDLE_BOTTOM_CLASS = "dx-resizable-handle-bottom";
  33. var RESIZABLE_HANDLE_LEFT_CLASS = "dx-resizable-handle-left";
  34. var RESIZABLE_HANDLE_RIGHT_CLASS = "dx-resizable-handle-right";
  35. var RESIZABLE_HANDLE_CORNER_CLASS = "dx-resizable-handle-corner";
  36. var DRAGSTART_START_EVENT_NAME = eventUtils.addNamespace(dragEvents.start, RESIZABLE);
  37. var DRAGSTART_EVENT_NAME = eventUtils.addNamespace(dragEvents.move, RESIZABLE);
  38. var DRAGSTART_END_EVENT_NAME = eventUtils.addNamespace(dragEvents.end, RESIZABLE);
  39. var SIDE_BORDER_WIDTH_STYLES = {
  40. left: "borderLeftWidth",
  41. top: "borderTopWidth",
  42. right: "borderRightWidth",
  43. bottom: "borderBottomWidth"
  44. };
  45. var Resizable = DOMComponent.inherit({
  46. _getDefaultOptions: function() {
  47. return extend(this.callBase(), {
  48. handles: "all",
  49. step: "1",
  50. stepPrecision: "simple",
  51. area: void 0,
  52. minWidth: 30,
  53. maxWidth: 1 / 0,
  54. minHeight: 30,
  55. maxHeight: 1 / 0,
  56. onResizeStart: null,
  57. onResize: null,
  58. onResizeEnd: null,
  59. roundStepValue: true
  60. })
  61. },
  62. _init: function() {
  63. this.callBase();
  64. this.$element().addClass(RESIZABLE_CLASS)
  65. },
  66. _initMarkup: function() {
  67. this.callBase();
  68. this._renderHandles()
  69. },
  70. _render: function() {
  71. this.callBase();
  72. this._renderActions()
  73. },
  74. _renderActions: function() {
  75. this._resizeStartAction = this._createActionByOption("onResizeStart");
  76. this._resizeEndAction = this._createActionByOption("onResizeEnd");
  77. this._resizeAction = this._createActionByOption("onResize")
  78. },
  79. _renderHandles: function() {
  80. var handles = this.option("handles");
  81. if ("none" === handles) {
  82. return
  83. }
  84. var directions = "all" === handles ? ["top", "bottom", "left", "right"] : handles.split(" ");
  85. each(directions, function(index, handleName) {
  86. this._renderHandle(handleName)
  87. }.bind(this));
  88. inArray("bottom", directions) + 1 && inArray("right", directions) + 1 && this._renderHandle("corner-bottom-right");
  89. inArray("bottom", directions) + 1 && inArray("left", directions) + 1 && this._renderHandle("corner-bottom-left");
  90. inArray("top", directions) + 1 && inArray("right", directions) + 1 && this._renderHandle("corner-top-right");
  91. inArray("top", directions) + 1 && inArray("left", directions) + 1 && this._renderHandle("corner-top-left")
  92. },
  93. _renderHandle: function(handleName) {
  94. var $element = this.$element();
  95. var $handle = $("<div>");
  96. $handle.addClass(RESIZABLE_HANDLE_CLASS).addClass(RESIZABLE_HANDLE_CLASS + "-" + handleName).appendTo($element);
  97. this._attachEventHandlers($handle)
  98. },
  99. _attachEventHandlers: function($handle) {
  100. if (this.option("disabled")) {
  101. return
  102. }
  103. var handlers = {};
  104. handlers[DRAGSTART_START_EVENT_NAME] = this._dragStartHandler.bind(this);
  105. handlers[DRAGSTART_EVENT_NAME] = this._dragHandler.bind(this);
  106. handlers[DRAGSTART_END_EVENT_NAME] = this._dragEndHandler.bind(this);
  107. eventsEngine.on($handle, handlers, {
  108. direction: "both",
  109. immediate: true
  110. })
  111. },
  112. _dragStartHandler: function(e) {
  113. var $element = this.$element();
  114. if ($element.is(".dx-state-disabled, .dx-state-disabled *")) {
  115. e.cancel = true;
  116. return
  117. }
  118. this._toggleResizingClass(true);
  119. this._movingSides = this._getMovingSides(e);
  120. this._elementLocation = translator.locate($element);
  121. var elementRect = $element.get(0).getBoundingClientRect();
  122. this._elementSize = {
  123. width: elementRect.width,
  124. height: elementRect.height
  125. };
  126. this._renderDragOffsets(e);
  127. this._resizeStartAction({
  128. event: e,
  129. width: this._elementSize.width,
  130. height: this._elementSize.height,
  131. handles: this._movingSides
  132. });
  133. e.targetElements = null
  134. },
  135. _toggleResizingClass: function(value) {
  136. this.$element().toggleClass(RESIZABLE_RESIZING_CLASS, value)
  137. },
  138. _renderDragOffsets: function(e) {
  139. var area = this._getArea();
  140. if (!area) {
  141. return
  142. }
  143. var $handle = $(e.target).closest("." + RESIZABLE_HANDLE_CLASS);
  144. var handleWidth = $handle.outerWidth();
  145. var handleHeight = $handle.outerHeight();
  146. var handleOffset = $handle.offset();
  147. var areaOffset = area.offset;
  148. var scrollOffset = this._getAreaScrollOffset();
  149. e.maxLeftOffset = handleOffset.left - areaOffset.left - scrollOffset.scrollX;
  150. e.maxRightOffset = areaOffset.left + area.width - handleOffset.left - handleWidth + scrollOffset.scrollX;
  151. e.maxTopOffset = handleOffset.top - areaOffset.top - scrollOffset.scrollY;
  152. e.maxBottomOffset = areaOffset.top + area.height - handleOffset.top - handleHeight + scrollOffset.scrollY
  153. },
  154. _getBorderWidth: function($element, direction) {
  155. if (typeUtils.isWindow($element.get(0))) {
  156. return 0
  157. }
  158. var borderWidth = $element.css(SIDE_BORDER_WIDTH_STYLES[direction]);
  159. return parseInt(borderWidth) || 0
  160. },
  161. _dragHandler: function(e) {
  162. var $element = this.$element();
  163. var sides = this._movingSides;
  164. var location = this._elementLocation;
  165. var size = this._elementSize;
  166. var offset = this._getOffset(e);
  167. var width = size.width + offset.x * (sides.left ? -1 : 1);
  168. var height = size.height + offset.y * (sides.top ? -1 : 1);
  169. if (offset.x || "strict" === this.option("stepPrecision")) {
  170. this._renderWidth(width)
  171. }
  172. if (offset.y || "strict" === this.option("stepPrecision")) {
  173. this._renderHeight(height)
  174. }
  175. var elementRect = $element.get(0).getBoundingClientRect();
  176. var offsetTop = offset.y - ((elementRect.height || height) - height);
  177. var offsetLeft = offset.x - ((elementRect.width || width) - width);
  178. translator.move($element, {
  179. top: location.top + (sides.top ? offsetTop : 0),
  180. left: location.left + (sides.left ? offsetLeft : 0)
  181. });
  182. this._resizeAction({
  183. event: e,
  184. width: this.option("width") || width,
  185. height: this.option("height") || height,
  186. handles: this._movingSides
  187. });
  188. domUtils.triggerResizeEvent($element)
  189. },
  190. _getOffset: function(e) {
  191. var offset = e.offset;
  192. var steps = commonUtils.pairToObject(this.option("step"), !this.option("roundStepValue"));
  193. var sides = this._getMovingSides(e);
  194. var strictPrecision = "strict" === this.option("stepPrecision");
  195. if (!sides.left && !sides.right) {
  196. offset.x = 0
  197. }
  198. if (!sides.top && !sides.bottom) {
  199. offset.y = 0
  200. }
  201. return strictPrecision ? this._getStrictOffset(offset, steps, sides) : this._getSimpleOffset(offset, steps)
  202. },
  203. _getSimpleOffset: function(offset, steps) {
  204. return {
  205. x: offset.x - offset.x % steps.h,
  206. y: offset.y - offset.y % steps.v
  207. }
  208. },
  209. _getStrictOffset: function(offset, steps, sides) {
  210. var location = this._elementLocation;
  211. var size = this._elementSize;
  212. var xPos = sides.left ? location.left : location.left + size.width;
  213. var yPos = sides.top ? location.top : location.top + size.height;
  214. var newXShift = (xPos + offset.x) % steps.h;
  215. var newYShift = (yPos + offset.y) % steps.v;
  216. var sign = Math.sign || function(x) {
  217. x = +x;
  218. if (0 === x || isNaN(x)) {
  219. return x
  220. }
  221. return x > 0 ? 1 : -1
  222. };
  223. var separatorOffset = function(steps, offset) {
  224. return (1 + .2 * sign(offset)) % 1 * steps
  225. };
  226. var isSmallOffset = function(offset, steps) {
  227. return Math.abs(offset) < .2 * steps
  228. };
  229. var newOffsetX = offset.x - newXShift;
  230. var newOffsetY = offset.y - newYShift;
  231. if (newXShift > separatorOffset(steps.h, offset.x)) {
  232. newOffsetX += steps.h
  233. }
  234. if (newYShift > separatorOffset(steps.v, offset.y)) {
  235. newOffsetY += steps.v
  236. }
  237. return {
  238. x: (sides.left || sides.right) && !isSmallOffset(offset.x, steps.h) ? newOffsetX : 0,
  239. y: (sides.top || sides.bottom) && !isSmallOffset(offset.y, steps.v) ? newOffsetY : 0
  240. }
  241. },
  242. _getMovingSides: function(e) {
  243. var $target = $(e.target);
  244. var hasCornerTopLeftClass = $target.hasClass(RESIZABLE_HANDLE_CORNER_CLASS + "-top-left");
  245. var hasCornerTopRightClass = $target.hasClass(RESIZABLE_HANDLE_CORNER_CLASS + "-top-right");
  246. var hasCornerBottomLeftClass = $target.hasClass(RESIZABLE_HANDLE_CORNER_CLASS + "-bottom-left");
  247. var hasCornerBottomRightClass = $target.hasClass(RESIZABLE_HANDLE_CORNER_CLASS + "-bottom-right");
  248. return {
  249. top: $target.hasClass(RESIZABLE_HANDLE_TOP_CLASS) || hasCornerTopLeftClass || hasCornerTopRightClass,
  250. left: $target.hasClass(RESIZABLE_HANDLE_LEFT_CLASS) || hasCornerTopLeftClass || hasCornerBottomLeftClass,
  251. bottom: $target.hasClass(RESIZABLE_HANDLE_BOTTOM_CLASS) || hasCornerBottomLeftClass || hasCornerBottomRightClass,
  252. right: $target.hasClass(RESIZABLE_HANDLE_RIGHT_CLASS) || hasCornerTopRightClass || hasCornerBottomRightClass
  253. }
  254. },
  255. _getArea: function() {
  256. var area = this.option("area");
  257. if (isFunction(area)) {
  258. area = area.call(this)
  259. }
  260. if (isPlainObject(area)) {
  261. return this._getAreaFromObject(area)
  262. }
  263. return this._getAreaFromElement(area)
  264. },
  265. _getAreaScrollOffset: function() {
  266. var area = this.option("area");
  267. var isElement = !isFunction(area) && !isPlainObject(area);
  268. var scrollOffset = {
  269. scrollY: 0,
  270. scrollX: 0
  271. };
  272. if (isElement) {
  273. var areaElement = $(area)[0];
  274. if (typeUtils.isWindow(areaElement)) {
  275. scrollOffset.scrollX = areaElement.pageXOffset;
  276. scrollOffset.scrollY = areaElement.pageYOffset
  277. }
  278. }
  279. return scrollOffset
  280. },
  281. _getAreaFromObject: function(area) {
  282. var result = {
  283. width: area.right - area.left,
  284. height: area.bottom - area.top,
  285. offset: {
  286. left: area.left,
  287. top: area.top
  288. }
  289. };
  290. this._correctAreaGeometry(result);
  291. return result
  292. },
  293. _getAreaFromElement: function(area) {
  294. var $area = $(area);
  295. var result;
  296. if ($area.length) {
  297. result = {
  298. width: $area.innerWidth(),
  299. height: $area.innerHeight(),
  300. offset: extend({
  301. top: 0,
  302. left: 0
  303. }, typeUtils.isWindow($area[0]) ? {} : $area.offset())
  304. };
  305. this._correctAreaGeometry(result, $area)
  306. }
  307. return result
  308. },
  309. _correctAreaGeometry: function(result, $area) {
  310. var areaBorderLeft = $area ? this._getBorderWidth($area, "left") : 0;
  311. var areaBorderTop = $area ? this._getBorderWidth($area, "top") : 0;
  312. result.offset.left += areaBorderLeft + this._getBorderWidth(this.$element(), "left");
  313. result.offset.top += areaBorderTop + this._getBorderWidth(this.$element(), "top");
  314. result.width -= this.$element().outerWidth() - this.$element().innerWidth();
  315. result.height -= this.$element().outerHeight() - this.$element().innerHeight()
  316. },
  317. _dragEndHandler: function(e) {
  318. var $element = this.$element();
  319. this._resizeEndAction({
  320. event: e,
  321. width: $element.outerWidth(),
  322. height: $element.outerHeight(),
  323. handles: this._movingSides
  324. });
  325. this._toggleResizingClass(false)
  326. },
  327. _renderWidth: function(width) {
  328. this.option("width", fitIntoRange(width, this.option("minWidth"), this.option("maxWidth")))
  329. },
  330. _renderHeight: function(height) {
  331. this.option("height", fitIntoRange(height, this.option("minHeight"), this.option("maxHeight")))
  332. },
  333. _optionChanged: function(args) {
  334. switch (args.name) {
  335. case "disabled":
  336. case "handles":
  337. this._invalidate();
  338. break;
  339. case "minWidth":
  340. case "maxWidth":
  341. windowUtils.hasWindow() && this._renderWidth(this.$element().outerWidth());
  342. break;
  343. case "minHeight":
  344. case "maxHeight":
  345. windowUtils.hasWindow() && this._renderHeight(this.$element().outerHeight());
  346. break;
  347. case "onResize":
  348. case "onResizeStart":
  349. case "onResizeEnd":
  350. this._renderActions();
  351. break;
  352. case "area":
  353. case "stepPrecision":
  354. case "step":
  355. case "roundStepValue":
  356. break;
  357. default:
  358. this.callBase(args)
  359. }
  360. },
  361. _clean: function() {
  362. this.$element().find("." + RESIZABLE_HANDLE_CLASS).remove()
  363. }
  364. });
  365. registerComponent(RESIZABLE, Resizable);
  366. module.exports = Resizable;
  367. module.exports.default = module.exports;