switch.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /**
  2. * DevExtreme (ui/switch.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 devices = require("../core/devices");
  13. var extend = require("../core/utils/extend").extend;
  14. var inkRipple = require("./widget/utils.ink_ripple");
  15. var registerComponent = require("../core/component_registrator");
  16. var Editor = require("./editor/editor");
  17. var eventUtils = require("../events/utils");
  18. var feedbackEvents = require("../events/core/emitter.feedback");
  19. var themes = require("./themes");
  20. var fx = require("../animation/fx");
  21. var messageLocalization = require("../localization/message");
  22. var clickEvent = require("../events/click");
  23. var Swipeable = require("../events/gesture/swipeable");
  24. var Deferred = require("../core/utils/deferred").Deferred;
  25. var SWITCH_CLASS = "dx-switch";
  26. var SWITCH_WRAPPER_CLASS = SWITCH_CLASS + "-wrapper";
  27. var SWITCH_CONTAINER_CLASS = SWITCH_CLASS + "-container";
  28. var SWITCH_INNER_CLASS = SWITCH_CLASS + "-inner";
  29. var SWITCH_HANDLE_CLASS = SWITCH_CLASS + "-handle";
  30. var SWITCH_ON_VALUE_CLASS = SWITCH_CLASS + "-on-value";
  31. var SWITCH_ON_CLASS = SWITCH_CLASS + "-on";
  32. var SWITCH_OFF_CLASS = SWITCH_CLASS + "-off";
  33. var SWITCH_ANIMATION_DURATION = 100;
  34. var Switch = Editor.inherit({
  35. _supportedKeys: function() {
  36. var isRTL = this.option("rtlEnabled");
  37. var click = function(e) {
  38. e.preventDefault();
  39. this._clickAction({
  40. event: e
  41. })
  42. };
  43. var move = function(value, e) {
  44. e.preventDefault();
  45. e.stopPropagation();
  46. this._animateValue(value)
  47. };
  48. return extend(this.callBase(), {
  49. space: click,
  50. enter: click,
  51. leftArrow: move.bind(this, isRTL ? true : false),
  52. rightArrow: move.bind(this, isRTL ? false : true)
  53. })
  54. },
  55. _getDefaultOptions: function() {
  56. return extend(this.callBase(), {
  57. hoverStateEnabled: true,
  58. activeStateEnabled: true,
  59. switchedOnText: this._getLocalizationMessage("On"),
  60. switchedOffText: this._getLocalizationMessage("Off"),
  61. value: false,
  62. useInkRipple: false,
  63. _animateHandle: true
  64. })
  65. },
  66. _defaultOptionsRules: function() {
  67. var themeName = themes.current();
  68. return this.callBase().concat([{
  69. device: function() {
  70. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  71. },
  72. options: {
  73. focusStateEnabled: true
  74. }
  75. }, {
  76. device: function(_device) {
  77. return themes.isIos7(themeName)
  78. },
  79. options: {
  80. _animateHandle: false
  81. }
  82. }])
  83. },
  84. _setDeprecatedOptions: function() {
  85. this.callBase();
  86. extend(this._deprecatedOptions, {
  87. onText: {
  88. since: "18.2",
  89. alias: "switchedOnText"
  90. },
  91. offText: {
  92. since: "18.2",
  93. alias: "switchedOffText"
  94. }
  95. })
  96. },
  97. _getLocalizationMessage: function(state) {
  98. var newMessage = messageLocalization.format("dxSwitch-switched" + state + "Text");
  99. var oldMessage = messageLocalization.format("dxSwitch-" + state.toLowerCase() + "Text");
  100. return newMessage || oldMessage
  101. },
  102. _feedbackHideTimeout: 0,
  103. _animating: false,
  104. _initMarkup: function() {
  105. this._renderContainers();
  106. this.option("useInkRipple") && this._renderInkRipple();
  107. this.$element().addClass(SWITCH_CLASS).append(this._$switchWrapper);
  108. this._renderSubmitElement();
  109. this._renderClick();
  110. this.setAria("role", "button");
  111. this._renderSwipeable();
  112. this.callBase();
  113. this._renderSwitchInner();
  114. this._renderLabels();
  115. this._renderValue()
  116. },
  117. _getInnerOffset: function(value, offset) {
  118. var ratio = (offset - this._offsetDirection() * Number(!value)) / 2;
  119. return 100 * ratio + "%"
  120. },
  121. _getHandleOffset: function(value, offset) {
  122. if (this.option("rtlEnabled")) {
  123. value = !value
  124. }
  125. if (value) {
  126. var calcValue = -100 + 100 * -offset;
  127. return calcValue + "%"
  128. } else {
  129. return 100 * -offset + "%"
  130. }
  131. },
  132. _renderSwitchInner: function() {
  133. this._$switchInner = $("<div>").addClass(SWITCH_INNER_CLASS).appendTo(this._$switchContainer);
  134. this._$handle = $("<div>").addClass(SWITCH_HANDLE_CLASS).appendTo(this._$switchInner)
  135. },
  136. _renderLabels: function() {
  137. this._$labelOn = $("<div>").addClass(SWITCH_ON_CLASS).prependTo(this._$switchInner);
  138. this._$labelOff = $("<div>").addClass(SWITCH_OFF_CLASS).appendTo(this._$switchInner);
  139. this._setLabelsText()
  140. },
  141. _renderContainers: function() {
  142. this._$switchContainer = $("<div>").addClass(SWITCH_CONTAINER_CLASS);
  143. this._$switchWrapper = $("<div>").addClass(SWITCH_WRAPPER_CLASS).append(this._$switchContainer)
  144. },
  145. _renderSwipeable: function() {
  146. this._createComponent(this.$element(), Swipeable, {
  147. elastic: false,
  148. immediate: true,
  149. onStart: this._swipeStartHandler.bind(this),
  150. onUpdated: this._swipeUpdateHandler.bind(this),
  151. onEnd: this._swipeEndHandler.bind(this),
  152. itemSizeFunc: this._getItemSizeFunc.bind(this)
  153. })
  154. },
  155. _getItemSizeFunc: function() {
  156. return this._$switchContainer.outerWidth(true) - this._$handle.get(0).getBoundingClientRect().width
  157. },
  158. _renderSubmitElement: function() {
  159. this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element())
  160. },
  161. _getSubmitElement: function() {
  162. return this._$submitElement
  163. },
  164. _renderInkRipple: function() {
  165. this._inkRipple = inkRipple.render({
  166. waveSizeCoefficient: 1.7,
  167. isCentered: true,
  168. useHoldAnimation: false,
  169. wavesNumber: 2
  170. })
  171. },
  172. _renderInkWave: function(element, dxEvent, doRender, waveIndex) {
  173. if (!this._inkRipple) {
  174. return
  175. }
  176. var config = {
  177. element: element,
  178. event: dxEvent,
  179. wave: waveIndex
  180. };
  181. if (doRender) {
  182. this._inkRipple.showWave(config)
  183. } else {
  184. this._inkRipple.hideWave(config)
  185. }
  186. },
  187. _updateFocusState: function(e, value) {
  188. this.callBase.apply(this, arguments);
  189. this._renderInkWave(this._$handle, e, value, 0)
  190. },
  191. _toggleActiveState: function($element, value, e) {
  192. this.callBase.apply(this, arguments);
  193. this._renderInkWave(this._$handle, e, value, 1)
  194. },
  195. _offsetDirection: function() {
  196. return this.option("rtlEnabled") ? -1 : 1
  197. },
  198. _renderPosition: function(state, swipeOffset) {
  199. var innerOffset = this._getInnerOffset(state, swipeOffset);
  200. var handleOffset = this._getHandleOffset(state, swipeOffset);
  201. if (this.option("_animateHandle")) {
  202. this._$switchInner.css("transform", " translateX(" + innerOffset + ")");
  203. this._$handle.css("transform", " translateX(" + handleOffset + ")")
  204. }
  205. },
  206. _validateValue: function() {
  207. var check = this.option("value");
  208. if ("boolean" !== typeof check) {
  209. this._options.value = !!check
  210. }
  211. },
  212. _renderClick: function() {
  213. var eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
  214. var $element = this.$element();
  215. this._clickAction = this._createAction(this._clickHandler.bind(this));
  216. eventsEngine.off($element, eventName);
  217. eventsEngine.on($element, eventName, function(e) {
  218. this._clickAction({
  219. event: e
  220. })
  221. }.bind(this))
  222. },
  223. _clickHandler: function(args) {
  224. var e = args.event;
  225. this._saveValueChangeEvent(e);
  226. if (this._animating || this._swiping) {
  227. return
  228. }
  229. this._animateValue(!this.option("value"))
  230. },
  231. _animateValue: function(value) {
  232. var startValue = this.option("value");
  233. var endValue = value;
  234. if (startValue === endValue) {
  235. return
  236. }
  237. this._animating = true;
  238. var fromInnerOffset = this._getInnerOffset(startValue, 0);
  239. var toInnerOffset = this._getInnerOffset(endValue, 0);
  240. var fromHandleOffset = this._getHandleOffset(startValue, 0);
  241. var toHandleOffset = this._getHandleOffset(endValue, 0);
  242. var that = this;
  243. var fromInnerConfig = {};
  244. var toInnerConfig = {};
  245. var fromHandleConfig = {};
  246. var toHandlerConfig = {};
  247. fromInnerConfig.transform = " translateX(" + fromInnerOffset + ")";
  248. toInnerConfig.transform = " translateX(" + toInnerOffset + ")";
  249. fromHandleConfig.transform = " translateX(" + fromHandleOffset + ")";
  250. toHandlerConfig.transform = " translateX(" + toHandleOffset + ")";
  251. this.$element().toggleClass(SWITCH_ON_VALUE_CLASS, endValue);
  252. fx.animate(this._$handle, {
  253. from: fromHandleConfig,
  254. to: toHandlerConfig,
  255. duration: SWITCH_ANIMATION_DURATION
  256. });
  257. fx.animate(this._$switchInner, {
  258. from: fromInnerConfig,
  259. to: toInnerConfig,
  260. duration: SWITCH_ANIMATION_DURATION,
  261. complete: function() {
  262. that._animating = false;
  263. that.option("value", endValue)
  264. }
  265. })
  266. },
  267. _swipeStartHandler: function(e) {
  268. var state = this.option("value");
  269. var rtlEnabled = this.option("rtlEnabled");
  270. var maxOffOffset = rtlEnabled ? 0 : 1;
  271. var maxOnOffset = rtlEnabled ? 1 : 0;
  272. e.event.maxLeftOffset = state ? maxOffOffset : maxOnOffset;
  273. e.event.maxRightOffset = state ? maxOnOffset : maxOffOffset;
  274. this._swiping = true;
  275. this._feedbackDeferred = new Deferred;
  276. feedbackEvents.lock(this._feedbackDeferred);
  277. this._toggleActiveState(this.$element(), this.option("activeStateEnabled"))
  278. },
  279. _swipeUpdateHandler: function(e) {
  280. this._renderPosition(this.option("value"), e.event.offset)
  281. },
  282. _swipeEndHandler: function(e) {
  283. var that = this;
  284. var offsetDirection = this._offsetDirection();
  285. var toInnerConfig = {};
  286. var toHandleConfig = {};
  287. var innerOffset = this._getInnerOffset(that.option("value"), e.event.targetOffset);
  288. var handleOffset = this._getHandleOffset(that.option("value"), e.event.targetOffset);
  289. toInnerConfig.transform = " translateX(" + innerOffset + ")";
  290. toHandleConfig.transform = " translateX(" + handleOffset + ")";
  291. fx.animate(this._$handle, {
  292. to: toHandleConfig,
  293. duration: SWITCH_ANIMATION_DURATION
  294. });
  295. fx.animate(this._$switchInner, {
  296. to: toInnerConfig,
  297. duration: SWITCH_ANIMATION_DURATION,
  298. complete: function() {
  299. that._swiping = false;
  300. var pos = that.option("value") + offsetDirection * e.event.targetOffset;
  301. that._saveValueChangeEvent(e.event);
  302. that.option("value", Boolean(pos));
  303. that._feedbackDeferred.resolve();
  304. that._toggleActiveState(that.$element(), false)
  305. }
  306. })
  307. },
  308. _renderValue: function() {
  309. this._validateValue();
  310. var val = this.option("value");
  311. this._renderPosition(val, 0);
  312. this.$element().toggleClass(SWITCH_ON_VALUE_CLASS, val);
  313. this._getSubmitElement().val(val);
  314. this.setAria({
  315. pressed: val,
  316. label: val ? this.option("switchedOnText") : this.option("switchedOffText")
  317. })
  318. },
  319. _setLabelsText: function() {
  320. this._$labelOn && this._$labelOn.text(this.option("switchedOnText"));
  321. this._$labelOff && this._$labelOff.text(this.option("switchedOffText"))
  322. },
  323. _visibilityChanged: function(visible) {
  324. if (visible) {
  325. this.repaint()
  326. }
  327. },
  328. _optionChanged: function(args) {
  329. switch (args.name) {
  330. case "useInkRipple":
  331. this._invalidate();
  332. break;
  333. case "width":
  334. delete this._marginBound;
  335. this._refresh();
  336. break;
  337. case "switchedOnText":
  338. case "switchedOffText":
  339. this._setLabelsText();
  340. break;
  341. case "value":
  342. this._renderValue();
  343. this.callBase(args);
  344. break;
  345. case "_animateHandle":
  346. break;
  347. default:
  348. this.callBase(args)
  349. }
  350. },
  351. _clean: function() {
  352. delete this._inkRipple;
  353. this.callBase()
  354. }
  355. });
  356. registerComponent("dxSwitch", Switch);
  357. module.exports = Switch;
  358. module.exports.default = module.exports;