diff --git a/src/components.ios.scss b/src/components.ios.scss
index 44fd2e067b2..9e7dac405aa 100644
--- a/src/components.ios.scss
+++ b/src/components.ios.scss
@@ -25,6 +25,7 @@
"components/picker/picker.ios",
"components/popover/popover.ios",
"components/radio/radio.ios",
+ "components/range/range.ios",
"components/searchbar/searchbar.ios",
"components/segment/segment.ios",
"components/select/select.ios",
diff --git a/src/components.md.scss b/src/components.md.scss
index f5f4a4b93a0..8541e9c6b2f 100644
--- a/src/components.md.scss
+++ b/src/components.md.scss
@@ -24,6 +24,7 @@
"components/picker/picker.md",
"components/popover/popover.md",
"components/radio/radio.md",
+ "components/range/range.md",
"components/searchbar/searchbar.md",
"components/segment/segment.md",
"components/select/select.md",
diff --git a/src/components.wp.scss b/src/components.wp.scss
index a9ad7f635ef..8bae231b469 100644
--- a/src/components.wp.scss
+++ b/src/components.wp.scss
@@ -24,6 +24,7 @@
"components/picker/picker.wp",
"components/popover/popover.wp",
"components/radio/radio.wp",
+ "components/range/range.wp",
"components/searchbar/searchbar.wp",
"components/segment/segment.wp",
"components/select/select.wp",
diff --git a/src/components/item/item.ts b/src/components/item/item.ts
index 30cb0edf0b1..28a9682fd7b 100644
--- a/src/components/item/item.ts
+++ b/src/components/item/item.ts
@@ -48,7 +48,7 @@ import {Label} from '../label/label';
'' +
'' +
'' +
- '' +
+ '' +
'' +
'' +
'' +
diff --git a/src/components/range/range.ios.scss b/src/components/range/range.ios.scss
new file mode 100644
index 00000000000..5c729e782a2
--- /dev/null
+++ b/src/components/range/range.ios.scss
@@ -0,0 +1,162 @@
+@import "../../globals.ios";
+
+// iOS Range
+// --------------------------------------------------
+
+$range-ios-slider-height: 42px !default;
+
+$range-ios-hit-width: 42px !default;
+$range-ios-hit-height: $range-ios-slider-height !default;
+
+$range-ios-bar-height: 2px !default;
+$range-ios-bar-background-color: #bdbdbd !default;
+$range-ios-bar-active-background-color: color($colors-ios, primary) !default;
+
+$range-ios-knob-width: 12px !default;
+$range-ios-knob-height: $range-ios-knob-width !default;
+$range-ios-knob-background-color: $range-ios-bar-active-background-color !default;
+
+$range-ios-tick-width: 6px !default;
+$range-ios-tick-height: $range-ios-tick-width !default;
+$range-ios-tick-background-color: $range-ios-bar-background-color !default;
+$range-ios-tick-active-background-color: $range-ios-bar-active-background-color !default;
+
+$range-ios-pin-background-color: $range-ios-bar-active-background-color !default;
+$range-ios-pin-color: color-contrast($colors-ios, $range-ios-pin-background-color) !default;
+$range-ios-pin-font-size: 12px !default;
+
+
+.item-range .item-inner {
+ overflow: visible;
+}
+
+.item-range .input-wrapper {
+ overflow: visible;
+
+ flex-direction: column;
+}
+
+.item-range ion-range {
+ width: 100%;
+}
+
+ion-range {
+ position: relative;
+ display: block;
+
+ margin-top: -16px;
+ padding: 8px;
+}
+
+.range-slider {
+ position: relative;
+
+ height: $range-ios-slider-height;
+
+ cursor: pointer;
+}
+
+.range-bar {
+ position: absolute;
+ top: ($range-ios-slider-height / 2);
+ left: 0;
+
+ width: 100%;
+ height: $range-ios-bar-height;
+
+ background: $range-ios-bar-background-color;
+
+ pointer-events: none;
+}
+
+.range-pressed .range-bar-active {
+ will-change: left, right;
+}
+
+.range-pressed .range-knob-handle {
+ will-change: left;
+}
+
+.range-bar-active {
+ bottom: 0;
+
+ width: auto;
+
+ background: $range-ios-bar-active-background-color;
+}
+
+.range-knob-handle {
+ position: absolute;
+ top: ($range-ios-slider-height / 2);
+ left: 0%;
+
+ margin-top: -($range-ios-hit-height / 2);
+ margin-left: -($range-ios-hit-width / 2);
+
+ width: $range-ios-hit-width;
+ height: $range-ios-hit-height;
+
+ text-align: center;
+}
+
+.range-knob {
+ position: absolute;
+ top: ($range-ios-hit-height / 2) - ($range-ios-knob-height / 2) + ($range-ios-bar-height / 2);
+ left: ($range-ios-hit-width / 2) - ($range-ios-knob-width / 2);
+
+ width: $range-ios-knob-width;
+ height: $range-ios-knob-height;
+
+ border-radius: 50%;
+
+ background: $range-ios-knob-background-color;
+
+ pointer-events: none;
+}
+
+.range-tick {
+ position: absolute;
+ top: ($range-ios-hit-height / 2) - ($range-ios-tick-height / 2) + ($range-ios-bar-height / 2);
+
+ margin-left: ($range-ios-tick-width / 2) * -1;
+
+ width: $range-ios-tick-width;
+ height: $range-ios-tick-height;
+
+ border-radius: 50%;
+
+ background: $range-ios-tick-background-color;
+
+ pointer-events: none;
+}
+
+.range-tick-active {
+ background: $range-ios-tick-active-background-color;
+}
+
+.range-pin {
+ position: relative;
+ top: -20px;
+ display: inline-block;
+
+ padding: 8px;
+
+ min-width: 28px;
+
+ border-radius: 50px;
+
+ font-size: $range-ios-pin-font-size;
+
+ text-align: center;
+
+ color: $range-ios-pin-color;
+
+ background: $range-ios-pin-background-color;
+
+ transform: translate3d(0, 28px, 0) scale(.01);
+ transition: transform 120ms ease;
+}
+
+.range-knob-pressed .range-pin {
+ transform: translate3d(0, 0, 0) scale(1);
+}
diff --git a/src/components/range/range.md.scss b/src/components/range/range.md.scss
new file mode 100644
index 00000000000..7fb37a2c58d
--- /dev/null
+++ b/src/components/range/range.md.scss
@@ -0,0 +1,4 @@
+@import "../../globals.md";
+
+// Material Design Range
+// --------------------------------------------------
diff --git a/src/components/range/range.ts b/src/components/range/range.ts
new file mode 100644
index 00000000000..3249b027775
--- /dev/null
+++ b/src/components/range/range.ts
@@ -0,0 +1,633 @@
+import {Component, Optional, Input, Output, EventEmitter, ViewChild, ViewChildren, QueryList, Renderer, ElementRef, Provider, Inject, forwardRef, ViewEncapsulation} from '@angular/core';
+import {NG_VALUE_ACCESSOR} from '@angular/common';
+
+import {Form} from '../../util/form';
+import {isTrueProperty, isNumber, isString, isPresent, clamp} from '../../util/util';
+import {Item} from '../item/item';
+import {pointerCoord} from '../../util/dom';
+
+
+const RANGE_VALUE_ACCESSOR = new Provider(
+ NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => Range), multi: true});
+
+
+@Component({
+ selector: '.range-knob-handle',
+ template:
+ '
{{_val}}
' +
+ '',
+ host: {
+ '[class.range-knob-pressed]': 'pressed',
+ '[style.left]': '_x',
+ '[style.top]': '_y',
+ '[style.transform]': '_trns',
+ '[attr.aria-valuenow]': '_val',
+ '[attr.aria-valuemin]': 'range.min',
+ '[attr.aria-valuemax]': 'range.max',
+ 'role': 'slider',
+ 'tabindex': '0'
+ }
+})
+export class RangeKnob {
+ private _ratio: number;
+ private _val: number;
+ private _x: string;
+ pressed: boolean;
+
+ @Input() upper: boolean;
+
+ constructor(@Inject(forwardRef(() => Range)) private range: Range) {}
+
+ get ratio(): number {
+ return this._ratio;
+ }
+ set ratio(ratio: number) {
+ this._ratio = clamp(0, ratio, 1);
+ this._val = this.range.ratioToValue(this._ratio);
+
+ if (this.range.snaps) {
+ this._ratio = this.range.valueToRatio(this._val);
+ }
+ }
+
+ get value(): number {
+ return this._val;
+ }
+ set value(val: number) {
+ if (isString(val)) {
+ val = Math.round(val);
+ }
+ if (isNumber(val) && !isNaN(val)) {
+ this._ratio = this.range.valueToRatio(val);
+ this._val = this.range.ratioToValue(this._ratio);
+ }
+ }
+
+ position() {
+ this._x = `${this._ratio * 100}%`;
+ }
+
+ ngOnInit() {
+ if (isPresent(this.range.value)) {
+ // we already have a value
+ if (this.range.dualKnobs) {
+ // we have a value and there are two knobs
+ if (this.upper) {
+ // this is the upper knob
+ this.value = this.range.value.upper;
+
+ } else {
+ // this is the lower knob
+ this.value = this.range.value.lower;
+ }
+
+ } else {
+ // we have a value and there is only one knob
+ this.value = this.range.value;
+ }
+
+ } else {
+ // we do not have a value so set defaults
+ this.ratio = ((this.range.dualKnobs && this.upper) ? 1 : 0);
+ }
+
+ this.position();
+ }
+
+}
+
+
+/**
+ * @name Range
+ *
+ * @description
+ */
+@Component({
+ selector: 'ion-range',
+ template:
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
',
+ host: {
+ '[class.range-disabled]': '_disabled',
+ '[class.range-pressed]': '_pressed',
+ },
+ directives: [RangeKnob],
+ providers: [RANGE_VALUE_ACCESSOR],
+ encapsulation: ViewEncapsulation.None,
+})
+export class Range {
+ private _dual: boolean = false;
+ private _pin: boolean;
+ private _disabled: boolean = false;
+ private _pressed: boolean;
+ private _labelId: string;
+ private _fn: Function;
+
+ private _active: RangeKnob;
+ private _start: Coordinates = null;
+ private _rect: ClientRect;
+ private _ticks: any[];
+ private _barL: string;
+ private _barR: string;
+
+ private _min: number = 0;
+ private _max: number = 100;
+ private _step: number = 1;
+ private _snaps: boolean = false;
+ private _removes: Function[] = [];
+ private _mouseRemove: Function;
+
+ value: any;
+
+ @ViewChild('bar') private _bar: ElementRef;
+ @ViewChild('slider') private _slider: ElementRef;
+ @ViewChildren(RangeKnob) private _knobs: QueryList;
+
+ /**
+ * @private
+ */
+ id: string;
+
+ /**
+ * @input {number} Minimum integer value of the range. Defaults to `0`.
+ */
+ @Input()
+ get min(): number {
+ return this._min;
+ }
+ set min(val: number) {
+ val = Math.round(val);
+ if (!isNaN(val)) {
+ this._min = val;
+ }
+ }
+
+ /**
+ * @input {number} Maximum integer value of the range. Defaults to `100`.
+ */
+ @Input()
+ get max(): number {
+ return this._max;
+ }
+ set max(val: number) {
+ val = Math.round(val);
+ if (!isNaN(val)) {
+ this._max = val;
+ }
+ }
+
+ /**
+ * @input {number} Specifies the value granularity. Defaults to `1`.
+ */
+ @Input()
+ get step(): number {
+ return this._step;
+ }
+ set step(val: number) {
+ val = Math.round(val);
+ if (!isNaN(val) && val > 0) {
+ this._step = val;
+ }
+ }
+
+ /**
+ * @input {number} If true, the knob snaps to tick marks evenly spaced based on the step property value. Defaults to `false`.
+ */
+ @Input()
+ get snaps(): boolean {
+ return this._snaps;
+ }
+ set snaps(val: boolean) {
+ this._snaps = isTrueProperty(val);
+ }
+
+ /**
+ * @input {number} If true, a pin with integer value is shown when the knob is pressed. Defaults to `false`.
+ */
+ @Input()
+ get pin(): boolean {
+ return this._pin;
+ }
+ set pin(val: boolean) {
+ this._pin = isTrueProperty(val);
+ }
+
+ /**
+ * @input {boolean} Show two knobs. Defaults to `false`.
+ */
+ @Input()
+ get dualKnobs(): boolean {
+ return this._dual;
+ }
+ set dualKnobs(val: boolean) {
+ this._dual = isTrueProperty(val);
+ }
+
+ /**
+ * @output {Range} Expression to evaluate when the range value changes.
+ */
+ @Output() rangeChange: EventEmitter = new EventEmitter();
+
+
+ constructor(
+ private _form: Form,
+ @Optional() private _item: Item,
+ private _renderer: Renderer
+ ) {
+ _form.register(this);
+
+ if (_item) {
+ this.id = 'rng-' + _item.registerInput('range');
+ this._labelId = 'lbl-' + _item.id;
+ _item.setCssClass('item-range', true);
+ }
+ }
+
+ /**
+ * @private
+ */
+ ngAfterViewInit() {
+ let barL = '';
+ let barR = '';
+
+ let firstRatio = this._knobs.first.ratio;
+
+ if (this._dual) {
+ let lastRatio = this._knobs.last.ratio;
+ barL = `${(Math.min(firstRatio, lastRatio) * 100)}%`;
+ barR = `${100 - (Math.max(firstRatio, lastRatio) * 100)}%`;
+
+ } else {
+ barR = `${100 - (firstRatio * 100)}%`;
+ }
+
+ this._renderer.setElementStyle(this._bar.nativeElement, 'left', barL);
+ this._renderer.setElementStyle(this._bar.nativeElement, 'right', barR);
+
+ this.createTicks();
+
+ // add touchstart/mousedown listeners
+ this._renderer.listen(this._slider.nativeElement, 'touchstart', this.pointerDown.bind(this));
+ this._mouseRemove = this._renderer.listen(this._slider.nativeElement, 'mousedown', this.pointerDown.bind(this));
+ }
+
+ /**
+ * @private
+ */
+ pointerDown(ev: UIEvent) {
+ console.debug(`range, ${ev.type}`);
+
+ // prevent default so scrolling does not happen
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ if (ev.type === 'touchstart') {
+ // if this was a touchstart, then let's remove the mousedown
+ this._mouseRemove && this._mouseRemove();
+ }
+
+ // get the start coordinates
+ this._start = pointerCoord(ev);
+
+ // get the full dimensions of the slider element
+ let rect: ClientRect = this._rect = this._slider.nativeElement.getBoundingClientRect();
+
+ // figure out the offset
+ // the start of the pointer could actually
+ // have been left or right of the slider bar
+ if (this._start.x < rect.left) {
+ rect.xOffset = (this._start.x - rect.left);
+
+ } else if (this._start.x > rect.right) {
+ rect.xOffset = (this._start.x - rect.right);
+
+ } else {
+ rect.xOffset = 0;
+ }
+
+ // figure out which knob we're interacting with
+ this.setActiveKnob(this._start, rect);
+
+ // update the ratio for the active knob
+ this.updateKnob(this._start, rect);
+
+ // ensure past listeners have been removed
+ this.clearListeners();
+
+ // update the active knob's position
+ this._active.position();
+ this._pressed = this._active.pressed = true;
+
+ // add a move listener depending on touch/mouse
+ let renderer = this._renderer;
+ let removes = this._removes;
+
+ if (ev.type === 'touchstart') {
+ removes.push(renderer.listen(this._slider.nativeElement, 'touchmove', this.pointerMove.bind(this)));
+ removes.push(renderer.listen(this._slider.nativeElement, 'touchend', this.pointerUp.bind(this)));
+
+ } else {
+ removes.push(renderer.listenGlobal('body', 'mousemove', this.pointerMove.bind(this)));
+ removes.push(renderer.listenGlobal('body', 'mouseup', this.pointerUp.bind(this)));
+ }
+ }
+
+ /**
+ * @private
+ */
+ pointerMove(ev: UIEvent) {
+ console.debug(`range, ${ev.type}`);
+
+ // prevent default so scrolling does not happen
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ if (this._start !== null && this._active !== null) {
+ // only use pointer move if it's a valid pointer
+ // and we already have start coordinates
+
+ // update the ratio for the active knob
+ this.updateKnob(pointerCoord(ev), this._rect);
+
+ // update the active knob's position
+ this._active.position();
+ this._pressed = this._active.pressed = true;
+
+ } else {
+ // ensure listeners have been removed
+ this.clearListeners();
+ }
+ }
+
+ /**
+ * @private
+ */
+ pointerUp(ev: UIEvent) {
+ console.debug(`range, ${ev.type}`);
+
+ // prevent default so scrolling does not happen
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ // update the ratio for the active knob
+ this.updateKnob(pointerCoord(ev), this._rect);
+
+ // update the active knob's position
+ this._active.position();
+
+ // clear the start coordinates and active knob
+ this._start = this._active = null;
+
+ // ensure listeners have been removed
+ this.clearListeners();
+ }
+
+ /**
+ * @private
+ */
+ clearListeners() {
+ this._pressed = this._knobs.first.pressed = this._knobs.last.pressed = false;
+
+ for (var i = 0; i < this._removes.length; i++) {
+ this._removes[i]();
+ }
+ this._removes.length = 0;
+ }
+
+ /**
+ * @private
+ */
+ setActiveKnob(current: Coordinates, rect: ClientRect) {
+ // figure out which knob is the closest one to the pointer
+ let ratio = (current.x - rect.left) / (rect.width);
+
+ if (this._dual && Math.abs(ratio - this._knobs.first.ratio) > Math.abs(ratio - this._knobs.last.ratio)) {
+ this._active = this._knobs.last;
+
+ } else {
+ this._active = this._knobs.first;
+ }
+ }
+
+ /**
+ * @private
+ */
+ updateKnob(current: Coordinates, rect: ClientRect) {
+ // figure out where the pointer is currently at
+ // update the knob being interacted with
+ if (this._active) {
+ let oldVal = this._active.value;
+ this._active.ratio = (current.x - rect.left) / (rect.width);
+ let newVal = this._active.value;
+
+ if (oldVal !== newVal) {
+ // value has been updated
+ if (this._dual) {
+ this.value = {
+ lower: Math.min(this._knobs.first.value, this._knobs.last.value),
+ upper: Math.max(this._knobs.first.value, this._knobs.last.value),
+ };
+
+ } else {
+ this.value = newVal;
+ }
+
+ this.onChange(this.value);
+ }
+
+ this.updateBar();
+ }
+ }
+
+ /**
+ * @private
+ */
+ updateBar() {
+ let firstRatio = this._knobs.first.ratio;
+
+ if (this._dual) {
+ let lastRatio = this._knobs.last.ratio;
+ this._barL = `${(Math.min(firstRatio, lastRatio) * 100)}%`;
+ this._barR = `${100 - (Math.max(firstRatio, lastRatio) * 100)}%`;
+
+ } else {
+ this._barL = '';
+ this._barR = `${100 - (firstRatio * 100)}%`;
+ }
+
+ this.updateTicks();
+ }
+
+ /**
+ * @private
+ */
+ createTicks() {
+ if (this._snaps) {
+ 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();
+
+ } else {
+ this._ticks = null;
+ }
+ }
+
+ /**
+ * @private
+ */
+ updateTicks() {
+ if (this._snaps) {
+ let ratio = this.ratio;
+ if (this._dual) {
+ let upperRatio = this.ratioUpper;
+
+ this._ticks.forEach(t => {
+ t.active = (t.ratio >= ratio && t.ratio <= upperRatio);
+ });
+
+ } else {
+ this._ticks.forEach(t => {
+ t.active = (t.ratio <= ratio);
+ });
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ ratioToValue(ratio: number) {
+ ratio = Math.round(((this._max - this._min) * ratio) + this._min);
+ return Math.round(ratio / this._step) * this._step;
+ }
+
+ /**
+ * @private
+ */
+ valueToRatio(value: number) {
+ value = Math.round(clamp(this._min, value, this._max) / this._step) * this._step;
+ return (value - this._min) / (this._max - this._min);
+ }
+
+ /**
+ * @private
+ */
+ writeValue(val: any) {
+ if (isPresent(val)) {
+ let knobs = this._knobs;
+ this.value = val;
+
+ if (this._knobs) {
+ if (this._dual) {
+ knobs.first.value = val.lower;
+ knobs.last.value = val.upper;
+ knobs.last.position();
+
+ } else {
+ knobs.first.value = val;
+ }
+ knobs.first.position();
+ this.updateBar();
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ registerOnChange(fn: Function): void {
+ this._fn = fn;
+ this.onChange = (val: any) => {
+ fn(val);
+ this.onTouched();
+ };
+ }
+
+ /**
+ * @private
+ */
+ registerOnTouched(fn) { this.onTouched = fn; }
+
+ /**
+ * @input {boolean} whether or not the checkbox is disabled or not.
+ */
+ @Input()
+ get disabled(): boolean {
+ return this._disabled;
+ }
+ set disabled(val: boolean) {
+ this._disabled = isTrueProperty(val);
+ this._item && this._item.setCssClass('item-range-disabled', this._disabled);
+ }
+
+ /**
+ * 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 ratio(): number {
+ if (this._dual) {
+ return Math.min(this._knobs.first.ratio, this._knobs.last.ratio);
+ }
+ return this._knobs.first.ratio;
+ }
+
+ /**
+ * 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 ratioUpper(): number {
+ if (this._dual) {
+ return Math.max(this._knobs.first.ratio, this._knobs.last.ratio);
+ }
+ return null;
+ }
+
+ /**
+ * @private
+ */
+ onChange(val: any) {
+ // used when this input does not have an ngModel or ngControl
+ this.onTouched();
+ }
+
+ /**
+ * @private
+ */
+ onTouched() {}
+
+ /**
+ * @private
+ */
+ ngOnDestroy() {
+ this._form.deregister(this);
+ this.clearListeners();
+ }
+}
+
+
+export interface ClientRect {
+ top?: number;
+ right?: number;
+ bottom?: number;
+ left?: number;
+ width?: number;
+ height?: number;
+ xOffset?: number;
+ yOffset?: number;
+}
+
+export interface Coordinates {
+ x?: number;
+ y?: number;
+}
diff --git a/src/components/range/range.wp.scss b/src/components/range/range.wp.scss
new file mode 100644
index 00000000000..edf79664cbb
--- /dev/null
+++ b/src/components/range/range.wp.scss
@@ -0,0 +1,4 @@
+@import "../../globals.wp";
+
+// Windows Range
+// --------------------------------------------------
diff --git a/src/components/range/test/basic/e2e.ts b/src/components/range/test/basic/e2e.ts
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/components/range/test/basic/index.ts b/src/components/range/test/basic/index.ts
new file mode 100644
index 00000000000..7cdfc8d9d66
--- /dev/null
+++ b/src/components/range/test/basic/index.ts
@@ -0,0 +1,22 @@
+import {App, Page} from '../../../../../src';
+
+
+@Page({
+ templateUrl: 'page1.html'
+})
+class Page1 {
+ singleValue: number;
+ singleValue2: number = 150;
+ singleValue3: number = 64;
+ singleValue4: number = 1300;
+ dualValue: any;
+ dualValue2 = {lower: 33, upper: 60};
+}
+
+
+@App({
+ templateUrl: 'main.html'
+})
+class E2EApp {
+ rootPage = Page1;
+}
diff --git a/src/components/range/test/basic/main.html b/src/components/range/test/basic/main.html
new file mode 100644
index 00000000000..4f1fff2076b
--- /dev/null
+++ b/src/components/range/test/basic/main.html
@@ -0,0 +1,133 @@
+
+
+
+ Left Menu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Right Menu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/range/test/basic/page1.html b/src/components/range/test/basic/page1.html
new file mode 100644
index 00000000000..bf0e15ebfb7
--- /dev/null
+++ b/src/components/range/test/basic/page1.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Range
+
+
+
+
+
+
+
+
+
+
+
+
+ no init value, default min/max, {{singleValue}}
+
+
+
+
+ {{singleValue2}}
+ init=150, min=-200, max=200
+
+ {{singleValue2}}
+
+
+
+ step=2, {{singleValue3}}
+
+
+
+
+ step=100, snaps, {{singleValue4}}
+
+
+
+
+ dual, {{dualValue | json}}
+
+
+
+
+ dual, step=3, snaps, {{dualValue2 | json}}
+
+
+
+
+ pin, {{singleValue}}
+
+
+
+
+ init=150, min=-200, max=200, {{singleValue2}}
+
+
+
+
+ step=2, {{singleValue3}}
+
+
+
+
+ step=100, snaps, pin, {{singleValue4}}
+
+
+
+
+ dual, pin, {{dualValue | json}}
+
+
+
+
+ dual, step=3, snaps, {{dualValue2 | json}}
+
+
+
+
+
+
diff --git a/src/config/directives.ts b/src/config/directives.ts
index b499145ff38..a50c130b8b0 100644
--- a/src/config/directives.ts
+++ b/src/config/directives.ts
@@ -34,6 +34,7 @@ import {Label} from '../components/label/label';
import {Segment, SegmentButton} from '../components/segment/segment';
import {RadioButton} from '../components/radio/radio-button';
import {RadioGroup} from '../components/radio/radio-group';
+import {Range} from '../components/range/range';
import {Searchbar, SearchbarInput} from '../components/searchbar/searchbar';
import {Nav} from '../components/nav/nav';
import {NavPush, NavPop} from '../components/nav/nav-push';
@@ -197,6 +198,7 @@ export const IONIC_DIRECTIVES: any[] = [
Checkbox,
RadioGroup,
RadioButton,
+ Range,
Select,
Option,
DateTime,