popover.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /**
  2. * DevExtreme (ui/popover.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. function _typeof(obj) {
  11. "@babel/helpers - typeof";
  12. return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj) {
  13. return typeof obj
  14. } : function(obj) {
  15. return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj
  16. }, _typeof(obj)
  17. }
  18. var $ = require("../core/renderer");
  19. var windowUtils = require("../core/utils/window");
  20. var window = windowUtils.getWindow();
  21. var getPublicElement = require("../core/utils/dom").getPublicElement;
  22. var domAdapter = require("../core/dom_adapter");
  23. var eventsEngine = require("../events/core/events_engine");
  24. var registerComponent = require("../core/component_registrator");
  25. var commonUtils = require("../core/utils/common");
  26. var extend = require("../core/utils/extend").extend;
  27. var browser = require("../core/utils/browser");
  28. var translator = require("../animation/translator");
  29. var positionUtils = require("../animation/position");
  30. var typeUtils = require("../core/utils/type");
  31. var mathUtils = require("../core/utils/math");
  32. var eventUtils = require("../events/utils");
  33. var Popup = require("./popup");
  34. var POPOVER_CLASS = "dx-popover";
  35. var POPOVER_WRAPPER_CLASS = "dx-popover-wrapper";
  36. var POPOVER_ARROW_CLASS = "dx-popover-arrow";
  37. var POPOVER_WITHOUT_TITLE_CLASS = "dx-popover-without-title";
  38. var POSITION_FLIP_MAP = {
  39. left: "right",
  40. top: "bottom",
  41. right: "left",
  42. bottom: "top",
  43. center: "center"
  44. };
  45. var WEIGHT_OF_SIDES = {
  46. left: -1,
  47. top: -1,
  48. center: 0,
  49. right: 1,
  50. bottom: 1
  51. };
  52. var POSITION_ALIASES = {
  53. top: {
  54. my: "bottom center",
  55. at: "top center",
  56. collision: "fit flip"
  57. },
  58. bottom: {
  59. my: "top center",
  60. at: "bottom center",
  61. collision: "fit flip"
  62. },
  63. right: {
  64. my: "left center",
  65. at: "right center",
  66. collision: "flip fit"
  67. },
  68. left: {
  69. my: "right center",
  70. at: "left center",
  71. collision: "flip fit"
  72. }
  73. };
  74. var SIDE_BORDER_WIDTH_STYLES = {
  75. left: "borderLeftWidth",
  76. top: "borderTopWidth",
  77. right: "borderRightWidth",
  78. bottom: "borderBottomWidth"
  79. };
  80. var isFirefox = browser.mozilla;
  81. var getEventName = function(that, optionName) {
  82. var optionValue = that.option(optionName);
  83. return getEventNameByOption(optionValue)
  84. };
  85. var getEventNameByOption = function(optionValue) {
  86. return typeUtils.isObject(optionValue) ? optionValue.name : optionValue
  87. };
  88. var getEventDelay = function(that, optionName) {
  89. var optionValue = that.option(optionName);
  90. return typeUtils.isObject(optionValue) && optionValue.delay
  91. };
  92. var attachEvent = function(that, name) {
  93. var target = that.option("target");
  94. var isSelector = typeUtils.isString(target);
  95. var event = getEventName(that, name + "Event");
  96. if (!event || that.option("disabled")) {
  97. return
  98. }
  99. var eventName = eventUtils.addNamespace(event, that.NAME);
  100. var action = that._createAction(function() {
  101. var delay = getEventDelay(that, name + "Event");
  102. this._clearEventsTimeouts();
  103. if (delay) {
  104. this._timeouts[name] = setTimeout(function() {
  105. that[name]()
  106. }, delay)
  107. } else {
  108. that[name]()
  109. }
  110. }.bind(that), {
  111. validatingTargetName: "target"
  112. });
  113. var handler = function(e) {
  114. action({
  115. event: e,
  116. target: $(e.currentTarget)
  117. })
  118. };
  119. var EVENT_HANDLER_NAME = "_" + name + "EventHandler";
  120. if (isSelector) {
  121. that[EVENT_HANDLER_NAME] = handler;
  122. eventsEngine.on(domAdapter.getDocument(), eventName, target, handler)
  123. } else {
  124. var targetElement = getPublicElement($(target));
  125. that[EVENT_HANDLER_NAME] = void 0;
  126. eventsEngine.on(targetElement, eventName, handler)
  127. }
  128. };
  129. var detachEvent = function(that, target, name, event) {
  130. var eventName = event || getEventName(that, name + "Event");
  131. if (!eventName) {
  132. return
  133. }
  134. eventName = eventUtils.addNamespace(eventName, that.NAME);
  135. var EVENT_HANDLER_NAME = "_" + name + "EventHandler";
  136. if (that[EVENT_HANDLER_NAME]) {
  137. eventsEngine.off(domAdapter.getDocument(), eventName, target, that[EVENT_HANDLER_NAME])
  138. } else {
  139. eventsEngine.off(getPublicElement($(target)), eventName)
  140. }
  141. };
  142. var Popover = Popup.inherit({
  143. _getDefaultOptions: function() {
  144. return extend(this.callBase(), {
  145. target: window,
  146. shading: false,
  147. position: "bottom",
  148. closeOnOutsideClick: true,
  149. animation: {
  150. show: {
  151. type: "fade",
  152. from: 0,
  153. to: 1
  154. },
  155. hide: {
  156. type: "fade",
  157. to: 0
  158. }
  159. },
  160. showTitle: false,
  161. width: "auto",
  162. height: "auto",
  163. dragEnabled: false,
  164. resizeEnabled: false,
  165. fullScreen: false,
  166. closeOnTargetScroll: true,
  167. arrowPosition: "",
  168. arrowOffset: 0,
  169. boundaryOffset: {
  170. h: 10,
  171. v: 10
  172. }
  173. })
  174. },
  175. _defaultOptionsRules: function() {
  176. return [{
  177. device: {
  178. platform: "ios"
  179. },
  180. options: {
  181. arrowPosition: {
  182. boundaryOffset: {
  183. h: 20,
  184. v: -10
  185. },
  186. collision: "fit"
  187. }
  188. }
  189. }, {
  190. device: function() {
  191. return !windowUtils.hasWindow()
  192. },
  193. options: {
  194. animation: null
  195. }
  196. }]
  197. },
  198. _init: function() {
  199. this.callBase();
  200. this._renderArrow();
  201. this._timeouts = {};
  202. this.$element().addClass(POPOVER_CLASS);
  203. this._wrapper().addClass(POPOVER_WRAPPER_CLASS)
  204. },
  205. _render: function() {
  206. this.callBase.apply(this, arguments);
  207. this._detachEvents(this.option("target"));
  208. this._attachEvents()
  209. },
  210. _detachEvents: function(target) {
  211. detachEvent(this, target, "show");
  212. detachEvent(this, target, "hide")
  213. },
  214. _attachEvents: function() {
  215. attachEvent(this, "show");
  216. attachEvent(this, "hide")
  217. },
  218. _renderArrow: function() {
  219. this._$arrow = $("<div>").addClass(POPOVER_ARROW_CLASS).prependTo(this.overlayContent())
  220. },
  221. _documentDownHandler: function(e) {
  222. if (this._isOutsideClick(e)) {
  223. return this.callBase(e)
  224. }
  225. return true
  226. },
  227. _isOutsideClick: function(e) {
  228. return !$(e.target).closest(this.option("target")).length
  229. },
  230. _animate: function(animation) {
  231. if (animation && animation.to && "object" === _typeof(animation.to)) {
  232. extend(animation.to, {
  233. position: this._getContainerPosition()
  234. })
  235. }
  236. this.callBase.apply(this, arguments)
  237. },
  238. _stopAnimation: function() {
  239. this.callBase.apply(this, arguments)
  240. },
  241. _renderTitle: function() {
  242. this._wrapper().toggleClass(POPOVER_WITHOUT_TITLE_CLASS, !this.option("showTitle"));
  243. this.callBase()
  244. },
  245. _renderPosition: function() {
  246. this.callBase();
  247. this._renderOverlayPosition()
  248. },
  249. _renderOverlayBoundaryOffset: commonUtils.noop,
  250. _renderOverlayPosition: function() {
  251. this._resetOverlayPosition();
  252. this._updateContentSize();
  253. var contentPosition = this._getContainerPosition();
  254. var resultLocation = positionUtils.setup(this._$content, contentPosition);
  255. var positionSide = this._getSideByLocation(resultLocation);
  256. this._togglePositionClass("dx-position-" + positionSide);
  257. this._toggleFlippedClass(resultLocation.h.flip, resultLocation.v.flip);
  258. var isArrowVisible = this._isHorizontalSide() || this._isVerticalSide();
  259. if (isArrowVisible) {
  260. this._renderArrowPosition(positionSide)
  261. }
  262. },
  263. _resetOverlayPosition: function() {
  264. this._setContentHeight(true);
  265. this._togglePositionClass("dx-position-" + this._positionSide);
  266. translator.move(this._$content, {
  267. left: 0,
  268. top: 0
  269. });
  270. this._$arrow.css({
  271. top: "auto",
  272. right: "auto",
  273. bottom: "auto",
  274. left: "auto"
  275. })
  276. },
  277. _updateContentSize: function() {
  278. if (!this._$popupContent) {
  279. return
  280. }
  281. var containerLocation = positionUtils.calculate(this._$content, this._getContainerPosition());
  282. if (containerLocation.h.oversize > 0 && this._isHorizontalSide() && !containerLocation.h.fit) {
  283. var newContainerWidth = this._$content.width() - containerLocation.h.oversize;
  284. this._$content.width(newContainerWidth)
  285. }
  286. if (containerLocation.v.oversize > 0 && this._isVerticalSide() && !containerLocation.v.fit) {
  287. var newOverlayContentHeight = this._$content.height() - containerLocation.v.oversize;
  288. var newPopupContentHeight = this._$popupContent.height() - containerLocation.v.oversize;
  289. this._$content.height(newOverlayContentHeight);
  290. this._$popupContent.height(newPopupContentHeight)
  291. }
  292. },
  293. _getContainerPosition: function() {
  294. var offset = commonUtils.pairToObject(this._position.offset || "");
  295. var hOffset = offset.h;
  296. var vOffset = offset.v;
  297. var isVerticalSide = this._isVerticalSide();
  298. var isHorizontalSide = this._isHorizontalSide();
  299. if (isVerticalSide || isHorizontalSide) {
  300. var isPopoverInside = this._isPopoverInside();
  301. var sign = (isPopoverInside ? -1 : 1) * WEIGHT_OF_SIDES[this._positionSide];
  302. var arrowSize = isVerticalSide ? this._$arrow.height() : this._$arrow.width();
  303. var arrowSizeCorrection = this._getContentBorderWidth(this._positionSide);
  304. var arrowOffset = sign * (arrowSize - arrowSizeCorrection);
  305. isVerticalSide ? vOffset += arrowOffset : hOffset += arrowOffset
  306. }
  307. return extend({}, this._position, {
  308. offset: hOffset + " " + vOffset
  309. })
  310. },
  311. _getContentBorderWidth: function(side) {
  312. var borderWidth = this._$content.css(SIDE_BORDER_WIDTH_STYLES[side]);
  313. return parseInt(borderWidth) || 0
  314. },
  315. _getSideByLocation: function(location) {
  316. var isFlippedByVertical = location.v.flip;
  317. var isFlippedByHorizontal = location.h.flip;
  318. return this._isVerticalSide() && isFlippedByVertical || this._isHorizontalSide() && isFlippedByHorizontal || this._isPopoverInside() ? POSITION_FLIP_MAP[this._positionSide] : this._positionSide
  319. },
  320. _togglePositionClass: function(positionClass) {
  321. this._$wrapper.removeClass("dx-position-left dx-position-right dx-position-top dx-position-bottom").addClass(positionClass)
  322. },
  323. _toggleFlippedClass: function(isFlippedHorizontal, isFlippedVertical) {
  324. this._$wrapper.toggleClass("dx-popover-flipped-horizontal", isFlippedHorizontal).toggleClass("dx-popover-flipped-vertical", isFlippedVertical)
  325. },
  326. _renderArrowPosition: function(side) {
  327. this._$arrow.css(POSITION_FLIP_MAP[side], -(this._isVerticalSide(side) ? this._$arrow.height() : this._$arrow.width()));
  328. var axis = this._isVerticalSide(side) ? "left" : "top";
  329. var sizeProperty = this._isVerticalSide(side) ? "outerWidth" : "outerHeight";
  330. var $target = $(this._position.of);
  331. var targetOffset = positionUtils.offset($target) || {
  332. top: 0,
  333. left: 0
  334. };
  335. var contentOffset = positionUtils.offset(this._$content);
  336. var arrowSize = this._$arrow[sizeProperty]();
  337. var contentLocation = contentOffset[axis];
  338. var contentSize = this._$content[sizeProperty]();
  339. var targetLocation = targetOffset[axis];
  340. var targetSize = $target.get(0).preventDefault ? 0 : $target[sizeProperty]();
  341. var min = Math.max(contentLocation, targetLocation);
  342. var max = Math.min(contentLocation + contentSize, targetLocation + targetSize);
  343. var arrowLocation;
  344. if ("start" === this.option("arrowPosition")) {
  345. arrowLocation = min - contentLocation
  346. } else {
  347. if ("end" === this.option("arrowPosition")) {
  348. arrowLocation = max - contentLocation - arrowSize
  349. } else {
  350. arrowLocation = (min + max) / 2 - contentLocation - arrowSize / 2
  351. }
  352. }
  353. var borderWidth = this._getContentBorderWidth(side);
  354. var finalArrowLocation = mathUtils.fitIntoRange(arrowLocation - borderWidth + this.option("arrowOffset"), borderWidth, contentSize - arrowSize - 2 * borderWidth);
  355. this._$arrow.css(axis, finalArrowLocation)
  356. },
  357. _isPopoverInside: function() {
  358. var position = this._transformStringPosition(this.option("position"), POSITION_ALIASES);
  359. var my = positionUtils.setup.normalizeAlign(position.my);
  360. var at = positionUtils.setup.normalizeAlign(position.at);
  361. return my.h === at.h && my.v === at.v
  362. },
  363. _setContentHeight: function(fullUpdate) {
  364. if (fullUpdate) {
  365. this.callBase()
  366. }
  367. },
  368. _renderShadingPosition: function() {
  369. if (this.option("shading")) {
  370. this._$wrapper.css({
  371. top: 0,
  372. left: 0
  373. })
  374. }
  375. },
  376. _renderShadingDimensions: function() {
  377. if (this.option("shading")) {
  378. this._$wrapper.css({
  379. width: "100%",
  380. height: "100%"
  381. })
  382. }
  383. },
  384. _normalizePosition: function() {
  385. var position = extend({}, this._transformStringPosition(this.option("position"), POSITION_ALIASES));
  386. if (!position.of) {
  387. position.of = this.option("target")
  388. }
  389. if (!position.collision) {
  390. position.collision = "flip"
  391. }
  392. if (!position.boundaryOffset) {
  393. position.boundaryOffset = this.option("boundaryOffset")
  394. }
  395. this._positionSide = this._getDisplaySide(position);
  396. this._position = position
  397. },
  398. _getDisplaySide: function(position) {
  399. var my = positionUtils.setup.normalizeAlign(position.my);
  400. var at = positionUtils.setup.normalizeAlign(position.at);
  401. var weightSign = WEIGHT_OF_SIDES[my.h] === WEIGHT_OF_SIDES[at.h] && WEIGHT_OF_SIDES[my.v] === WEIGHT_OF_SIDES[at.v] ? -1 : 1;
  402. var horizontalWeight = Math.abs(WEIGHT_OF_SIDES[my.h] - weightSign * WEIGHT_OF_SIDES[at.h]);
  403. var verticalWeight = Math.abs(WEIGHT_OF_SIDES[my.v] - weightSign * WEIGHT_OF_SIDES[at.v]);
  404. return horizontalWeight > verticalWeight ? at.h : at.v
  405. },
  406. _resetContentHeight: function() {
  407. this.callBase();
  408. if (isFirefox) {
  409. var originalOverflow = this._$popupContent.css("overflow");
  410. this._$popupContent.css("overflow", "visible");
  411. this._$popupContent.css("overflow", originalOverflow)
  412. }
  413. },
  414. _isVerticalSide: function(side) {
  415. side = side || this._positionSide;
  416. return "top" === side || "bottom" === side
  417. },
  418. _isHorizontalSide: function(side) {
  419. side = side || this._positionSide;
  420. return "left" === side || "right" === side
  421. },
  422. _clearEventTimeout: function(name) {
  423. clearTimeout(this._timeouts[name])
  424. },
  425. _clearEventsTimeouts: function() {
  426. this._clearEventTimeout("show");
  427. this._clearEventTimeout("hide")
  428. },
  429. _clean: function() {
  430. this._detachEvents(this.option("target"));
  431. this.callBase.apply(this, arguments)
  432. },
  433. _optionChanged: function(args) {
  434. switch (args.name) {
  435. case "boundaryOffset":
  436. case "arrowPosition":
  437. case "arrowOffset":
  438. this._renderGeometry();
  439. break;
  440. case "fullScreen":
  441. if (args.value) {
  442. this.option("fullScreen", false)
  443. }
  444. break;
  445. case "target":
  446. args.previousValue && this._detachEvents(args.previousValue);
  447. this.callBase(args);
  448. break;
  449. case "showEvent":
  450. case "hideEvent":
  451. var name = args.name.substring(0, 4);
  452. var event = getEventNameByOption(args.previousValue);
  453. this.hide();
  454. detachEvent(this, this.option("target"), name, event);
  455. attachEvent(this, name);
  456. break;
  457. case "visible":
  458. this._clearEventTimeout(args.value ? "show" : "hide");
  459. this.callBase(args);
  460. break;
  461. default:
  462. this.callBase(args)
  463. }
  464. },
  465. show: function(target) {
  466. if (target) {
  467. this.option("target", target)
  468. }
  469. return this.callBase()
  470. }
  471. });
  472. registerComponent("dxPopover", Popover);
  473. module.exports = Popover;
  474. module.exports.default = module.exports;