mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
feat(range): create ion-range input
This commit is contained in:
162
src/components/range/range.ios.scss
Normal file
162
src/components/range/range.ios.scss
Normal file
@ -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);
|
||||
}
|
4
src/components/range/range.md.scss
Normal file
4
src/components/range/range.md.scss
Normal file
@ -0,0 +1,4 @@
|
||||
@import "../../globals.md";
|
||||
|
||||
// Material Design Range
|
||||
// --------------------------------------------------
|
633
src/components/range/range.ts
Normal file
633
src/components/range/range.ts
Normal file
@ -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:
|
||||
'<div class="range-pin" *ngIf="range.pin">{{_val}}</div>' +
|
||||
'<div class="range-knob"></div>',
|
||||
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:
|
||||
'<div class="range-slider" #slider>' +
|
||||
'<div class="range-tick" *ngFor="let t of _ticks" [style.left]="t.left" [class.range-tick-active]="t.active"></div>' +
|
||||
'<div class="range-bar"></div>' +
|
||||
'<div class="range-bar range-bar-active" [style.left]="_barL" [style.right]="_barR" #bar></div>' +
|
||||
'<div class="range-knob-handle"></div>' +
|
||||
'<div class="range-knob-handle" [upper]="true" *ngIf="_dual"></div>' +
|
||||
'</div>',
|
||||
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<RangeKnob>;
|
||||
|
||||
/**
|
||||
* @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<Range> = 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;
|
||||
}
|
4
src/components/range/range.wp.scss
Normal file
4
src/components/range/range.wp.scss
Normal file
@ -0,0 +1,4 @@
|
||||
@import "../../globals.wp";
|
||||
|
||||
// Windows Range
|
||||
// --------------------------------------------------
|
0
src/components/range/test/basic/e2e.ts
Normal file
0
src/components/range/test/basic/e2e.ts
Normal file
22
src/components/range/test/basic/index.ts
Normal file
22
src/components/range/test/basic/index.ts
Normal file
@ -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;
|
||||
}
|
133
src/components/range/test/basic/main.html
Normal file
133
src/components/range/test/basic/main.html
Normal file
@ -0,0 +1,133 @@
|
||||
<ion-menu [content]="content" side="left" persistent="true">
|
||||
|
||||
<ion-toolbar secondary>
|
||||
<ion-title>Left Menu</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
|
||||
<button ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
|
||||
<ion-menu [content]="content" side="right">
|
||||
|
||||
<ion-toolbar danger>
|
||||
<ion-title>Right Menu</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="rootPage" #content></ion-nav>
|
86
src/components/range/test/basic/page1.html
Normal file
86
src/components/range/test/basic/page1.html
Normal file
@ -0,0 +1,86 @@
|
||||
<ion-navbar *navbar>
|
||||
|
||||
<button menuToggle="left">
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
|
||||
<ion-title>
|
||||
Range
|
||||
</ion-title>
|
||||
|
||||
<button menuToggle="right" right>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
|
||||
</ion-navbar>
|
||||
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>no init value, default min/max, {{singleValue}}</ion-label>
|
||||
<ion-range [(ngModel)]="singleValue"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<div item-left>{{singleValue2}}</div>
|
||||
<ion-label>init=150, min=-200, max=200</ion-label>
|
||||
<ion-range min="-200" max="200" [(ngModel)]="singleValue2"></ion-range>
|
||||
<div item-right>{{singleValue2}}</div>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>step=2, {{singleValue3}}</ion-label>
|
||||
<ion-range min="20" max="80" step="2" [(ngModel)]="singleValue3"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>step=100, snaps, {{singleValue4}}</ion-label>
|
||||
<ion-range min="1000" max="2000" step="100" snaps="true" [(ngModel)]="singleValue4"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>dual, {{dualValue | json}}</ion-label>
|
||||
<ion-range dualKnobs="true" [(ngModel)]="dualValue"></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-item>
|
||||
<ion-label>pin, {{singleValue}}</ion-label>
|
||||
<ion-range [(ngModel)]="singleValue" pin="true"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>init=150, min=-200, max=200, {{singleValue2}}</ion-label>
|
||||
<ion-range min="-200" max="200" [(ngModel)]="singleValue2"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>step=2, {{singleValue3}}</ion-label>
|
||||
<ion-range min="20" max="80" step="2" [(ngModel)]="singleValue3"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>step=100, snaps, pin, {{singleValue4}}</ion-label>
|
||||
<ion-range min="1000" max="2000" step="100" pin="true" snaps="true" [(ngModel)]="singleValue4"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>dual, pin, {{dualValue | json}}</ion-label>
|
||||
<ion-range dualKnobs="true" pin="true" [(ngModel)]="dualValue"></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>
|
||||
|
||||
</ion-content>
|
Reference in New Issue
Block a user