foundation.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035
  1. /**
  2. * @license
  3. * Copyright 2020 Google Inc.
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. */
  23. import { __assign, __extends } from "tslib";
  24. import { AnimationFrame } from '@material/animation/animationframe';
  25. import { getCorrectPropertyName } from '@material/animation/util';
  26. import { MDCFoundation } from '@material/base/foundation';
  27. import { attributes, cssClasses, numbers, strings } from './constants';
  28. import { Thumb, TickMark } from './types';
  29. var AnimationKeys;
  30. (function (AnimationKeys) {
  31. AnimationKeys["SLIDER_UPDATE"] = "slider_update";
  32. })(AnimationKeys || (AnimationKeys = {}));
  33. // Accessing `window` without a `typeof` check will throw on Node environments.
  34. var HAS_WINDOW = typeof window !== 'undefined';
  35. /**
  36. * Foundation class for slider. Responsibilities include:
  37. * - Updating slider values (internal state and DOM updates) based on client
  38. * 'x' position.
  39. * - Updating DOM after slider property updates (e.g. min, max).
  40. */
  41. var MDCSliderFoundation = /** @class */ (function (_super) {
  42. __extends(MDCSliderFoundation, _super);
  43. function MDCSliderFoundation(adapter) {
  44. var _this = _super.call(this, __assign(__assign({}, MDCSliderFoundation.defaultAdapter), adapter)) || this;
  45. // Whether the initial styles (to position the thumb, before component
  46. // initialization) have been removed.
  47. _this.initialStylesRemoved = false;
  48. _this.isDisabled = false;
  49. _this.isDiscrete = false;
  50. _this.step = numbers.STEP_SIZE;
  51. _this.minRange = numbers.MIN_RANGE;
  52. _this.hasTickMarks = false;
  53. // The following properties are only set for range sliders.
  54. _this.isRange = false;
  55. // Tracks the thumb being moved across a slider pointer interaction (down,
  56. // move event).
  57. _this.thumb = null;
  58. // `clientX` from the most recent down event. Used in subsequent move
  59. // events to determine which thumb to move (in the case of
  60. // overlapping thumbs).
  61. _this.downEventClientX = null;
  62. // Width of the start thumb knob.
  63. _this.startThumbKnobWidth = 0;
  64. // Width of the end thumb knob.
  65. _this.endThumbKnobWidth = 0;
  66. _this.animFrame = new AnimationFrame();
  67. return _this;
  68. }
  69. Object.defineProperty(MDCSliderFoundation, "defaultAdapter", {
  70. get: function () {
  71. // tslint:disable:object-literal-sort-keys Methods should be in the same
  72. // order as the adapter interface.
  73. return {
  74. hasClass: function () { return false; },
  75. addClass: function () { return undefined; },
  76. removeClass: function () { return undefined; },
  77. addThumbClass: function () { return undefined; },
  78. removeThumbClass: function () { return undefined; },
  79. getAttribute: function () { return null; },
  80. getInputValue: function () { return ''; },
  81. setInputValue: function () { return undefined; },
  82. getInputAttribute: function () { return null; },
  83. setInputAttribute: function () { return null; },
  84. removeInputAttribute: function () { return null; },
  85. focusInput: function () { return undefined; },
  86. isInputFocused: function () { return false; },
  87. shouldHideFocusStylesForPointerEvents: function () { return false; },
  88. getThumbKnobWidth: function () { return 0; },
  89. getValueIndicatorContainerWidth: function () { return 0; },
  90. getThumbBoundingClientRect: function () {
  91. return ({ top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 });
  92. },
  93. getBoundingClientRect: function () {
  94. return ({ top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 });
  95. },
  96. isRTL: function () { return false; },
  97. setThumbStyleProperty: function () { return undefined; },
  98. removeThumbStyleProperty: function () { return undefined; },
  99. setTrackActiveStyleProperty: function () { return undefined; },
  100. removeTrackActiveStyleProperty: function () { return undefined; },
  101. setValueIndicatorText: function () { return undefined; },
  102. getValueToAriaValueTextFn: function () { return null; },
  103. updateTickMarks: function () { return undefined; },
  104. setPointerCapture: function () { return undefined; },
  105. emitChangeEvent: function () { return undefined; },
  106. emitInputEvent: function () { return undefined; },
  107. emitDragStartEvent: function () { return undefined; },
  108. emitDragEndEvent: function () { return undefined; },
  109. registerEventHandler: function () { return undefined; },
  110. deregisterEventHandler: function () { return undefined; },
  111. registerThumbEventHandler: function () { return undefined; },
  112. deregisterThumbEventHandler: function () { return undefined; },
  113. registerInputEventHandler: function () { return undefined; },
  114. deregisterInputEventHandler: function () { return undefined; },
  115. registerBodyEventHandler: function () { return undefined; },
  116. deregisterBodyEventHandler: function () { return undefined; },
  117. registerWindowEventHandler: function () { return undefined; },
  118. deregisterWindowEventHandler: function () { return undefined; },
  119. };
  120. // tslint:enable:object-literal-sort-keys
  121. },
  122. enumerable: false,
  123. configurable: true
  124. });
  125. MDCSliderFoundation.prototype.init = function () {
  126. var _this = this;
  127. this.isDisabled = this.adapter.hasClass(cssClasses.DISABLED);
  128. this.isDiscrete = this.adapter.hasClass(cssClasses.DISCRETE);
  129. this.hasTickMarks = this.adapter.hasClass(cssClasses.TICK_MARKS);
  130. this.isRange = this.adapter.hasClass(cssClasses.RANGE);
  131. var min = this.convertAttributeValueToNumber(this.adapter.getInputAttribute(attributes.INPUT_MIN, this.isRange ? Thumb.START : Thumb.END), attributes.INPUT_MIN);
  132. var max = this.convertAttributeValueToNumber(this.adapter.getInputAttribute(attributes.INPUT_MAX, Thumb.END), attributes.INPUT_MAX);
  133. var value = this.convertAttributeValueToNumber(this.adapter.getInputAttribute(attributes.INPUT_VALUE, Thumb.END), attributes.INPUT_VALUE);
  134. var valueStart = this.isRange ?
  135. this.convertAttributeValueToNumber(this.adapter.getInputAttribute(attributes.INPUT_VALUE, Thumb.START), attributes.INPUT_VALUE) :
  136. min;
  137. var stepAttr = this.adapter.getInputAttribute(attributes.INPUT_STEP, Thumb.END);
  138. var step = stepAttr ?
  139. this.convertAttributeValueToNumber(stepAttr, attributes.INPUT_STEP) :
  140. this.step;
  141. var minRangeAttr = this.adapter.getAttribute(attributes.DATA_MIN_RANGE);
  142. var minRange = minRangeAttr ?
  143. this.convertAttributeValueToNumber(minRangeAttr, attributes.DATA_MIN_RANGE) :
  144. this.minRange;
  145. this.validateProperties({ min: min, max: max, value: value, valueStart: valueStart, step: step, minRange: minRange });
  146. this.min = min;
  147. this.max = max;
  148. this.value = value;
  149. this.valueStart = valueStart;
  150. this.step = step;
  151. this.minRange = minRange;
  152. this.numDecimalPlaces = getNumDecimalPlaces(this.step);
  153. this.valueBeforeDownEvent = value;
  154. this.valueStartBeforeDownEvent = valueStart;
  155. this.mousedownOrTouchstartListener =
  156. this.handleMousedownOrTouchstart.bind(this);
  157. this.moveListener = this.handleMove.bind(this);
  158. this.pointerdownListener = this.handlePointerdown.bind(this);
  159. this.pointerupListener = this.handlePointerup.bind(this);
  160. this.thumbMouseenterListener = this.handleThumbMouseenter.bind(this);
  161. this.thumbMouseleaveListener = this.handleThumbMouseleave.bind(this);
  162. this.inputStartChangeListener = function () {
  163. _this.handleInputChange(Thumb.START);
  164. };
  165. this.inputEndChangeListener = function () {
  166. _this.handleInputChange(Thumb.END);
  167. };
  168. this.inputStartFocusListener = function () {
  169. _this.handleInputFocus(Thumb.START);
  170. };
  171. this.inputEndFocusListener = function () {
  172. _this.handleInputFocus(Thumb.END);
  173. };
  174. this.inputStartBlurListener = function () {
  175. _this.handleInputBlur(Thumb.START);
  176. };
  177. this.inputEndBlurListener = function () {
  178. _this.handleInputBlur(Thumb.END);
  179. };
  180. this.resizeListener = this.handleResize.bind(this);
  181. this.registerEventHandlers();
  182. };
  183. MDCSliderFoundation.prototype.destroy = function () {
  184. this.deregisterEventHandlers();
  185. };
  186. MDCSliderFoundation.prototype.setMin = function (value) {
  187. this.min = value;
  188. if (!this.isRange) {
  189. this.valueStart = value;
  190. }
  191. this.updateUI();
  192. };
  193. MDCSliderFoundation.prototype.setMax = function (value) {
  194. this.max = value;
  195. this.updateUI();
  196. };
  197. MDCSliderFoundation.prototype.getMin = function () {
  198. return this.min;
  199. };
  200. MDCSliderFoundation.prototype.getMax = function () {
  201. return this.max;
  202. };
  203. /**
  204. * - For single point sliders, returns the thumb value.
  205. * - For range (two-thumb) sliders, returns the end thumb's value.
  206. */
  207. MDCSliderFoundation.prototype.getValue = function () {
  208. return this.value;
  209. };
  210. /**
  211. * - For single point sliders, sets the thumb value.
  212. * - For range (two-thumb) sliders, sets the end thumb's value.
  213. */
  214. MDCSliderFoundation.prototype.setValue = function (value) {
  215. if (this.isRange && value < this.valueStart + this.minRange) {
  216. throw new Error("end thumb value (" + value + ") must be >= start thumb " +
  217. ("value (" + this.valueStart + ") + min range (" + this.minRange + ")"));
  218. }
  219. this.updateValue(value, Thumb.END);
  220. };
  221. /**
  222. * Only applicable for range sliders.
  223. * @return The start thumb's value.
  224. */
  225. MDCSliderFoundation.prototype.getValueStart = function () {
  226. if (!this.isRange) {
  227. throw new Error('`valueStart` is only applicable for range sliders.');
  228. }
  229. return this.valueStart;
  230. };
  231. /**
  232. * Only applicable for range sliders. Sets the start thumb's value.
  233. */
  234. MDCSliderFoundation.prototype.setValueStart = function (valueStart) {
  235. if (!this.isRange) {
  236. throw new Error('`valueStart` is only applicable for range sliders.');
  237. }
  238. if (this.isRange && valueStart > this.value - this.minRange) {
  239. throw new Error("start thumb value (" + valueStart + ") must be <= end thumb " +
  240. ("value (" + this.value + ") - min range (" + this.minRange + ")"));
  241. }
  242. this.updateValue(valueStart, Thumb.START);
  243. };
  244. MDCSliderFoundation.prototype.setStep = function (value) {
  245. this.step = value;
  246. this.numDecimalPlaces = getNumDecimalPlaces(value);
  247. this.updateUI();
  248. };
  249. /**
  250. * Only applicable for range sliders. Sets the minimum difference between the
  251. * start and end values.
  252. */
  253. MDCSliderFoundation.prototype.setMinRange = function (value) {
  254. if (!this.isRange) {
  255. throw new Error('`minRange` is only applicable for range sliders.');
  256. }
  257. if (value < 0) {
  258. throw new Error('`minRange` must be non-negative. ' +
  259. ("Current value: " + value));
  260. }
  261. if (this.value - this.valueStart < value) {
  262. throw new Error("start thumb value (" + this.valueStart + ") and end thumb value " +
  263. ("(" + this.value + ") must differ by at least " + value + "."));
  264. }
  265. this.minRange = value;
  266. };
  267. MDCSliderFoundation.prototype.setIsDiscrete = function (value) {
  268. this.isDiscrete = value;
  269. this.updateValueIndicatorUI();
  270. this.updateTickMarksUI();
  271. };
  272. MDCSliderFoundation.prototype.getStep = function () {
  273. return this.step;
  274. };
  275. MDCSliderFoundation.prototype.getMinRange = function () {
  276. if (!this.isRange) {
  277. throw new Error('`minRange` is only applicable for range sliders.');
  278. }
  279. return this.minRange;
  280. };
  281. MDCSliderFoundation.prototype.setHasTickMarks = function (value) {
  282. this.hasTickMarks = value;
  283. this.updateTickMarksUI();
  284. };
  285. MDCSliderFoundation.prototype.getDisabled = function () {
  286. return this.isDisabled;
  287. };
  288. /**
  289. * Sets disabled state, including updating styles and thumb tabindex.
  290. */
  291. MDCSliderFoundation.prototype.setDisabled = function (disabled) {
  292. this.isDisabled = disabled;
  293. if (disabled) {
  294. this.adapter.addClass(cssClasses.DISABLED);
  295. if (this.isRange) {
  296. this.adapter.setInputAttribute(attributes.INPUT_DISABLED, '', Thumb.START);
  297. }
  298. this.adapter.setInputAttribute(attributes.INPUT_DISABLED, '', Thumb.END);
  299. }
  300. else {
  301. this.adapter.removeClass(cssClasses.DISABLED);
  302. if (this.isRange) {
  303. this.adapter.removeInputAttribute(attributes.INPUT_DISABLED, Thumb.START);
  304. }
  305. this.adapter.removeInputAttribute(attributes.INPUT_DISABLED, Thumb.END);
  306. }
  307. };
  308. /** @return Whether the slider is a range slider. */
  309. MDCSliderFoundation.prototype.getIsRange = function () {
  310. return this.isRange;
  311. };
  312. /**
  313. * - Syncs slider boundingClientRect with the current DOM.
  314. * - Updates UI based on internal state.
  315. */
  316. MDCSliderFoundation.prototype.layout = function (_a) {
  317. var _b = _a === void 0 ? {} : _a, skipUpdateUI = _b.skipUpdateUI;
  318. this.rect = this.adapter.getBoundingClientRect();
  319. if (this.isRange) {
  320. this.startThumbKnobWidth = this.adapter.getThumbKnobWidth(Thumb.START);
  321. this.endThumbKnobWidth = this.adapter.getThumbKnobWidth(Thumb.END);
  322. }
  323. if (!skipUpdateUI) {
  324. this.updateUI();
  325. }
  326. };
  327. /** Handles resize events on the window. */
  328. MDCSliderFoundation.prototype.handleResize = function () {
  329. this.layout();
  330. };
  331. /**
  332. * Handles pointer down events on the slider root element.
  333. */
  334. MDCSliderFoundation.prototype.handleDown = function (event) {
  335. if (this.isDisabled)
  336. return;
  337. this.valueStartBeforeDownEvent = this.valueStart;
  338. this.valueBeforeDownEvent = this.value;
  339. var clientX = event.clientX != null ?
  340. event.clientX :
  341. event.targetTouches[0].clientX;
  342. this.downEventClientX = clientX;
  343. var value = this.mapClientXOnSliderScale(clientX);
  344. this.thumb = this.getThumbFromDownEvent(clientX, value);
  345. if (this.thumb === null)
  346. return;
  347. this.handleDragStart(event, value, this.thumb);
  348. this.updateValue(value, this.thumb, { emitInputEvent: true });
  349. };
  350. /**
  351. * Handles pointer move events on the slider root element.
  352. */
  353. MDCSliderFoundation.prototype.handleMove = function (event) {
  354. if (this.isDisabled)
  355. return;
  356. // Prevent scrolling.
  357. event.preventDefault();
  358. var clientX = event.clientX != null ?
  359. event.clientX :
  360. event.targetTouches[0].clientX;
  361. var dragAlreadyStarted = this.thumb != null;
  362. this.thumb = this.getThumbFromMoveEvent(clientX);
  363. if (this.thumb === null)
  364. return;
  365. var value = this.mapClientXOnSliderScale(clientX);
  366. if (!dragAlreadyStarted) {
  367. this.handleDragStart(event, value, this.thumb);
  368. this.adapter.emitDragStartEvent(value, this.thumb);
  369. }
  370. this.updateValue(value, this.thumb, { emitInputEvent: true });
  371. };
  372. /**
  373. * Handles pointer up events on the slider root element.
  374. */
  375. MDCSliderFoundation.prototype.handleUp = function () {
  376. var _a, _b;
  377. if (this.isDisabled || this.thumb === null)
  378. return;
  379. // Remove the focused state and hide the value indicator(s) (if present)
  380. // if focus state is meant to be hidden.
  381. if ((_b = (_a = this.adapter).shouldHideFocusStylesForPointerEvents) === null || _b === void 0 ? void 0 : _b.call(_a)) {
  382. this.handleInputBlur(this.thumb);
  383. }
  384. var oldValue = this.thumb === Thumb.START ?
  385. this.valueStartBeforeDownEvent :
  386. this.valueBeforeDownEvent;
  387. var newValue = this.thumb === Thumb.START ? this.valueStart : this.value;
  388. if (oldValue !== newValue) {
  389. this.adapter.emitChangeEvent(newValue, this.thumb);
  390. }
  391. this.adapter.emitDragEndEvent(newValue, this.thumb);
  392. this.thumb = null;
  393. };
  394. /**
  395. * For range, discrete slider, shows the value indicator on both thumbs.
  396. */
  397. MDCSliderFoundation.prototype.handleThumbMouseenter = function () {
  398. if (!this.isDiscrete || !this.isRange)
  399. return;
  400. this.adapter.addThumbClass(cssClasses.THUMB_WITH_INDICATOR, Thumb.START);
  401. this.adapter.addThumbClass(cssClasses.THUMB_WITH_INDICATOR, Thumb.END);
  402. };
  403. /**
  404. * For range, discrete slider, hides the value indicator on both thumbs.
  405. */
  406. MDCSliderFoundation.prototype.handleThumbMouseleave = function () {
  407. var _a, _b;
  408. if (!this.isDiscrete || !this.isRange)
  409. return;
  410. if ((!((_b = (_a = this.adapter).shouldHideFocusStylesForPointerEvents) === null || _b === void 0 ? void 0 : _b.call(_a)) &&
  411. (this.adapter.isInputFocused(Thumb.START) ||
  412. this.adapter.isInputFocused(Thumb.END))) ||
  413. this.thumb) {
  414. // Leave value indicator shown if either input is focused or the thumb is
  415. // being dragged.
  416. return;
  417. }
  418. this.adapter.removeThumbClass(cssClasses.THUMB_WITH_INDICATOR, Thumb.START);
  419. this.adapter.removeThumbClass(cssClasses.THUMB_WITH_INDICATOR, Thumb.END);
  420. };
  421. MDCSliderFoundation.prototype.handleMousedownOrTouchstart = function (event) {
  422. var _this = this;
  423. var moveEventType = event.type === 'mousedown' ? 'mousemove' : 'touchmove';
  424. // After a down event on the slider root, listen for move events on
  425. // body (so the slider value is updated for events outside of the
  426. // slider root).
  427. this.adapter.registerBodyEventHandler(moveEventType, this.moveListener);
  428. var upHandler = function () {
  429. _this.handleUp();
  430. // Once the drag is finished (up event on body), remove the move
  431. // handler.
  432. _this.adapter.deregisterBodyEventHandler(moveEventType, _this.moveListener);
  433. // Also stop listening for subsequent up events.
  434. _this.adapter.deregisterEventHandler('mouseup', upHandler);
  435. _this.adapter.deregisterEventHandler('touchend', upHandler);
  436. };
  437. this.adapter.registerBodyEventHandler('mouseup', upHandler);
  438. this.adapter.registerBodyEventHandler('touchend', upHandler);
  439. this.handleDown(event);
  440. };
  441. MDCSliderFoundation.prototype.handlePointerdown = function (event) {
  442. var isPrimaryButton = event.button === 0;
  443. if (!isPrimaryButton)
  444. return;
  445. if (event.pointerId != null) {
  446. this.adapter.setPointerCapture(event.pointerId);
  447. }
  448. this.adapter.registerEventHandler('pointermove', this.moveListener);
  449. this.handleDown(event);
  450. };
  451. /**
  452. * Handles input `change` event by setting internal slider value to match
  453. * input's new value.
  454. */
  455. MDCSliderFoundation.prototype.handleInputChange = function (thumb) {
  456. var value = Number(this.adapter.getInputValue(thumb));
  457. if (thumb === Thumb.START) {
  458. this.setValueStart(value);
  459. }
  460. else {
  461. this.setValue(value);
  462. }
  463. this.adapter.emitChangeEvent(thumb === Thumb.START ? this.valueStart : this.value, thumb);
  464. this.adapter.emitInputEvent(thumb === Thumb.START ? this.valueStart : this.value, thumb);
  465. };
  466. /** Shows activated state and value indicator on thumb(s). */
  467. MDCSliderFoundation.prototype.handleInputFocus = function (thumb) {
  468. this.adapter.addThumbClass(cssClasses.THUMB_FOCUSED, thumb);
  469. if (!this.isDiscrete)
  470. return;
  471. this.adapter.addThumbClass(cssClasses.THUMB_WITH_INDICATOR, thumb);
  472. if (this.isRange) {
  473. var otherThumb = thumb === Thumb.START ? Thumb.END : Thumb.START;
  474. this.adapter.addThumbClass(cssClasses.THUMB_WITH_INDICATOR, otherThumb);
  475. }
  476. };
  477. /** Removes activated state and value indicator from thumb(s). */
  478. MDCSliderFoundation.prototype.handleInputBlur = function (thumb) {
  479. this.adapter.removeThumbClass(cssClasses.THUMB_FOCUSED, thumb);
  480. if (!this.isDiscrete)
  481. return;
  482. this.adapter.removeThumbClass(cssClasses.THUMB_WITH_INDICATOR, thumb);
  483. if (this.isRange) {
  484. var otherThumb = thumb === Thumb.START ? Thumb.END : Thumb.START;
  485. this.adapter.removeThumbClass(cssClasses.THUMB_WITH_INDICATOR, otherThumb);
  486. }
  487. };
  488. /**
  489. * Emits custom dragStart event, along with focusing the underlying input.
  490. */
  491. MDCSliderFoundation.prototype.handleDragStart = function (event, value, thumb) {
  492. var _a, _b;
  493. this.adapter.emitDragStartEvent(value, thumb);
  494. this.adapter.focusInput(thumb);
  495. // Restore focused state and show the value indicator(s) (if present)
  496. // in case they were previously hidden on dragEnd.
  497. // This is needed if the input is already focused, in which case
  498. // #focusInput above wouldn't actually trigger #handleInputFocus,
  499. // which is why we need to invoke it manually here.
  500. if ((_b = (_a = this.adapter).shouldHideFocusStylesForPointerEvents) === null || _b === void 0 ? void 0 : _b.call(_a)) {
  501. this.handleInputFocus(thumb);
  502. }
  503. // Prevent the input (that we just focused) from losing focus.
  504. event.preventDefault();
  505. };
  506. /**
  507. * @return The thumb to be moved based on initial down event.
  508. */
  509. MDCSliderFoundation.prototype.getThumbFromDownEvent = function (clientX, value) {
  510. // For single point slider, thumb to be moved is always the END (only)
  511. // thumb.
  512. if (!this.isRange)
  513. return Thumb.END;
  514. // Check if event press point is in the bounds of any thumb.
  515. var thumbStartRect = this.adapter.getThumbBoundingClientRect(Thumb.START);
  516. var thumbEndRect = this.adapter.getThumbBoundingClientRect(Thumb.END);
  517. var inThumbStartBounds = clientX >= thumbStartRect.left && clientX <= thumbStartRect.right;
  518. var inThumbEndBounds = clientX >= thumbEndRect.left && clientX <= thumbEndRect.right;
  519. if (inThumbStartBounds && inThumbEndBounds) {
  520. // Thumbs overlapping. Thumb to be moved cannot be determined yet.
  521. return null;
  522. }
  523. // If press is in bounds for either thumb on down event, that's the thumb
  524. // to be moved.
  525. if (inThumbStartBounds) {
  526. return Thumb.START;
  527. }
  528. if (inThumbEndBounds) {
  529. return Thumb.END;
  530. }
  531. // For presses outside the range, return whichever thumb is closer.
  532. if (value < this.valueStart) {
  533. return Thumb.START;
  534. }
  535. if (value > this.value) {
  536. return Thumb.END;
  537. }
  538. // For presses inside the range, return whichever thumb is closer.
  539. return (value - this.valueStart <= this.value - value) ? Thumb.START :
  540. Thumb.END;
  541. };
  542. /**
  543. * @return The thumb to be moved based on move event (based on drag
  544. * direction from original down event). Only applicable if thumbs
  545. * were overlapping in the down event.
  546. */
  547. MDCSliderFoundation.prototype.getThumbFromMoveEvent = function (clientX) {
  548. // Thumb has already been chosen.
  549. if (this.thumb !== null)
  550. return this.thumb;
  551. if (this.downEventClientX === null) {
  552. throw new Error('`downEventClientX` is null after move event.');
  553. }
  554. var moveDistanceUnderThreshold = Math.abs(this.downEventClientX - clientX) < numbers.THUMB_UPDATE_MIN_PX;
  555. if (moveDistanceUnderThreshold)
  556. return this.thumb;
  557. var draggedThumbToLeft = clientX < this.downEventClientX;
  558. if (draggedThumbToLeft) {
  559. return this.adapter.isRTL() ? Thumb.END : Thumb.START;
  560. }
  561. else {
  562. return this.adapter.isRTL() ? Thumb.START : Thumb.END;
  563. }
  564. };
  565. /**
  566. * Updates UI based on internal state.
  567. * @param thumb Thumb whose value is being updated. If undefined, UI is
  568. * updated for both thumbs based on current internal state.
  569. */
  570. MDCSliderFoundation.prototype.updateUI = function (thumb) {
  571. if (thumb) {
  572. this.updateThumbAndInputAttributes(thumb);
  573. }
  574. else {
  575. this.updateThumbAndInputAttributes(Thumb.START);
  576. this.updateThumbAndInputAttributes(Thumb.END);
  577. }
  578. this.updateThumbAndTrackUI(thumb);
  579. this.updateValueIndicatorUI(thumb);
  580. this.updateTickMarksUI();
  581. };
  582. /**
  583. * Updates thumb and input attributes based on current value.
  584. * @param thumb Thumb whose aria attributes to update.
  585. */
  586. MDCSliderFoundation.prototype.updateThumbAndInputAttributes = function (thumb) {
  587. if (!thumb)
  588. return;
  589. var value = this.isRange && thumb === Thumb.START ? this.valueStart : this.value;
  590. var valueStr = String(value);
  591. this.adapter.setInputAttribute(attributes.INPUT_VALUE, valueStr, thumb);
  592. if (this.isRange && thumb === Thumb.START) {
  593. this.adapter.setInputAttribute(attributes.INPUT_MIN, String(value + this.minRange), Thumb.END);
  594. }
  595. else if (this.isRange && thumb === Thumb.END) {
  596. this.adapter.setInputAttribute(attributes.INPUT_MAX, String(value - this.minRange), Thumb.START);
  597. }
  598. // Sync attribute with property.
  599. if (this.adapter.getInputValue(thumb) !== valueStr) {
  600. this.adapter.setInputValue(valueStr, thumb);
  601. }
  602. var valueToAriaValueTextFn = this.adapter.getValueToAriaValueTextFn();
  603. if (valueToAriaValueTextFn) {
  604. this.adapter.setInputAttribute(attributes.ARIA_VALUETEXT, valueToAriaValueTextFn(value, thumb), thumb);
  605. }
  606. };
  607. /**
  608. * Updates value indicator UI based on current value.
  609. * @param thumb Thumb whose value indicator to update. If undefined, all
  610. * thumbs' value indicators are updated.
  611. */
  612. MDCSliderFoundation.prototype.updateValueIndicatorUI = function (thumb) {
  613. if (!this.isDiscrete)
  614. return;
  615. var value = this.isRange && thumb === Thumb.START ? this.valueStart : this.value;
  616. this.adapter.setValueIndicatorText(value, thumb === Thumb.START ? Thumb.START : Thumb.END);
  617. if (!thumb && this.isRange) {
  618. this.adapter.setValueIndicatorText(this.valueStart, Thumb.START);
  619. }
  620. };
  621. /**
  622. * Updates tick marks UI within slider, based on current min, max, and step.
  623. */
  624. MDCSliderFoundation.prototype.updateTickMarksUI = function () {
  625. if (!this.isDiscrete || !this.hasTickMarks)
  626. return;
  627. var numTickMarksInactiveStart = (this.valueStart - this.min) / this.step;
  628. var numTickMarksActive = (this.value - this.valueStart) / this.step + 1;
  629. var numTickMarksInactiveEnd = (this.max - this.value) / this.step;
  630. var tickMarksInactiveStart = Array.from({ length: numTickMarksInactiveStart })
  631. .fill(TickMark.INACTIVE);
  632. var tickMarksActive = Array.from({ length: numTickMarksActive })
  633. .fill(TickMark.ACTIVE);
  634. var tickMarksInactiveEnd = Array.from({ length: numTickMarksInactiveEnd })
  635. .fill(TickMark.INACTIVE);
  636. this.adapter.updateTickMarks(tickMarksInactiveStart.concat(tickMarksActive)
  637. .concat(tickMarksInactiveEnd));
  638. };
  639. /** Maps clientX to a value on the slider scale. */
  640. MDCSliderFoundation.prototype.mapClientXOnSliderScale = function (clientX) {
  641. var xPos = clientX - this.rect.left;
  642. var pctComplete = xPos / this.rect.width;
  643. if (this.adapter.isRTL()) {
  644. pctComplete = 1 - pctComplete;
  645. }
  646. // Fit the percentage complete between the range [min,max]
  647. // by remapping from [0, 1] to [min, min+(max-min)].
  648. var value = this.min + pctComplete * (this.max - this.min);
  649. if (value === this.max || value === this.min) {
  650. return value;
  651. }
  652. return Number(this.quantize(value).toFixed(this.numDecimalPlaces));
  653. };
  654. /** Calculates the quantized value based on step value. */
  655. MDCSliderFoundation.prototype.quantize = function (value) {
  656. var numSteps = Math.round((value - this.min) / this.step);
  657. return this.min + numSteps * this.step;
  658. };
  659. /**
  660. * Updates slider value (internal state and UI) based on the given value.
  661. */
  662. MDCSliderFoundation.prototype.updateValue = function (value, thumb, _a) {
  663. var _b = _a === void 0 ? {} : _a, emitInputEvent = _b.emitInputEvent;
  664. value = this.clampValue(value, thumb);
  665. if (this.isRange && thumb === Thumb.START) {
  666. // Exit early if current value is the same as the new value.
  667. if (this.valueStart === value)
  668. return;
  669. this.valueStart = value;
  670. }
  671. else {
  672. // Exit early if current value is the same as the new value.
  673. if (this.value === value)
  674. return;
  675. this.value = value;
  676. }
  677. this.updateUI(thumb);
  678. if (emitInputEvent) {
  679. this.adapter.emitInputEvent(thumb === Thumb.START ? this.valueStart : this.value, thumb);
  680. }
  681. };
  682. /**
  683. * Clamps the given value for the given thumb based on slider properties:
  684. * - Restricts value within [min, max].
  685. * - If range slider, clamp start value <= end value - min range, and
  686. * end value >= start value + min range.
  687. */
  688. MDCSliderFoundation.prototype.clampValue = function (value, thumb) {
  689. // Clamp value to [min, max] range.
  690. value = Math.min(Math.max(value, this.min), this.max);
  691. var thumbStartMovedPastThumbEnd = this.isRange && thumb === Thumb.START &&
  692. value > this.value - this.minRange;
  693. if (thumbStartMovedPastThumbEnd) {
  694. return this.value - this.minRange;
  695. }
  696. var thumbEndMovedPastThumbStart = this.isRange && thumb === Thumb.END &&
  697. value < this.valueStart + this.minRange;
  698. if (thumbEndMovedPastThumbStart) {
  699. return this.valueStart + this.minRange;
  700. }
  701. return value;
  702. };
  703. /**
  704. * Updates the active track and thumb style properties to reflect current
  705. * value.
  706. */
  707. MDCSliderFoundation.prototype.updateThumbAndTrackUI = function (thumb) {
  708. var _this = this;
  709. var _a = this, max = _a.max, min = _a.min;
  710. var pctComplete = (this.value - this.valueStart) / (max - min);
  711. var rangePx = pctComplete * this.rect.width;
  712. var isRtl = this.adapter.isRTL();
  713. var transformProp = HAS_WINDOW ? getCorrectPropertyName(window, 'transform') : 'transform';
  714. if (this.isRange) {
  715. var thumbLeftPos_1 = this.adapter.isRTL() ?
  716. (max - this.value) / (max - min) * this.rect.width :
  717. (this.valueStart - min) / (max - min) * this.rect.width;
  718. var thumbRightPos_1 = thumbLeftPos_1 + rangePx;
  719. this.animFrame.request(AnimationKeys.SLIDER_UPDATE, function () {
  720. // Set active track styles, accounting for animation direction by
  721. // setting `transform-origin`.
  722. var trackAnimatesFromRight = (!isRtl && thumb === Thumb.START) ||
  723. (isRtl && thumb !== Thumb.START);
  724. if (trackAnimatesFromRight) {
  725. _this.adapter.setTrackActiveStyleProperty('transform-origin', 'right');
  726. _this.adapter.setTrackActiveStyleProperty('left', 'auto');
  727. _this.adapter.setTrackActiveStyleProperty('right', _this.rect.width - thumbRightPos_1 + "px");
  728. }
  729. else {
  730. _this.adapter.setTrackActiveStyleProperty('transform-origin', 'left');
  731. _this.adapter.setTrackActiveStyleProperty('right', 'auto');
  732. _this.adapter.setTrackActiveStyleProperty('left', thumbLeftPos_1 + "px");
  733. }
  734. _this.adapter.setTrackActiveStyleProperty(transformProp, "scaleX(" + pctComplete + ")");
  735. // Set thumb styles.
  736. var thumbStartPos = isRtl ? thumbRightPos_1 : thumbLeftPos_1;
  737. var thumbEndPos = _this.adapter.isRTL() ? thumbLeftPos_1 : thumbRightPos_1;
  738. if (thumb === Thumb.START || !thumb || !_this.initialStylesRemoved) {
  739. _this.adapter.setThumbStyleProperty(transformProp, "translateX(" + thumbStartPos + "px)", Thumb.START);
  740. _this.alignValueIndicator(Thumb.START, thumbStartPos);
  741. }
  742. if (thumb === Thumb.END || !thumb || !_this.initialStylesRemoved) {
  743. _this.adapter.setThumbStyleProperty(transformProp, "translateX(" + thumbEndPos + "px)", Thumb.END);
  744. _this.alignValueIndicator(Thumb.END, thumbEndPos);
  745. }
  746. _this.removeInitialStyles(isRtl);
  747. _this.updateOverlappingThumbsUI(thumbStartPos, thumbEndPos, thumb);
  748. });
  749. }
  750. else {
  751. this.animFrame.request(AnimationKeys.SLIDER_UPDATE, function () {
  752. var thumbStartPos = isRtl ? _this.rect.width - rangePx : rangePx;
  753. _this.adapter.setThumbStyleProperty(transformProp, "translateX(" + thumbStartPos + "px)", Thumb.END);
  754. _this.alignValueIndicator(Thumb.END, thumbStartPos);
  755. _this.adapter.setTrackActiveStyleProperty(transformProp, "scaleX(" + pctComplete + ")");
  756. _this.removeInitialStyles(isRtl);
  757. });
  758. }
  759. };
  760. /**
  761. * Shifts the value indicator to either side if it would otherwise stick
  762. * beyond the slider's length while keeping the caret above the knob.
  763. */
  764. MDCSliderFoundation.prototype.alignValueIndicator = function (thumb, thumbPos) {
  765. if (!this.isDiscrete)
  766. return;
  767. var thumbHalfWidth = this.adapter.getThumbBoundingClientRect(thumb).width / 2;
  768. var containerWidth = this.adapter.getValueIndicatorContainerWidth(thumb);
  769. var sliderWidth = this.adapter.getBoundingClientRect().width;
  770. if (containerWidth / 2 > thumbPos + thumbHalfWidth) {
  771. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_LEFT, thumbHalfWidth + "px", thumb);
  772. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_RIGHT, 'auto', thumb);
  773. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_TRANSFORM, 'translateX(-50%)', thumb);
  774. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_LEFT, '0', thumb);
  775. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_RIGHT, 'auto', thumb);
  776. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_TRANSFORM, 'none', thumb);
  777. }
  778. else if (containerWidth / 2 > sliderWidth - thumbPos + thumbHalfWidth) {
  779. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_LEFT, 'auto', thumb);
  780. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_RIGHT, thumbHalfWidth + "px", thumb);
  781. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_TRANSFORM, 'translateX(50%)', thumb);
  782. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_LEFT, 'auto', thumb);
  783. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_RIGHT, '0', thumb);
  784. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_TRANSFORM, 'none', thumb);
  785. }
  786. else {
  787. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_LEFT, '50%', thumb);
  788. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_RIGHT, 'auto', thumb);
  789. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CARET_TRANSFORM, 'translateX(-50%)', thumb);
  790. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_LEFT, '50%', thumb);
  791. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_RIGHT, 'auto', thumb);
  792. this.adapter.setThumbStyleProperty(strings.VAR_VALUE_INDICATOR_CONTAINER_TRANSFORM, 'translateX(-50%)', thumb);
  793. }
  794. };
  795. /**
  796. * Removes initial inline styles if not already removed. `left:<...>%`
  797. * inline styles can be added to position the thumb correctly before JS
  798. * initialization. However, they need to be removed before the JS starts
  799. * positioning the thumb. This is because the JS uses
  800. * `transform:translateX(<...>)px` (for performance reasons) to position
  801. * the thumb (which is not possible for initial styles since we need the
  802. * bounding rect measurements).
  803. */
  804. MDCSliderFoundation.prototype.removeInitialStyles = function (isRtl) {
  805. if (this.initialStylesRemoved)
  806. return;
  807. // Remove thumb position properties that were added for initial render.
  808. var position = isRtl ? 'right' : 'left';
  809. this.adapter.removeThumbStyleProperty(position, Thumb.END);
  810. if (this.isRange) {
  811. this.adapter.removeThumbStyleProperty(position, Thumb.START);
  812. }
  813. this.initialStylesRemoved = true;
  814. this.resetTrackAndThumbAnimation();
  815. };
  816. /**
  817. * Resets track/thumb animation to prevent animation when adding
  818. * `transform` styles to thumb initially.
  819. */
  820. MDCSliderFoundation.prototype.resetTrackAndThumbAnimation = function () {
  821. var _this = this;
  822. if (!this.isDiscrete)
  823. return;
  824. // Set transition properties to default (no animation), so that the
  825. // newly added `transform` styles do not animate thumb/track from
  826. // their default positions.
  827. var transitionProp = HAS_WINDOW ?
  828. getCorrectPropertyName(window, 'transition') :
  829. 'transition';
  830. var transitionDefault = 'none 0s ease 0s';
  831. this.adapter.setThumbStyleProperty(transitionProp, transitionDefault, Thumb.END);
  832. if (this.isRange) {
  833. this.adapter.setThumbStyleProperty(transitionProp, transitionDefault, Thumb.START);
  834. }
  835. this.adapter.setTrackActiveStyleProperty(transitionProp, transitionDefault);
  836. // In the next frame, remove the transition inline styles we just
  837. // added, such that any animations added in the CSS can now take effect.
  838. requestAnimationFrame(function () {
  839. _this.adapter.removeThumbStyleProperty(transitionProp, Thumb.END);
  840. _this.adapter.removeTrackActiveStyleProperty(transitionProp);
  841. if (_this.isRange) {
  842. _this.adapter.removeThumbStyleProperty(transitionProp, Thumb.START);
  843. }
  844. });
  845. };
  846. /**
  847. * Adds THUMB_TOP class to active thumb if thumb knobs overlap; otherwise
  848. * removes THUMB_TOP class from both thumbs.
  849. * @param thumb Thumb that is active (being moved).
  850. */
  851. MDCSliderFoundation.prototype.updateOverlappingThumbsUI = function (thumbStartPos, thumbEndPos, thumb) {
  852. var thumbsOverlap = false;
  853. if (this.adapter.isRTL()) {
  854. var startThumbLeftEdge = thumbStartPos - this.startThumbKnobWidth / 2;
  855. var endThumbRightEdge = thumbEndPos + this.endThumbKnobWidth / 2;
  856. thumbsOverlap = endThumbRightEdge >= startThumbLeftEdge;
  857. }
  858. else {
  859. var startThumbRightEdge = thumbStartPos + this.startThumbKnobWidth / 2;
  860. var endThumbLeftEdge = thumbEndPos - this.endThumbKnobWidth / 2;
  861. thumbsOverlap = startThumbRightEdge >= endThumbLeftEdge;
  862. }
  863. if (thumbsOverlap) {
  864. this.adapter.addThumbClass(cssClasses.THUMB_TOP,
  865. // If no thumb was dragged (in the case of initial layout), end
  866. // thumb is on top by default.
  867. thumb || Thumb.END);
  868. this.adapter.removeThumbClass(cssClasses.THUMB_TOP, thumb === Thumb.START ? Thumb.END : Thumb.START);
  869. }
  870. else {
  871. this.adapter.removeThumbClass(cssClasses.THUMB_TOP, Thumb.START);
  872. this.adapter.removeThumbClass(cssClasses.THUMB_TOP, Thumb.END);
  873. }
  874. };
  875. /**
  876. * Converts attribute value to a number, e.g. '100' => 100. Throws errors
  877. * for invalid values.
  878. * @param attributeValue Attribute value, e.g. 100.
  879. * @param attributeName Attribute name, e.g. `aria-valuemax`.
  880. */
  881. MDCSliderFoundation.prototype.convertAttributeValueToNumber = function (attributeValue, attributeName) {
  882. if (attributeValue === null) {
  883. throw new Error('MDCSliderFoundation: `' + attributeName + '` must be non-null.');
  884. }
  885. var value = Number(attributeValue);
  886. if (isNaN(value)) {
  887. throw new Error('MDCSliderFoundation: `' + attributeName + '` value is `' +
  888. attributeValue + '`, but must be a number.');
  889. }
  890. return value;
  891. };
  892. /** Checks that the given properties are valid slider values. */
  893. MDCSliderFoundation.prototype.validateProperties = function (_a) {
  894. var min = _a.min, max = _a.max, value = _a.value, valueStart = _a.valueStart, step = _a.step, minRange = _a.minRange;
  895. if (min >= max) {
  896. throw new Error("MDCSliderFoundation: min must be strictly less than max. " +
  897. ("Current: [min: " + min + ", max: " + max + "]"));
  898. }
  899. if (step <= 0) {
  900. throw new Error("MDCSliderFoundation: step must be a positive number. " +
  901. ("Current step: " + step));
  902. }
  903. if (this.isRange) {
  904. if (value < min || value > max || valueStart < min || valueStart > max) {
  905. throw new Error("MDCSliderFoundation: values must be in [min, max] range. " +
  906. ("Current values: [start value: " + valueStart + ", end value: ") +
  907. (value + ", min: " + min + ", max: " + max + "]"));
  908. }
  909. if (valueStart > value) {
  910. throw new Error("MDCSliderFoundation: start value must be <= end value. " +
  911. ("Current values: [start value: " + valueStart + ", end value: " + value + "]"));
  912. }
  913. if (minRange < 0) {
  914. throw new Error("MDCSliderFoundation: minimum range must be non-negative. " +
  915. ("Current min range: " + minRange));
  916. }
  917. if (value - valueStart < minRange) {
  918. throw new Error("MDCSliderFoundation: start value and end value must differ by at least " +
  919. (minRange + ". Current values: [start value: " + valueStart + ", ") +
  920. ("end value: " + value + "]"));
  921. }
  922. var numStepsValueStartFromMin = (valueStart - min) / step;
  923. var numStepsValueFromMin = (value - min) / step;
  924. if (!Number.isInteger(parseFloat(numStepsValueStartFromMin.toFixed(6))) ||
  925. !Number.isInteger(parseFloat(numStepsValueFromMin.toFixed(6)))) {
  926. throw new Error("MDCSliderFoundation: Slider values must be valid based on the " +
  927. ("step value (" + step + "). Current values: [start value: ") +
  928. (valueStart + ", end value: " + value + ", min: " + min + "]"));
  929. }
  930. }
  931. else { // Single point slider.
  932. if (value < min || value > max) {
  933. throw new Error("MDCSliderFoundation: value must be in [min, max] range. " +
  934. ("Current values: [value: " + value + ", min: " + min + ", max: " + max + "]"));
  935. }
  936. var numStepsValueFromMin = (value - min) / step;
  937. if (!Number.isInteger(parseFloat(numStepsValueFromMin.toFixed(6)))) {
  938. throw new Error("MDCSliderFoundation: Slider value must be valid based on the " +
  939. ("step value (" + step + "). Current value: " + value));
  940. }
  941. }
  942. };
  943. MDCSliderFoundation.prototype.registerEventHandlers = function () {
  944. this.adapter.registerWindowEventHandler('resize', this.resizeListener);
  945. if (MDCSliderFoundation.SUPPORTS_POINTER_EVENTS) {
  946. // If supported, use pointer events API with #setPointerCapture.
  947. this.adapter.registerEventHandler('pointerdown', this.pointerdownListener);
  948. this.adapter.registerEventHandler('pointerup', this.pointerupListener);
  949. }
  950. else {
  951. // Otherwise, fall back to mousedown/touchstart events.
  952. this.adapter.registerEventHandler('mousedown', this.mousedownOrTouchstartListener);
  953. this.adapter.registerEventHandler('touchstart', this.mousedownOrTouchstartListener);
  954. }
  955. if (this.isRange) {
  956. this.adapter.registerThumbEventHandler(Thumb.START, 'mouseenter', this.thumbMouseenterListener);
  957. this.adapter.registerThumbEventHandler(Thumb.START, 'mouseleave', this.thumbMouseleaveListener);
  958. this.adapter.registerInputEventHandler(Thumb.START, 'change', this.inputStartChangeListener);
  959. this.adapter.registerInputEventHandler(Thumb.START, 'focus', this.inputStartFocusListener);
  960. this.adapter.registerInputEventHandler(Thumb.START, 'blur', this.inputStartBlurListener);
  961. }
  962. this.adapter.registerThumbEventHandler(Thumb.END, 'mouseenter', this.thumbMouseenterListener);
  963. this.adapter.registerThumbEventHandler(Thumb.END, 'mouseleave', this.thumbMouseleaveListener);
  964. this.adapter.registerInputEventHandler(Thumb.END, 'change', this.inputEndChangeListener);
  965. this.adapter.registerInputEventHandler(Thumb.END, 'focus', this.inputEndFocusListener);
  966. this.adapter.registerInputEventHandler(Thumb.END, 'blur', this.inputEndBlurListener);
  967. };
  968. MDCSliderFoundation.prototype.deregisterEventHandlers = function () {
  969. this.adapter.deregisterWindowEventHandler('resize', this.resizeListener);
  970. if (MDCSliderFoundation.SUPPORTS_POINTER_EVENTS) {
  971. this.adapter.deregisterEventHandler('pointerdown', this.pointerdownListener);
  972. this.adapter.deregisterEventHandler('pointerup', this.pointerupListener);
  973. }
  974. else {
  975. this.adapter.deregisterEventHandler('mousedown', this.mousedownOrTouchstartListener);
  976. this.adapter.deregisterEventHandler('touchstart', this.mousedownOrTouchstartListener);
  977. }
  978. if (this.isRange) {
  979. this.adapter.deregisterThumbEventHandler(Thumb.START, 'mouseenter', this.thumbMouseenterListener);
  980. this.adapter.deregisterThumbEventHandler(Thumb.START, 'mouseleave', this.thumbMouseleaveListener);
  981. this.adapter.deregisterInputEventHandler(Thumb.START, 'change', this.inputStartChangeListener);
  982. this.adapter.deregisterInputEventHandler(Thumb.START, 'focus', this.inputStartFocusListener);
  983. this.adapter.deregisterInputEventHandler(Thumb.START, 'blur', this.inputStartBlurListener);
  984. }
  985. this.adapter.deregisterThumbEventHandler(Thumb.END, 'mouseenter', this.thumbMouseenterListener);
  986. this.adapter.deregisterThumbEventHandler(Thumb.END, 'mouseleave', this.thumbMouseleaveListener);
  987. this.adapter.deregisterInputEventHandler(Thumb.END, 'change', this.inputEndChangeListener);
  988. this.adapter.deregisterInputEventHandler(Thumb.END, 'focus', this.inputEndFocusListener);
  989. this.adapter.deregisterInputEventHandler(Thumb.END, 'blur', this.inputEndBlurListener);
  990. };
  991. MDCSliderFoundation.prototype.handlePointerup = function () {
  992. this.handleUp();
  993. this.adapter.deregisterEventHandler('pointermove', this.moveListener);
  994. };
  995. MDCSliderFoundation.SUPPORTS_POINTER_EVENTS = HAS_WINDOW && Boolean(window.PointerEvent) &&
  996. // #setPointerCapture is buggy on iOS, so we can't use pointer events
  997. // until the following bug is fixed:
  998. // https://bugs.webkit.org/show_bug.cgi?id=220196
  999. !isIOS();
  1000. return MDCSliderFoundation;
  1001. }(MDCFoundation));
  1002. export { MDCSliderFoundation };
  1003. function isIOS() {
  1004. // Source:
  1005. // https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
  1006. return [
  1007. 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone',
  1008. 'iPod'
  1009. ].includes(navigator.platform)
  1010. // iPad on iOS 13 detection
  1011. || (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
  1012. }
  1013. /**
  1014. * Given a number, returns the number of digits that appear after the
  1015. * decimal point.
  1016. * See
  1017. * https://stackoverflow.com/questions/9539513/is-there-a-reliable-way-in-javascript-to-obtain-the-number-of-decimal-places-of
  1018. */
  1019. function getNumDecimalPlaces(n) {
  1020. // Pull out the fraction and the exponent.
  1021. var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(String(n));
  1022. // NaN or Infinity or integer.
  1023. // We arbitrarily decide that Infinity is integral.
  1024. if (!match)
  1025. return 0;
  1026. var fraction = match[1] || ''; // E.g. 1.234e-2 => 234
  1027. var exponent = match[2] || 0; // E.g. 1.234e-2 => -2
  1028. // Count the number of digits in the fraction and subtract the
  1029. // exponent to simulate moving the decimal point left by exponent places.
  1030. // 1.234e+2 has 1 fraction digit and '234'.length - 2 == 1
  1031. // 1.234e-2 has 5 fraction digit and '234'.length - -2 == 5
  1032. return Math.max(0, // lower limit
  1033. (fraction === '0' ? 0 : fraction.length) - Number(exponent));
  1034. }
  1035. //# sourceMappingURL=foundation.js.map