ionic-angular
Version:
A powerful framework for building mobile and progressive web apps with JavaScript and Angular
542 lines • 19.5 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { ChangeDetectorRef, Component, ElementRef, Input, Optional, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { clamp, isTrueProperty } from '../../util/util';
import { Config } from '../../config/config';
import { DomController } from '../../platform/dom-controller';
import { Form } from '../../util/form';
import { Haptic } from '../../tap-click/haptic';
import { BaseInput } from '../../util/base-input';
import { Item } from '../item/item';
import { Platform } from '../../platform/platform';
import { pointerCoord } from '../../util/dom';
import { UIEventManager } from '../../gestures/ui-event-manager';
/**
* @name Range
* @description
* The Range slider lets users select from a range of values by moving
* the slider knob. It can accept dual knobs, but by default one knob
* controls the value of the range.
*
* ### Range Labels
* Labels can be placed on either side of the range by adding the
* `range-left` or `range-right` property to the element. The element
* doesn't have to be an `ion-label`, it can be added to any element
* to place it to the left or right of the range. See [usage](#usage)
* below for examples.
*
*
* ### Minimum and Maximum Values
* Minimum and maximum values can be passed to the range through the `min`
* and `max` properties, respectively. By default, the range sets the `min`
* to `0` and the `max` to `100`.
*
*
* ### Steps and Snaps
* The `step` property specifies the value granularity of the range's value.
* It can be useful to set the `step` when the value isn't in increments of `1`.
* Setting the `step` property will show tick marks on the range for each step.
* The `snaps` property can be set to automatically move the knob to the nearest
* tick mark based on the step property value.
*
*
* ### Dual Knobs
* Setting the `dualKnobs` property to `true` on the range component will
* enable two knobs on the range. If the range has two knobs, the value will
* be an object containing two properties: `lower` and `upper`.
*
*
* @usage
* ```html
* <ion-list>
* <ion-item>
* <ion-range [(ngModel)]="singleValue" color="danger" pin="true"></ion-range>
* </ion-item>
*
* <ion-item>
* <ion-range min="-200" max="200" [(ngModel)]="saturation" color="secondary">
* <ion-label range-left>-200</ion-label>
* <ion-label range-right>200</ion-label>
* </ion-range>
* </ion-item>
*
* <ion-item>
* <ion-range min="20" max="80" step="2" [(ngModel)]="brightness">
* <ion-icon small range-left name="sunny"></ion-icon>
* <ion-icon range-right name="sunny"></ion-icon>
* </ion-range>
* </ion-item>
*
* <ion-item>
* <ion-label>step=100, snaps, {{singleValue4}}</ion-label>
* <ion-range min="1000" max="2000" step="100" snaps="true" color="secondary" [(ngModel)]="singleValue4"></ion-range>
* </ion-item>
*
* <ion-item>
* <ion-label>dual, step=3, snaps, {{dualValue2 | json}}</ion-label>
* <ion-range dualKnobs="true" [(ngModel)]="dualValue2" min="21" max="72" step="3" snaps="true"></ion-range>
* </ion-item>
* </ion-list>
* ```
*
*
* @demo /docs/demos/src/range/
*/
var Range = (function (_super) {
__extends(Range, _super);
function Range(form, _haptic, item, config, _plt, elementRef, renderer, _dom, _cd) {
var _this = _super.call(this, config, elementRef, renderer, 'range', 0, form, item, null) || this;
_this._haptic = _haptic;
_this._plt = _plt;
_this._dom = _dom;
_this._cd = _cd;
_this._min = 0;
_this._max = 100;
_this._step = 1;
_this._valA = 0;
_this._valB = 0;
_this._ratioA = 0;
_this._ratioB = 0;
_this._events = new UIEventManager(_plt);
return _this;
}
Object.defineProperty(Range.prototype, "min", {
/**
* @input {number} Minimum integer value of the range. Defaults to `0`.
*/
get: function () {
return this._min;
},
set: function (val) {
val = Math.round(val);
if (!isNaN(val)) {
this._min = val;
this._inputUpdated();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "max", {
/**
* @input {number} Maximum integer value of the range. Defaults to `100`.
*/
get: function () {
return this._max;
},
set: function (val) {
val = Math.round(val);
if (!isNaN(val)) {
this._max = val;
this._inputUpdated();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "step", {
/**
* @input {number} Specifies the value granularity. Defaults to `1`.
*/
get: function () {
return this._step;
},
set: function (val) {
val = Math.round(val);
if (!isNaN(val) && val > 0) {
this._step = val;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "snaps", {
/**
* @input {boolean} If true, the knob snaps to tick marks evenly spaced based
* on the step property value. Defaults to `false`.
*/
get: function () {
return this._snaps;
},
set: function (val) {
this._snaps = isTrueProperty(val);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "pin", {
/**
* @input {boolean} If true, a pin with integer value is shown when the knob
* is pressed. Defaults to `false`.
*/
get: function () {
return this._pin;
},
set: function (val) {
this._pin = isTrueProperty(val);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "debounce", {
/**
* @input {number} How long, in milliseconds, to wait to trigger the
* `ionChange` event after each change in the range value. Default `0`.
*/
get: function () {
return this._debouncer.wait;
},
set: function (val) {
this._debouncer.wait = val;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "dualKnobs", {
/**
* @input {boolean} Show two knobs. Defaults to `false`.
*/
get: function () {
return this._dual;
},
set: function (val) {
this._dual = isTrueProperty(val);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "ratio", {
/**
* Returns the ratio of the knob's is current location, which is a number
* between `0` and `1`. If two knobs are used, this property represents
* the lower value.
*/
get: function () {
if (this._dual) {
return Math.min(this._ratioA, this._ratioB);
}
return this._ratioA;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Range.prototype, "ratioUpper", {
/**
* Returns the ratio of the upper value's is current location, which is
* a number between `0` and `1`. If there is only one knob, then this
* will return `null`.
*/
get: function () {
if (this._dual) {
return Math.max(this._ratioA, this._ratioB);
}
return null;
},
enumerable: true,
configurable: true
});
/**
* @hidden
*/
Range.prototype.ngAfterContentInit = function () {
this._initialize();
// add touchstart/mousedown listeners
this._events.pointerEvents({
element: this._slider.nativeElement,
pointerDown: this._pointerDown.bind(this),
pointerMove: this._pointerMove.bind(this),
pointerUp: this._pointerUp.bind(this),
zone: true
});
// build all the ticks if there are any to show
this._createTicks();
};
/** @internal */
Range.prototype._pointerDown = function (ev) {
// TODO: we could stop listening for events instead of checking this._disabled.
// since there are a lot of events involved, this solution is
// enough for the moment
if (this._disabled) {
return false;
}
// trigger ionFocus event
this._fireFocus();
// prevent default so scrolling does not happen
ev.preventDefault();
ev.stopPropagation();
// get the start coordinates
var current = pointerCoord(ev);
// get the full dimensions of the slider element
var rect = this._rect = this._plt.getElementBoundingClientRect(this._slider.nativeElement);
// figure out which knob they started closer to
var ratio = clamp(0, (current.x - rect.left) / (rect.width), 1);
this._activeB = this._dual && (Math.abs(ratio - this._ratioA) > Math.abs(ratio - this._ratioB));
// update the active knob's position
this._update(current, rect, true);
// trigger a haptic start
this._haptic.gestureSelectionStart();
// return true so the pointer events
// know everything's still valid
return true;
};
/** @internal */
Range.prototype._pointerMove = function (ev) {
if (this._disabled) {
return;
}
// prevent default so scrolling does not happen
ev.preventDefault();
ev.stopPropagation();
// update the active knob's position
var hasChanged = this._update(pointerCoord(ev), this._rect, true);
if (hasChanged && this._snaps) {
// trigger a haptic selection changed event
// if this is a snap range
this._haptic.gestureSelectionChanged();
}
};
/** @internal */
Range.prototype._pointerUp = function (ev) {
if (this._disabled) {
return;
}
// prevent default so scrolling does not happen
ev.preventDefault();
ev.stopPropagation();
// update the active knob's position
this._update(pointerCoord(ev), this._rect, false);
// trigger a haptic end
this._haptic.gestureSelectionEnd();
// trigger ionBlur event
this._fireBlur();
};
/** @internal */
Range.prototype._update = function (current, rect, isPressed) {
// figure out where the pointer is currently at
// update the knob being interacted with
var ratio = clamp(0, (current.x - rect.left) / (rect.width), 1);
var val = this._ratioToValue(ratio);
if (this._snaps) {
// snaps the ratio to the current value
ratio = this._valueToRatio(val);
}
// update which knob is pressed
this._pressed = isPressed;
var valChanged = false;
if (this._activeB) {
// when the pointer down started it was determined
// that knob B was the one they were interacting with
this._pressedB = isPressed;
this._pressedA = false;
this._ratioB = ratio;
valChanged = val === this._valB;
this._valB = val;
}
else {
// interacting with knob A
this._pressedA = isPressed;
this._pressedB = false;
this._ratioA = ratio;
valChanged = val === this._valA;
this._valA = val;
}
this._updateBar();
if (valChanged) {
return false;
}
// value has been updated
var value;
if (this._dual) {
// dual knobs have an lower and upper value
value = {
lower: Math.min(this._valA, this._valB),
upper: Math.max(this._valA, this._valB)
};
(void 0) /* console.debug */;
}
else {
// single knob only has one value
value = this._valA;
(void 0) /* console.debug */;
}
// Update input value
this.value = value;
return true;
};
/** @internal */
Range.prototype._updateBar = function () {
var ratioA = this._ratioA;
var ratioB = this._ratioB;
if (this._dual) {
this._barL = (Math.min(ratioA, ratioB) * 100) + "%";
this._barR = 100 - (Math.max(ratioA, ratioB) * 100) + "%";
}
else {
this._barL = '';
this._barR = 100 - (ratioA * 100) + "%";
}
this._updateTicks();
};
/** @internal */
Range.prototype._createTicks = function () {
var _this = this;
if (this._snaps) {
this._dom.write(function () {
// TODO: Fix to not use RAF
_this._ticks = [];
for (var value = _this._min; value <= _this._max; value += _this._step) {
var ratio = _this._valueToRatio(value);
_this._ticks.push({
ratio: ratio,
left: ratio * 100 + "%",
});
}
_this._updateTicks();
});
}
};
/** @internal */
Range.prototype._updateTicks = function () {
var ticks = this._ticks;
var ratio = this.ratio;
if (this._snaps && ticks) {
if (this._dual) {
var upperRatio = this.ratioUpper;
ticks.forEach(function (t) {
t.active = (t.ratio >= ratio && t.ratio <= upperRatio);
});
}
else {
ticks.forEach(function (t) {
t.active = (t.ratio <= ratio);
});
}
}
};
/** @hidden */
Range.prototype._keyChg = function (isIncrease, isKnobB) {
var step = this._step;
if (isKnobB) {
if (isIncrease) {
this._valB += step;
}
else {
this._valB -= step;
}
this._valB = clamp(this._min, this._valB, this._max);
this._ratioB = this._valueToRatio(this._valB);
}
else {
if (isIncrease) {
this._valA += step;
}
else {
this._valA -= step;
}
this._valA = clamp(this._min, this._valA, this._max);
this._ratioA = this._valueToRatio(this._valA);
}
this._updateBar();
};
/** @internal */
Range.prototype._ratioToValue = function (ratio) {
ratio = Math.round(((this._max - this._min) * ratio));
ratio = Math.round(ratio / this._step) * this._step + this._min;
return clamp(this._min, ratio, this._max);
};
/** @internal */
Range.prototype._valueToRatio = function (value) {
value = Math.round((value - this._min) / this._step) * this._step;
value = value / (this._max - this._min);
return clamp(0, value, 1);
};
Range.prototype._inputNormalize = function (val) {
if (this._dual) {
return val;
}
else {
val = parseFloat(val);
return isNaN(val) ? undefined : val;
}
};
/**
* @hidden
*/
Range.prototype._inputUpdated = function () {
var val = this.value;
if (this._dual) {
this._valA = val.lower;
this._valB = val.upper;
this._ratioA = this._valueToRatio(val.lower);
this._ratioB = this._valueToRatio(val.upper);
}
else {
this._valA = val;
this._ratioA = this._valueToRatio(val);
}
this._updateBar();
this._cd.detectChanges();
};
/**
* @hidden
*/
Range.prototype.ngOnDestroy = function () {
_super.prototype.ngOnDestroy.call(this);
this._events.destroy();
};
Range.decorators = [
{ type: Component, args: [{
selector: 'ion-range',
template: '<ng-content select="[range-left]"></ng-content>' +
'<div class="range-slider" #slider>' +
'<div class="range-tick" *ngFor="let t of _ticks" [style.left]="t.left" [class.range-tick-active]="t.active" role="presentation"></div>' +
'<div class="range-bar" role="presentation"></div>' +
'<div class="range-bar range-bar-active" [style.left]="_barL" [style.right]="_barR" #bar role="presentation"></div>' +
'<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>' +
'<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>' +
'</div>' +
'<ng-content select="[range-right]"></ng-content>',
host: {
'[class.range-disabled]': '_disabled',
'[class.range-pressed]': '_pressed',
'[class.range-has-pin]': '_pin'
},
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: Range, multi: true }],
encapsulation: ViewEncapsulation.None,
},] },
];
/** @nocollapse */
Range.ctorParameters = function () { return [
{ type: Form, },
{ type: Haptic, },
{ type: Item, decorators: [{ type: Optional },] },
{ type: Config, },
{ type: Platform, },
{ type: ElementRef, },
{ type: Renderer, },
{ type: DomController, },
{ type: ChangeDetectorRef, },
]; };
Range.propDecorators = {
'_slider': [{ type: ViewChild, args: ['slider',] },],
'min': [{ type: Input },],
'max': [{ type: Input },],
'step': [{ type: Input },],
'snaps': [{ type: Input },],
'pin': [{ type: Input },],
'debounce': [{ type: Input },],
'dualKnobs': [{ type: Input },],
};
return Range;
}(BaseInput));
export { Range };
//# sourceMappingURL=range.js.map