123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. var __extends = (this && this.__extends) || (function () {
  2. var extendStatics = Object.setPrototypeOf ||
  3. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  4. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  5. return function (d, b) {
  6. extendStatics(d, b);
  7. function __() { this.constructor = d; }
  8. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  9. };
  10. })();
  11. import { ChangeDetectorRef, Component, ElementRef, Input, Optional, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
  12. import { NG_VALUE_ACCESSOR } from '@angular/forms';
  13. import { clamp, isTrueProperty } from '../../util/util';
  14. import { Config } from '../../config/config';
  15. import { DomController } from '../../platform/dom-controller';
  16. import { Form } from '../../util/form';
  17. import { Haptic } from '../../tap-click/haptic';
  18. import { BaseInput } from '../../util/base-input';
  19. import { Item } from '../item/item';
  20. import { Platform } from '../../platform/platform';
  21. import { pointerCoord } from '../../util/dom';
  22. import { UIEventManager } from '../../gestures/ui-event-manager';
  23. /**
  24. * @name Range
  25. * @description
  26. * The Range slider lets users select from a range of values by moving
  27. * the slider knob. It can accept dual knobs, but by default one knob
  28. * controls the value of the range.
  29. *
  30. * ### Range Labels
  31. * Labels can be placed on either side of the range by adding the
  32. * `range-left` or `range-right` property to the element. The element
  33. * doesn't have to be an `ion-label`, it can be added to any element
  34. * to place it to the left or right of the range. See [usage](#usage)
  35. * below for examples.
  36. *
  37. *
  38. * ### Minimum and Maximum Values
  39. * Minimum and maximum values can be passed to the range through the `min`
  40. * and `max` properties, respectively. By default, the range sets the `min`
  41. * to `0` and the `max` to `100`.
  42. *
  43. *
  44. * ### Steps and Snaps
  45. * The `step` property specifies the value granularity of the range's value.
  46. * It can be useful to set the `step` when the value isn't in increments of `1`.
  47. * Setting the `step` property will show tick marks on the range for each step.
  48. * The `snaps` property can be set to automatically move the knob to the nearest
  49. * tick mark based on the step property value.
  50. *
  51. *
  52. * ### Dual Knobs
  53. * Setting the `dualKnobs` property to `true` on the range component will
  54. * enable two knobs on the range. If the range has two knobs, the value will
  55. * be an object containing two properties: `lower` and `upper`.
  56. *
  57. *
  58. * @usage
  59. * ```html
  60. * <ion-list>
  61. * <ion-item>
  62. * <ion-range [(ngModel)]="singleValue" color="danger" pin="true"></ion-range>
  63. * </ion-item>
  64. *
  65. * <ion-item>
  66. * <ion-range min="-200" max="200" [(ngModel)]="saturation" color="secondary">
  67. * <ion-label range-left>-200</ion-label>
  68. * <ion-label range-right>200</ion-label>
  69. * </ion-range>
  70. * </ion-item>
  71. *
  72. * <ion-item>
  73. * <ion-range min="20" max="80" step="2" [(ngModel)]="brightness">
  74. * <ion-icon small range-left name="sunny"></ion-icon>
  75. * <ion-icon range-right name="sunny"></ion-icon>
  76. * </ion-range>
  77. * </ion-item>
  78. *
  79. * <ion-item>
  80. * <ion-label>step=100, snaps, {{singleValue4}}</ion-label>
  81. * <ion-range min="1000" max="2000" step="100" snaps="true" color="secondary" [(ngModel)]="singleValue4"></ion-range>
  82. * </ion-item>
  83. *
  84. * <ion-item>
  85. * <ion-label>dual, step=3, snaps, {{dualValue2 | json}}</ion-label>
  86. * <ion-range dualKnobs="true" [(ngModel)]="dualValue2" min="21" max="72" step="3" snaps="true"></ion-range>
  87. * </ion-item>
  88. * </ion-list>
  89. * ```
  90. *
  91. *
  92. * @demo /docs/demos/src/range/
  93. */
  94. var Range = (function (_super) {
  95. __extends(Range, _super);
  96. function Range(form, _haptic, item, config, _plt, elementRef, renderer, _dom, _cd) {
  97. var _this = _super.call(this, config, elementRef, renderer, 'range', 0, form, item, null) || this;
  98. _this._haptic = _haptic;
  99. _this._plt = _plt;
  100. _this._dom = _dom;
  101. _this._cd = _cd;
  102. _this._min = 0;
  103. _this._max = 100;
  104. _this._step = 1;
  105. _this._valA = 0;
  106. _this._valB = 0;
  107. _this._ratioA = 0;
  108. _this._ratioB = 0;
  109. _this._events = new UIEventManager(_plt);
  110. return _this;
  111. }
  112. Object.defineProperty(Range.prototype, "min", {
  113. /**
  114. * @input {number} Minimum integer value of the range. Defaults to `0`.
  115. */
  116. get: function () {
  117. return this._min;
  118. },
  119. set: function (val) {
  120. val = Math.round(val);
  121. if (!isNaN(val)) {
  122. this._min = val;
  123. this._inputUpdated();
  124. }
  125. },
  126. enumerable: true,
  127. configurable: true
  128. });
  129. Object.defineProperty(Range.prototype, "max", {
  130. /**
  131. * @input {number} Maximum integer value of the range. Defaults to `100`.
  132. */
  133. get: function () {
  134. return this._max;
  135. },
  136. set: function (val) {
  137. val = Math.round(val);
  138. if (!isNaN(val)) {
  139. this._max = val;
  140. this._inputUpdated();
  141. }
  142. },
  143. enumerable: true,
  144. configurable: true
  145. });
  146. Object.defineProperty(Range.prototype, "step", {
  147. /**
  148. * @input {number} Specifies the value granularity. Defaults to `1`.
  149. */
  150. get: function () {
  151. return this._step;
  152. },
  153. set: function (val) {
  154. val = Math.round(val);
  155. if (!isNaN(val) && val > 0) {
  156. this._step = val;
  157. }
  158. },
  159. enumerable: true,
  160. configurable: true
  161. });
  162. Object.defineProperty(Range.prototype, "snaps", {
  163. /**
  164. * @input {boolean} If true, the knob snaps to tick marks evenly spaced based
  165. * on the step property value. Defaults to `false`.
  166. */
  167. get: function () {
  168. return this._snaps;
  169. },
  170. set: function (val) {
  171. this._snaps = isTrueProperty(val);
  172. },
  173. enumerable: true,
  174. configurable: true
  175. });
  176. Object.defineProperty(Range.prototype, "pin", {
  177. /**
  178. * @input {boolean} If true, a pin with integer value is shown when the knob
  179. * is pressed. Defaults to `false`.
  180. */
  181. get: function () {
  182. return this._pin;
  183. },
  184. set: function (val) {
  185. this._pin = isTrueProperty(val);
  186. },
  187. enumerable: true,
  188. configurable: true
  189. });
  190. Object.defineProperty(Range.prototype, "debounce", {
  191. /**
  192. * @input {number} How long, in milliseconds, to wait to trigger the
  193. * `ionChange` event after each change in the range value. Default `0`.
  194. */
  195. get: function () {
  196. return this._debouncer.wait;
  197. },
  198. set: function (val) {
  199. this._debouncer.wait = val;
  200. },
  201. enumerable: true,
  202. configurable: true
  203. });
  204. Object.defineProperty(Range.prototype, "dualKnobs", {
  205. /**
  206. * @input {boolean} Show two knobs. Defaults to `false`.
  207. */
  208. get: function () {
  209. return this._dual;
  210. },
  211. set: function (val) {
  212. this._dual = isTrueProperty(val);
  213. },
  214. enumerable: true,
  215. configurable: true
  216. });
  217. Object.defineProperty(Range.prototype, "ratio", {
  218. /**
  219. * Returns the ratio of the knob's is current location, which is a number
  220. * between `0` and `1`. If two knobs are used, this property represents
  221. * the lower value.
  222. */
  223. get: function () {
  224. if (this._dual) {
  225. return Math.min(this._ratioA, this._ratioB);
  226. }
  227. return this._ratioA;
  228. },
  229. enumerable: true,
  230. configurable: true
  231. });
  232. Object.defineProperty(Range.prototype, "ratioUpper", {
  233. /**
  234. * Returns the ratio of the upper value's is current location, which is
  235. * a number between `0` and `1`. If there is only one knob, then this
  236. * will return `null`.
  237. */
  238. get: function () {
  239. if (this._dual) {
  240. return Math.max(this._ratioA, this._ratioB);
  241. }
  242. return null;
  243. },
  244. enumerable: true,
  245. configurable: true
  246. });
  247. /**
  248. * @hidden
  249. */
  250. Range.prototype.ngAfterContentInit = function () {
  251. this._initialize();
  252. // add touchstart/mousedown listeners
  253. this._events.pointerEvents({
  254. element: this._slider.nativeElement,
  255. pointerDown: this._pointerDown.bind(this),
  256. pointerMove: this._pointerMove.bind(this),
  257. pointerUp: this._pointerUp.bind(this),
  258. zone: true
  259. });
  260. // build all the ticks if there are any to show
  261. this._createTicks();
  262. };
  263. /** @internal */
  264. Range.prototype._pointerDown = function (ev) {
  265. // TODO: we could stop listening for events instead of checking this._disabled.
  266. // since there are a lot of events involved, this solution is
  267. // enough for the moment
  268. if (this._disabled) {
  269. return false;
  270. }
  271. // trigger ionFocus event
  272. this._fireFocus();
  273. // prevent default so scrolling does not happen
  274. ev.preventDefault();
  275. ev.stopPropagation();
  276. // get the start coordinates
  277. var current = pointerCoord(ev);
  278. // get the full dimensions of the slider element
  279. var rect = this._rect = this._plt.getElementBoundingClientRect(this._slider.nativeElement);
  280. // figure out which knob they started closer to
  281. var ratio = clamp(0, (current.x - rect.left) / (rect.width), 1);
  282. this._activeB = this._dual && (Math.abs(ratio - this._ratioA) > Math.abs(ratio - this._ratioB));
  283. // update the active knob's position
  284. this._update(current, rect, true);
  285. // trigger a haptic start
  286. this._haptic.gestureSelectionStart();
  287. // return true so the pointer events
  288. // know everything's still valid
  289. return true;
  290. };
  291. /** @internal */
  292. Range.prototype._pointerMove = function (ev) {
  293. if (this._disabled) {
  294. return;
  295. }
  296. // prevent default so scrolling does not happen
  297. ev.preventDefault();
  298. ev.stopPropagation();
  299. // update the active knob's position
  300. var hasChanged = this._update(pointerCoord(ev), this._rect, true);
  301. if (hasChanged && this._snaps) {
  302. // trigger a haptic selection changed event
  303. // if this is a snap range
  304. this._haptic.gestureSelectionChanged();
  305. }
  306. };
  307. /** @internal */
  308. Range.prototype._pointerUp = function (ev) {
  309. if (this._disabled) {
  310. return;
  311. }
  312. // prevent default so scrolling does not happen
  313. ev.preventDefault();
  314. ev.stopPropagation();
  315. // update the active knob's position
  316. this._update(pointerCoord(ev), this._rect, false);
  317. // trigger a haptic end
  318. this._haptic.gestureSelectionEnd();
  319. // trigger ionBlur event
  320. this._fireBlur();
  321. };
  322. /** @internal */
  323. Range.prototype._update = function (current, rect, isPressed) {
  324. // figure out where the pointer is currently at
  325. // update the knob being interacted with
  326. var ratio = clamp(0, (current.x - rect.left) / (rect.width), 1);
  327. var val = this._ratioToValue(ratio);
  328. if (this._snaps) {
  329. // snaps the ratio to the current value
  330. ratio = this._valueToRatio(val);
  331. }
  332. // update which knob is pressed
  333. this._pressed = isPressed;
  334. var valChanged = false;
  335. if (this._activeB) {
  336. // when the pointer down started it was determined
  337. // that knob B was the one they were interacting with
  338. this._pressedB = isPressed;
  339. this._pressedA = false;
  340. this._ratioB = ratio;
  341. valChanged = val === this._valB;
  342. this._valB = val;
  343. }
  344. else {
  345. // interacting with knob A
  346. this._pressedA = isPressed;
  347. this._pressedB = false;
  348. this._ratioA = ratio;
  349. valChanged = val === this._valA;
  350. this._valA = val;
  351. }
  352. this._updateBar();
  353. if (valChanged) {
  354. return false;
  355. }
  356. // value has been updated
  357. var value;
  358. if (this._dual) {
  359. // dual knobs have an lower and upper value
  360. value = {
  361. lower: Math.min(this._valA, this._valB),
  362. upper: Math.max(this._valA, this._valB)
  363. };
  364. (void 0) /* console.debug */;
  365. }
  366. else {
  367. // single knob only has one value
  368. value = this._valA;
  369. (void 0) /* console.debug */;
  370. }
  371. // Update input value
  372. this.value = value;
  373. return true;
  374. };
  375. /** @internal */
  376. Range.prototype._updateBar = function () {
  377. var ratioA = this._ratioA;
  378. var ratioB = this._ratioB;
  379. if (this._dual) {
  380. this._barL = (Math.min(ratioA, ratioB) * 100) + "%";
  381. this._barR = 100 - (Math.max(ratioA, ratioB) * 100) + "%";
  382. }
  383. else {
  384. this._barL = '';
  385. this._barR = 100 - (ratioA * 100) + "%";
  386. }
  387. this._updateTicks();
  388. };
  389. /** @internal */
  390. Range.prototype._createTicks = function () {
  391. var _this = this;
  392. if (this._snaps) {
  393. this._dom.write(function () {
  394. // TODO: Fix to not use RAF
  395. _this._ticks = [];
  396. for (var value = _this._min; value <= _this._max; value += _this._step) {
  397. var ratio = _this._valueToRatio(value);
  398. _this._ticks.push({
  399. ratio: ratio,
  400. left: ratio * 100 + "%",
  401. });
  402. }
  403. _this._updateTicks();
  404. });
  405. }
  406. };
  407. /** @internal */
  408. Range.prototype._updateTicks = function () {
  409. var ticks = this._ticks;
  410. var ratio = this.ratio;
  411. if (this._snaps && ticks) {
  412. if (this._dual) {
  413. var upperRatio = this.ratioUpper;
  414. ticks.forEach(function (t) {
  415. t.active = (t.ratio >= ratio && t.ratio <= upperRatio);
  416. });
  417. }
  418. else {
  419. ticks.forEach(function (t) {
  420. t.active = (t.ratio <= ratio);
  421. });
  422. }
  423. }
  424. };
  425. /** @hidden */
  426. Range.prototype._keyChg = function (isIncrease, isKnobB) {
  427. var step = this._step;
  428. if (isKnobB) {
  429. if (isIncrease) {
  430. this._valB += step;
  431. }
  432. else {
  433. this._valB -= step;
  434. }
  435. this._valB = clamp(this._min, this._valB, this._max);
  436. this._ratioB = this._valueToRatio(this._valB);
  437. }
  438. else {
  439. if (isIncrease) {
  440. this._valA += step;
  441. }
  442. else {
  443. this._valA -= step;
  444. }
  445. this._valA = clamp(this._min, this._valA, this._max);
  446. this._ratioA = this._valueToRatio(this._valA);
  447. }
  448. this._updateBar();
  449. };
  450. /** @internal */
  451. Range.prototype._ratioToValue = function (ratio) {
  452. ratio = Math.round(((this._max - this._min) * ratio));
  453. ratio = Math.round(ratio / this._step) * this._step + this._min;
  454. return clamp(this._min, ratio, this._max);
  455. };
  456. /** @internal */
  457. Range.prototype._valueToRatio = function (value) {
  458. value = Math.round((value - this._min) / this._step) * this._step;
  459. value = value / (this._max - this._min);
  460. return clamp(0, value, 1);
  461. };
  462. Range.prototype._inputNormalize = function (val) {
  463. if (this._dual) {
  464. return val;
  465. }
  466. else {
  467. val = parseFloat(val);
  468. return isNaN(val) ? undefined : val;
  469. }
  470. };
  471. /**
  472. * @hidden
  473. */
  474. Range.prototype._inputUpdated = function () {
  475. var val = this.value;
  476. if (this._dual) {
  477. this._valA = val.lower;
  478. this._valB = val.upper;
  479. this._ratioA = this._valueToRatio(val.lower);
  480. this._ratioB = this._valueToRatio(val.upper);
  481. }
  482. else {
  483. this._valA = val;
  484. this._ratioA = this._valueToRatio(val);
  485. }
  486. this._updateBar();
  487. this._cd.detectChanges();
  488. };
  489. /**
  490. * @hidden
  491. */
  492. Range.prototype.ngOnDestroy = function () {
  493. _super.prototype.ngOnDestroy.call(this);
  494. this._events.destroy();
  495. };
  496. Range.decorators = [
  497. { type: Component, args: [{
  498. selector: 'ion-range',
  499. template: '<ng-content select="[range-left]"></ng-content>' +
  500. '<div class="range-slider" #slider>' +
  501. '<div class="range-tick" *ngFor="let t of _ticks" [style.left]="t.left" [class.range-tick-active]="t.active" role="presentation"></div>' +
  502. '<div class="range-bar" role="presentation"></div>' +
  503. '<div class="range-bar range-bar-active" [style.left]="_barL" [style.right]="_barR" #bar role="presentation"></div>' +
  504. '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, false)" (ionDecrease)="_keyChg(false, false)" [ratio]="_ratioA" [val]="_valA" [pin]="_pin" [pressed]="_pressedA" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_labelId"></div>' +
  505. '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, true)" (ionDecrease)="_keyChg(false, true)" [ratio]="_ratioB" [val]="_valB" [pin]="_pin" [pressed]="_pressedB" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_labelId" *ngIf="_dual"></div>' +
  506. '</div>' +
  507. '<ng-content select="[range-right]"></ng-content>',
  508. host: {
  509. '[class.range-disabled]': '_disabled',
  510. '[class.range-pressed]': '_pressed',
  511. '[class.range-has-pin]': '_pin'
  512. },
  513. providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: Range, multi: true }],
  514. encapsulation: ViewEncapsulation.None,
  515. },] },
  516. ];
  517. /** @nocollapse */
  518. Range.ctorParameters = function () { return [
  519. { type: Form, },
  520. { type: Haptic, },
  521. { type: Item, decorators: [{ type: Optional },] },
  522. { type: Config, },
  523. { type: Platform, },
  524. { type: ElementRef, },
  525. { type: Renderer, },
  526. { type: DomController, },
  527. { type: ChangeDetectorRef, },
  528. ]; };
  529. Range.propDecorators = {
  530. '_slider': [{ type: ViewChild, args: ['slider',] },],
  531. 'min': [{ type: Input },],
  532. 'max': [{ type: Input },],
  533. 'step': [{ type: Input },],
  534. 'snaps': [{ type: Input },],
  535. 'pin': [{ type: Input },],
  536. 'debounce': [{ type: Input },],
  537. 'dualKnobs': [{ type: Input },],
  538. };
  539. return Range;
  540. }(BaseInput));
  541. export { Range };
  542. //# sourceMappingURL=range.js.map