mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 21:48:42 +08:00
feat(range): improve range and gesture components
This commit is contained in:
@ -3,6 +3,42 @@ import { BlockerDelegate, GestureController, GestureDelegate, BLOCK_ALL } from '
|
||||
import { Component, Element, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core';
|
||||
import { PanRecognizer } from './recognizers';
|
||||
|
||||
/**
|
||||
* @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-start` or `range-end` 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 `dual-knobs` 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`.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
@Component({
|
||||
tag: 'ion-gesture'
|
||||
@ -44,6 +80,8 @@ export class Gesture {
|
||||
@Prop() onMove: GestureCallback;
|
||||
@Prop() onEnd: GestureCallback;
|
||||
@Prop() onPress: GestureCallback;
|
||||
@Prop() onDown: GestureCallback;
|
||||
@Prop() onUp: GestureCallback;
|
||||
@Prop() notCaptured: GestureCallback;
|
||||
|
||||
|
||||
@ -149,7 +187,7 @@ export class Gesture {
|
||||
|
||||
this.pan.start(detail.startX, detail.startY);
|
||||
}
|
||||
|
||||
this.onDown(detail)
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -305,7 +343,7 @@ export class Gesture {
|
||||
detail.event = ev;
|
||||
|
||||
this.calcGestureData(ev);
|
||||
|
||||
this.onUp(detail)
|
||||
if (this.pan) {
|
||||
if (this.hasCapturedPan) {
|
||||
detail.type = 'pan';
|
||||
|
@ -11,6 +11,7 @@ export class RangeKnob {
|
||||
@Prop() val: number;
|
||||
@Prop() disabled: boolean;
|
||||
@Prop() labelId: string;
|
||||
@Prop() knob: string;
|
||||
@Prop() ratio: number;
|
||||
|
||||
@Event() ionIncrease: EventEmitter;
|
||||
@ -20,11 +21,11 @@ export class RangeKnob {
|
||||
handleKeyBoard(ev: KeyboardEvent) {
|
||||
const keyCode = ev.keyCode;
|
||||
if (keyCode === KEY_LEFT || keyCode === KEY_DOWN) {
|
||||
this.ionDecrease.emit({isIncrease: false});
|
||||
this.ionDecrease.emit({isIncrease: false, knob: this.knob});
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
} else if (keyCode === KEY_RIGHT || keyCode === KEY_UP) {
|
||||
this.ionIncrease.emit({isIncrease: true});
|
||||
this.ionIncrease.emit({isIncrease: true, knob: this.knob});
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
@ -42,16 +43,16 @@ export class RangeKnob {
|
||||
'range-knob-max': this.val === this.max
|
||||
},
|
||||
style: {
|
||||
left: this.leftPos(this.ratio)
|
||||
'left': this.leftPos(this.ratio)
|
||||
},
|
||||
attrs: {
|
||||
role: 'slider',
|
||||
tabindex: this.disabled ? -1 : 0,
|
||||
'aria-valuemin': `${this.min}`,
|
||||
'aria-valuemax': `${this.max}`,
|
||||
'aria-disabled': `${this.disabled}`,
|
||||
'aria-labelledby': `${this.labelId}`,
|
||||
'aria-valuenow': `${this.val}`
|
||||
'role': 'slider',
|
||||
'tabindex': this.disabled ? -1 : 0,
|
||||
'aria-valuemin': this.min,
|
||||
'aria-valuemax': this.max,
|
||||
'aria-disabled': this.disabled,
|
||||
'aria-labelledby': this.labelId,
|
||||
'aria-valuenow': this.val
|
||||
}
|
||||
};
|
||||
}
|
||||
|
227
packages/core/src/components/range/range.ios.scss
Normal file
227
packages/core/src/components/range/range.ios.scss
Normal file
@ -0,0 +1,227 @@
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
|
||||
// iOS Range
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Padding top/bottom of the range
|
||||
$range-ios-padding-vertical: 8px !default;
|
||||
|
||||
/// @prop - Padding start/end of the range
|
||||
$range-ios-padding-horizontal: 16px !default;
|
||||
|
||||
/// @prop - Height of the range slider
|
||||
$range-ios-slider-height: 42px !default;
|
||||
|
||||
/// @prop - Width of the area that will select the range knob
|
||||
$range-ios-hit-width: 42px !default;
|
||||
|
||||
/// @prop - Height of the area that will select the range knob
|
||||
$range-ios-hit-height: $range-ios-slider-height !default;
|
||||
|
||||
/// @prop - Height of the range bar
|
||||
$range-ios-bar-height: 1px !default;
|
||||
|
||||
/// @prop - Background of the range bar
|
||||
$range-ios-bar-background-color: #bdbdbd !default;
|
||||
|
||||
/// @prop - Background of the active range bar
|
||||
$range-ios-bar-active-background-color: color($colors-ios, primary) !default;
|
||||
|
||||
/// @prop - Width of the range knob
|
||||
$range-ios-knob-width: 28px !default;
|
||||
|
||||
/// @prop - Height of the range knob
|
||||
$range-ios-knob-height: $range-ios-knob-width !default;
|
||||
|
||||
/// @prop - Box shadow of the range knob
|
||||
$range-ios-knob-box-shadow: 0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02) !default;
|
||||
|
||||
/// @prop - Border radius of the range knob
|
||||
$range-ios-knob-border-radius: 50% !default;
|
||||
|
||||
/// @prop - Background of the range knob
|
||||
$range-ios-knob-background-color: #fff !default;
|
||||
|
||||
/// @prop - Width of the range tick
|
||||
$range-ios-tick-width: $range-ios-bar-height !default;
|
||||
|
||||
/// @prop - Height of the range tick
|
||||
$range-ios-tick-height: 8px !default;
|
||||
|
||||
/// @prop - Border radius of the range tick
|
||||
$range-ios-tick-border-radius: 0 !default;
|
||||
|
||||
/// @prop - Background of the range tick
|
||||
$range-ios-tick-background-color: $range-ios-bar-background-color !default;
|
||||
|
||||
/// @prop - Background of the active range tick
|
||||
$range-ios-tick-active-background-color: $range-ios-bar-active-background-color !default;
|
||||
|
||||
/// @prop - Background of the range pin
|
||||
$range-ios-pin-background-color: transparent !default;
|
||||
|
||||
/// @prop - Color of the range pin
|
||||
$range-ios-pin-color: $text-ios-color !default;
|
||||
|
||||
/// @prop - Font size of the range pin
|
||||
$range-ios-pin-font-size: 12px !default;
|
||||
|
||||
// deprecated
|
||||
$range-ios-pin-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the range pin
|
||||
$range-ios-pin-padding-top: 8px !default;
|
||||
|
||||
/// @prop - Padding end of the range pin
|
||||
$range-ios-pin-padding-end: $range-ios-pin-padding-top !default;
|
||||
|
||||
/// @prop - Padding bottom of the range pin
|
||||
$range-ios-pin-padding-bottom: $range-ios-pin-padding-top !default;
|
||||
|
||||
/// @prop - Padding start of the range pin
|
||||
$range-ios-pin-padding-start: $range-ios-pin-padding-end !default;
|
||||
|
||||
|
||||
.range-ios {
|
||||
@include padding($range-ios-padding-vertical, $range-ios-padding-horizontal);
|
||||
}
|
||||
|
||||
.range-ios [range-left] {
|
||||
@include margin(0, 20px, 0, 0);
|
||||
}
|
||||
|
||||
.range-ios [range-right] {
|
||||
@include margin(0, 0, 0, 20px);
|
||||
}
|
||||
|
||||
.range-ios.range-has-pin {
|
||||
@include padding($range-ios-padding-vertical + $range-ios-pin-font-size, null, null, null);
|
||||
}
|
||||
|
||||
.range-ios .range-slider {
|
||||
height: $range-ios-slider-height;
|
||||
}
|
||||
|
||||
.range-ios .range-bar {
|
||||
@include position(($range-ios-slider-height / 2), null, null, 0);
|
||||
@include border-radius(1px);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 100%;
|
||||
height: $range-ios-bar-height;
|
||||
|
||||
background: $range-ios-bar-background-color;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-ios.range-pressed .range-bar-active {
|
||||
will-change: left, right;
|
||||
}
|
||||
|
||||
.range-ios.range-pressed .range-knob-handle {
|
||||
will-change: left;
|
||||
}
|
||||
|
||||
.range-ios .range-bar-active {
|
||||
bottom: 0;
|
||||
|
||||
width: auto;
|
||||
|
||||
background: $range-ios-bar-active-background-color;
|
||||
}
|
||||
|
||||
.range-ios .range-knob-handle {
|
||||
@include position(($range-ios-slider-height / 2), null, null, 0);
|
||||
@include margin(-($range-ios-hit-height / 2), null, null, -($range-ios-hit-width / 2));
|
||||
@include text-align(center);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: $range-ios-hit-width;
|
||||
height: $range-ios-hit-height;
|
||||
}
|
||||
|
||||
.range-ios .range-knob {
|
||||
@include position(($range-ios-hit-height / 2) - ($range-ios-knob-height / 2) + ($range-ios-bar-height / 2) - .5px,
|
||||
null, null, ($range-ios-hit-width / 2) - ($range-ios-knob-width / 2));
|
||||
@include border-radius($range-ios-knob-border-radius);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: $range-ios-knob-width;
|
||||
height: $range-ios-knob-height;
|
||||
|
||||
background: $range-ios-knob-background-color;
|
||||
|
||||
box-shadow: $range-ios-knob-box-shadow;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-ios .range-tick {
|
||||
@include margin-horizontal(-($range-ios-tick-width / 2), null);
|
||||
@include border-radius($range-ios-tick-border-radius);
|
||||
|
||||
position: absolute;
|
||||
top: ($range-ios-hit-height / 2) - ($range-ios-tick-height / 2) + ($range-ios-bar-height / 2);
|
||||
|
||||
width: $range-ios-tick-width;
|
||||
height: $range-ios-tick-height;
|
||||
|
||||
background: $range-ios-tick-background-color;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-ios .range-tick-active {
|
||||
background: $range-ios-tick-active-background-color;
|
||||
}
|
||||
|
||||
.range-ios .range-pin {
|
||||
@include text-align(center);
|
||||
@include border-radius(50px);
|
||||
@include transform(translate3d(0, 28px, 0), scale(.01));
|
||||
|
||||
position: relative;
|
||||
top: -20px;
|
||||
display: inline-block;
|
||||
|
||||
min-width: 28px;
|
||||
|
||||
font-size: $range-ios-pin-font-size;
|
||||
|
||||
color: $range-ios-pin-color;
|
||||
|
||||
background: $range-ios-pin-background-color;
|
||||
|
||||
transition: transform 120ms ease;
|
||||
|
||||
@include deprecated-variable(padding, $range-ios-pin-padding) {
|
||||
@include padding($range-ios-pin-padding-top, $range-ios-pin-padding-end, $range-ios-pin-padding-bottom, $range-ios-pin-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.range-ios .range-knob-pressed .range-pin {
|
||||
@include transform(translate3d(0, 0, 0), scale(1));
|
||||
}
|
||||
|
||||
.range-ios.range-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
|
||||
// Generate iOS Range Colors
|
||||
// --------------------------------------------------
|
||||
|
||||
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
|
||||
|
||||
.range-ios-#{$color-name} {
|
||||
.range-bar-active,
|
||||
.range-tick-active {
|
||||
background: $color-base;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +1,13 @@
|
||||
import {
|
||||
Component,
|
||||
Element,
|
||||
Event,
|
||||
EventEmitter,
|
||||
Listen,
|
||||
Method,
|
||||
Prop,
|
||||
PropDidChange,
|
||||
State
|
||||
} from '@stencil/core';
|
||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { BaseInputComponent, GestureDetail } from '../../index';
|
||||
import { clamp } from '../../utils/helpers';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-range',
|
||||
styleUrls: {
|
||||
// ios: 'toggle.ios.scss',
|
||||
md: 'range.md.scss'
|
||||
// wp: 'toggle.wp.scss'
|
||||
ios: 'range.ios.scss',
|
||||
md: 'range.md.scss',
|
||||
wp: 'range.wp.scss'
|
||||
},
|
||||
host: {
|
||||
theme: 'range'
|
||||
@ -39,7 +29,7 @@ export class Range implements BaseInputComponent {
|
||||
@State() _valB: number = 0;
|
||||
@State() _ratioA: number = 0;
|
||||
@State() _ratioB: number = 0;
|
||||
@State() _ticks: any[];
|
||||
@State() _ticks: any[] = [];
|
||||
@State() _activeB: boolean;
|
||||
@State() _rect: ClientRect;
|
||||
|
||||
@ -51,12 +41,13 @@ export class Range implements BaseInputComponent {
|
||||
@Event() ionStyle: EventEmitter;
|
||||
@Event() ionFocus: EventEmitter;
|
||||
@Event() ionBlur: EventEmitter;
|
||||
//
|
||||
// @Prop() color: string;
|
||||
// @Prop() mode: string;
|
||||
|
||||
@Prop() color: string;
|
||||
@Prop() mode: string;
|
||||
|
||||
@Prop({ state: true })
|
||||
value: any;
|
||||
|
||||
@Prop() disabled: boolean = false;
|
||||
@Prop() min: number = 0;
|
||||
@Prop() max: number = 100;
|
||||
@ -66,10 +57,6 @@ export class Range implements BaseInputComponent {
|
||||
@Prop() snaps: boolean = false;
|
||||
@Prop() debounce: number = 0;
|
||||
|
||||
private canStart() {
|
||||
return !this.disabled;
|
||||
}
|
||||
|
||||
fireBlur() {
|
||||
if (this.hasFocus) {
|
||||
this.hasFocus = false;
|
||||
@ -83,8 +70,15 @@ export class Range implements BaseInputComponent {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@PropDidChange('value')
|
||||
valueChanged(val: boolean) {
|
||||
this.ionChange.emit({ value: val });
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
ionViewWillLoad() {
|
||||
this._inputUpdated();
|
||||
this.inputUpdated();
|
||||
this.createTicks();
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@ -106,22 +100,21 @@ export class Range implements BaseInputComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_inputUpdated() {
|
||||
inputUpdated() {
|
||||
const val = this.value;
|
||||
if (this.dualKnobs) {
|
||||
this._valA = val.lower;
|
||||
this._valB = val.upper;
|
||||
this._ratioA = this._valueToRatio(val.lower);
|
||||
this._ratioB = this._valueToRatio(val.upper);
|
||||
this._ratioA = this.valueToRatio(val.lower);
|
||||
this._ratioB = this.valueToRatio(val.upper);
|
||||
} else {
|
||||
this._valA = val;
|
||||
this._ratioA = this._valueToRatio(val);
|
||||
this._ratioA = this.valueToRatio(val);
|
||||
}
|
||||
|
||||
this._updateBar();
|
||||
this.updateBar();
|
||||
}
|
||||
|
||||
_updateBar() {
|
||||
updateBar() {
|
||||
const ratioA = this._ratioA;
|
||||
const ratioB = this._ratioB;
|
||||
|
||||
@ -133,27 +126,25 @@ export class Range implements BaseInputComponent {
|
||||
this._barR = `${100 - ratioA * 100}%`;
|
||||
}
|
||||
|
||||
this._updateTicks();
|
||||
this.updateTicks();
|
||||
}
|
||||
|
||||
_creatTicks() {
|
||||
createTicks() {
|
||||
if (this.snaps) {
|
||||
this._ticks = [];
|
||||
for (let value = this.min; value <= this.max; value += this.steps) {
|
||||
let ratio = this._valueToRatio(value);
|
||||
let ratio = this.valueToRatio(value);
|
||||
this._ticks.push({
|
||||
ratio,
|
||||
left: `${ratio * 100}%`
|
||||
});
|
||||
}
|
||||
this._updateTicks();
|
||||
this.updateTicks();
|
||||
}
|
||||
}
|
||||
|
||||
_updateTicks() {
|
||||
updateTicks() {
|
||||
const ticks = this._ticks;
|
||||
const ratio = this.ratio;
|
||||
|
||||
if (this.snaps && ticks) {
|
||||
if (this.dualKnobs) {
|
||||
let upperRatio = this.ratioUpper();
|
||||
@ -169,19 +160,19 @@ export class Range implements BaseInputComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_valueToRatio(value: number) {
|
||||
valueToRatio(value: number) {
|
||||
value = Math.round((value - this.min) / this.steps) * this.steps;
|
||||
value = value / (this.max - this.min);
|
||||
return clamp(0, value, 1);
|
||||
}
|
||||
|
||||
_ratioToValue(ratio: number) {
|
||||
ratioToValue(ratio: number) {
|
||||
ratio = Math.round((this.max - this.min) * ratio);
|
||||
ratio = Math.round(ratio / this.steps) * this.steps + this.min;
|
||||
return clamp(this.min, ratio, this.max);
|
||||
}
|
||||
|
||||
_inputNormalize(val: any): any {
|
||||
inputNormalize(val: any): any {
|
||||
if (this.dualKnobs) {
|
||||
return val;
|
||||
} else {
|
||||
@ -190,21 +181,16 @@ export class Range implements BaseInputComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_update(
|
||||
current: { x?: number; y?: number },
|
||||
rect: ClientRect,
|
||||
isPressed: boolean
|
||||
) {
|
||||
update(current: { x?: number; y?: number }, rect: ClientRect, isPressed: boolean) {
|
||||
// figure out where the pointer is currently at
|
||||
// update the knob being interacted with
|
||||
let ratio = clamp(0, (current.x - rect.left) / rect.width, 1);
|
||||
let val = this._ratioToValue(ratio);
|
||||
let val = this.ratioToValue(ratio);
|
||||
|
||||
if (this.snaps) {
|
||||
// snaps the ratio to the current value
|
||||
ratio = this._valueToRatio(val);
|
||||
ratio = this.valueToRatio(val);
|
||||
}
|
||||
|
||||
// update which knob is pressed
|
||||
this._pressed = isPressed;
|
||||
let valChanged = false;
|
||||
@ -224,7 +210,8 @@ export class Range implements BaseInputComponent {
|
||||
valChanged = val === this._valA;
|
||||
this._valA = val;
|
||||
}
|
||||
this._updateBar();
|
||||
|
||||
this.updateBar();
|
||||
if (valChanged) {
|
||||
return false;
|
||||
}
|
||||
@ -271,72 +258,83 @@ export class Range implements BaseInputComponent {
|
||||
}
|
||||
|
||||
@Listen('ionIncrease, ionDecrease')
|
||||
_keyChg(ev: any) {
|
||||
keyChng(ev: RangeEvent) {
|
||||
const step = this.steps;
|
||||
// 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 (!!ev.detail.isIncrease) {
|
||||
this._valA += step;
|
||||
if (ev.detail.knob === 'knobB') {
|
||||
if (!!ev.detail.isIncrease) {
|
||||
this._valB += step;
|
||||
} else {
|
||||
this._valB -= step;
|
||||
}
|
||||
this._valB = clamp(this.min, this._valB, this.max);
|
||||
this._ratioB = this.valueToRatio(this._valB);
|
||||
} else {
|
||||
this._valA -= step;
|
||||
if (!!ev.detail.isIncrease) {
|
||||
this._valA += step;
|
||||
} else {
|
||||
this._valA -= step;
|
||||
}
|
||||
this._valA = clamp(this.min, this._valA, this.max);
|
||||
this._ratioA = this.valueToRatio(this._valA);
|
||||
}
|
||||
this._valA = clamp(this.min, this._valA, this.max);
|
||||
this._ratioA = this._valueToRatio(this._valA);
|
||||
// }
|
||||
this._updateBar();
|
||||
this.updateBar();
|
||||
}
|
||||
|
||||
onPress(detail: GestureDetail) {
|
||||
console.log('on press')
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
canStart() {
|
||||
const el = this.rangeEl.querySelector('.range-slider');
|
||||
this._rect = el.getBoundingClientRect();
|
||||
return !this.disabled;
|
||||
}
|
||||
|
||||
onDown(detail: GestureDetail) {
|
||||
if (this.disabled) return false;
|
||||
this.fireFocus();
|
||||
|
||||
const current = { x: detail.currentX, y: detail.currentY };
|
||||
const rect = (this._rect = this.rangeEl.getBoundingClientRect());
|
||||
const rect = this._rect;
|
||||
|
||||
// figure out which knob they started closer to
|
||||
const ratio = clamp(0, (current.x - rect.left) / rect.width, 1);
|
||||
|
||||
this._activeB =
|
||||
this.dualKnobs &&
|
||||
Math.abs(ratio - this._ratioA) > Math.abs(ratio - this._ratioB);
|
||||
|
||||
// update the active knob's position
|
||||
this._update(current, rect, true);
|
||||
this.update(current, rect, true);
|
||||
|
||||
// return true so the pointer events
|
||||
// know everything's still valid
|
||||
return true;
|
||||
}
|
||||
|
||||
onUp(detail: GestureDetail) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
// update the active knob's position
|
||||
this.update({ x: detail.currentX, y: detail.currentY }, this._rect, false);
|
||||
// trigger ionBlur event
|
||||
this.fireBlur();
|
||||
}
|
||||
|
||||
onDragMove(detail: GestureDetail) {
|
||||
console.log('drag start')
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const current = { x: detail.currentX, y: detail.currentY };
|
||||
// update the active knob's position
|
||||
this._update(current, this._rect, true);
|
||||
this.update(current, this._rect, true);
|
||||
}
|
||||
|
||||
onDragEnd(detail: GestureDetail) {
|
||||
console.log('drag end')
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
// update the active knob's position
|
||||
this._update({ x: detail.currentX, y: detail.currentY }, this._rect, false);
|
||||
|
||||
// trigger ionBlur event
|
||||
this.fireBlur();
|
||||
hostData() {
|
||||
return {
|
||||
class: {
|
||||
'range-disabled': this.disabled,
|
||||
'range-pressed': this._pressed,
|
||||
'range-has-pin': this.pin
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -346,17 +344,26 @@ export class Range implements BaseInputComponent {
|
||||
<ion-gesture
|
||||
props={{
|
||||
disableScroll: true,
|
||||
onPress: this.onPress.bind(this),
|
||||
canStart: this.canStart.bind(this),
|
||||
onDown: this.onDown.bind(this),
|
||||
onMove: this.onDragMove.bind(this),
|
||||
onEnd: this.onDragEnd.bind(this),
|
||||
onUp: this.onUp.bind(this),
|
||||
gestureName: 'range',
|
||||
gesturePriority: 30,
|
||||
type: 'pan,press',
|
||||
type: 'press,pan',
|
||||
direction: 'x',
|
||||
threshold: 0
|
||||
}}
|
||||
>
|
||||
<div class="range-slider">
|
||||
{this._ticks.map(t =>
|
||||
<div
|
||||
style={{ left: t.left }}
|
||||
role="presentation"
|
||||
class={{ 'range-tick': true, 'range-tick-active': t.active }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div class="range-bar" role="presentation" />
|
||||
<div
|
||||
class="range-bar range-bar-active"
|
||||
@ -368,6 +375,7 @@ export class Range implements BaseInputComponent {
|
||||
/>
|
||||
<ion-range-knob
|
||||
class="range-knob-handle"
|
||||
knob="knobA"
|
||||
pressed={this._pressedA}
|
||||
ratio={this._ratioA}
|
||||
val={this._valA}
|
||||
@ -375,9 +383,29 @@ export class Range implements BaseInputComponent {
|
||||
min={this.min}
|
||||
max={this.max}
|
||||
/>
|
||||
|
||||
{this.dualKnobs
|
||||
? <ion-range-knob
|
||||
class="range-knob-handle"
|
||||
knob="knobB"
|
||||
pressed={this._pressedB}
|
||||
ratio={this._ratioB}
|
||||
val={this._valB}
|
||||
pin={this.pin}
|
||||
min={this.min}
|
||||
max={this.max}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
</ion-gesture>,
|
||||
<slot name="range-end" />
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export interface RangeEvent {
|
||||
detail: {
|
||||
isIncrease: boolean,
|
||||
knob: string
|
||||
};
|
||||
}
|
||||
|
223
packages/core/src/components/range/range.wp.scss
Normal file
223
packages/core/src/components/range/range.wp.scss
Normal file
@ -0,0 +1,223 @@
|
||||
@import "../../themes/ionic.globals.wp";
|
||||
|
||||
// Windows Range
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Padding top/bottom of the range
|
||||
$range-wp-padding-vertical: 8px !default;
|
||||
|
||||
/// @prop - Padding start/end of the range
|
||||
$range-wp-padding-horizontal: 8px !default;
|
||||
|
||||
/// @prop - Height of the range slider
|
||||
$range-wp-slider-height: 42px !default;
|
||||
|
||||
/// @prop - Width of the area that will select the range knob
|
||||
$range-wp-hit-width: 42px !default;
|
||||
|
||||
/// @prop - Height of the area that will select the range knob
|
||||
$range-wp-hit-height: $range-wp-slider-height !default;
|
||||
|
||||
/// @prop - Height of the range bar
|
||||
$range-wp-bar-height: 2px !default;
|
||||
|
||||
/// @prop - Background of the range bar
|
||||
$range-wp-bar-background-color: #bdbdbd !default;
|
||||
|
||||
/// @prop - Background of the active range bar
|
||||
$range-wp-bar-active-background-color: color($colors-wp, primary) !default;
|
||||
|
||||
/// @prop - Width of the range knob
|
||||
$range-wp-knob-width: 8px !default;
|
||||
|
||||
/// @prop - Height of the range knob
|
||||
$range-wp-knob-height: $range-wp-knob-width * 3 !default;
|
||||
|
||||
/// @prop - Background of the range knob
|
||||
$range-wp-knob-background-color: $range-wp-bar-active-background-color !default;
|
||||
|
||||
/// @prop - Border radius of the range knob
|
||||
$range-wp-knob-border-radius: $range-wp-knob-width / 2 !default;
|
||||
|
||||
/// @prop - Width of the range tick
|
||||
$range-wp-tick-width: $range-wp-bar-height !default;
|
||||
|
||||
/// @prop - Height of the range tick
|
||||
$range-wp-tick-height: $range-wp-tick-width * 3 !default;
|
||||
|
||||
/// @prop - Border radius of the range tick
|
||||
$range-wp-tick-border-radius: $range-wp-knob-width / 2 !default;
|
||||
|
||||
/// @prop - Background of the range tick
|
||||
$range-wp-tick-background-color: $range-wp-bar-background-color !default;
|
||||
|
||||
/// @prop - Background of the active range tick
|
||||
$range-wp-tick-active-background-color: $range-wp-bar-active-background-color !default;
|
||||
|
||||
/// @prop - Background of the range pin
|
||||
$range-wp-pin-background-color: $range-wp-bar-active-background-color !default;
|
||||
|
||||
/// @prop - Color of the range pin
|
||||
$range-wp-pin-color: color-contrast($colors-wp, $range-wp-bar-active-background-color) !default;
|
||||
|
||||
/// @prop - Font size of the range pin
|
||||
$range-wp-pin-font-size: 12px !default;
|
||||
|
||||
// deprecated
|
||||
$range-wp-pin-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the range pin
|
||||
$range-wp-pin-padding-top: 8px !default;
|
||||
|
||||
/// @prop - Padding end of the range pin
|
||||
$range-wp-pin-padding-end: $range-wp-pin-padding-top !default;
|
||||
|
||||
/// @prop - Padding bottom of the range pin
|
||||
$range-wp-pin-padding-bottom: $range-wp-pin-padding-top !default;
|
||||
|
||||
/// @prop - Padding start of the range pin
|
||||
$range-wp-pin-padding-start: $range-wp-pin-padding-end !default;
|
||||
|
||||
|
||||
.range-wp {
|
||||
@include padding($range-wp-padding-vertical, $range-wp-padding-horizontal);
|
||||
}
|
||||
|
||||
.range-wp [range-left] {
|
||||
@include margin(0, 12px, 0, 0);
|
||||
}
|
||||
|
||||
.range-wp [range-right] {
|
||||
@include margin(0, 0, 0, 12px);
|
||||
}
|
||||
|
||||
.range-wp.range-has-pin {
|
||||
@include padding($range-wp-padding-vertical + $range-wp-pin-font-size + $range-wp-pin-padding-top, null, null, null);
|
||||
}
|
||||
|
||||
.range-wp .range-slider {
|
||||
height: $range-wp-slider-height;
|
||||
}
|
||||
|
||||
.range-wp .range-bar {
|
||||
@include position(($range-wp-slider-height / 2), null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 100%;
|
||||
height: $range-wp-bar-height;
|
||||
|
||||
background: $range-wp-bar-background-color;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-wp.range-pressed .range-bar-active {
|
||||
will-change: left, right;
|
||||
}
|
||||
|
||||
.range-wp.range-pressed .range-knob-handle {
|
||||
will-change: left;
|
||||
}
|
||||
|
||||
.range-wp .range-bar-active {
|
||||
bottom: 0;
|
||||
|
||||
width: auto;
|
||||
|
||||
background: $range-wp-bar-active-background-color;
|
||||
}
|
||||
|
||||
.range-wp .range-knob-handle {
|
||||
@include position(($range-wp-slider-height / 2), null, null, 0);
|
||||
@include margin(-($range-wp-hit-height / 2), null, null, -($range-wp-hit-width / 2));
|
||||
@include text-align(center);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: $range-wp-hit-width;
|
||||
height: $range-wp-hit-height;
|
||||
}
|
||||
|
||||
.range-wp .range-knob {
|
||||
@include position(($range-wp-hit-height / 2) - ($range-wp-knob-height / 2) + ($range-wp-bar-height / 2),
|
||||
null, null, ($range-wp-hit-width / 2) - ($range-wp-knob-width / 2));
|
||||
@include border-radius($range-wp-knob-border-radius);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: $range-wp-knob-width;
|
||||
height: $range-wp-knob-height;
|
||||
|
||||
background: $range-wp-knob-background-color;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-wp .range-tick {
|
||||
@include margin-horizontal(-($range-wp-tick-width / 2), null);
|
||||
@include border-radius($range-wp-tick-border-radius);
|
||||
|
||||
position: absolute;
|
||||
top: ($range-wp-hit-height / 2) - ($range-wp-tick-height / 2) + ($range-wp-bar-height / 2);
|
||||
|
||||
width: $range-wp-tick-width;
|
||||
height: $range-wp-tick-height;
|
||||
|
||||
background: $range-wp-tick-background-color;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.range-wp .range-tick-active {
|
||||
background: $range-wp-tick-active-background-color;
|
||||
}
|
||||
|
||||
.range-wp .range-pin {
|
||||
@include text-align(center);
|
||||
@include border-radius(50px);
|
||||
@include transform(translate3d(0, 28px, 0), scale(.01));
|
||||
|
||||
position: relative;
|
||||
top: -24px;
|
||||
display: inline-block;
|
||||
|
||||
min-width: 28px;
|
||||
|
||||
font-size: $range-wp-pin-font-size;
|
||||
|
||||
color: $range-wp-pin-color;
|
||||
|
||||
background: $range-wp-pin-background-color;
|
||||
|
||||
transition: transform 120ms ease;
|
||||
|
||||
@include deprecated-variable(padding, $range-wp-pin-padding) {
|
||||
@include padding($range-wp-pin-padding-top, $range-wp-pin-padding-end, $range-wp-pin-padding-bottom, $range-wp-pin-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.range-wp .range-knob-pressed .range-pin {
|
||||
@include transform(translate3d(0, 0, 0), scale(1));
|
||||
}
|
||||
|
||||
.range-wp.range-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
// Generate Windows Range Colors
|
||||
// --------------------------------------------------
|
||||
|
||||
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
|
||||
|
||||
.range-wp-#{$color-name} {
|
||||
.range-bar-active,
|
||||
.range-tick-active,
|
||||
.range-knob,
|
||||
.range-pin {
|
||||
background: $color-base;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,20 +17,86 @@
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Range color
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-range value="40" id="range">
|
||||
<ion-icon small name="sunny" slot="range-start"></ion-icon>
|
||||
<ion-icon name="sunny" slot="range-end"></ion-icon>
|
||||
</ion-range>
|
||||
<ion-range value="20"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="40" color="secondary"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="60" color="light"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="80" color="dark"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="100" color="danger"></ion-range>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<button onClick='elFunction()'> Helllo</button>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Mode
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-range value="50" mode="md"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="50" mode="ios"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="50" mode="wp"></ion-range>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Options
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-range pin="true"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range min="1000" max="2000" steps="100" snaps="true" id="range"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range dual-knobs="true" id="multiKnob"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range color="danger">
|
||||
<ion-icon small name="thermometer" slot="range-start"></ion-icon>
|
||||
<ion-icon name="thermometer" slot="range-end"></ion-icon>
|
||||
</ion-range>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
|
||||
<ion-button onclick="elTest()">Test</ion-button>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
function elFunction(){
|
||||
var range = document.querySelector('ion-range');
|
||||
console.log(range.ratio())
|
||||
var knob = document.getElementById('multiKnob')
|
||||
knob.value = {
|
||||
lower: 33,
|
||||
upper: 60
|
||||
}
|
||||
knob.addEventListener('ionFocus', function(ev) {
|
||||
console.log('focus', ev)
|
||||
})
|
||||
knob.addEventListener('ionBlur', function(ev) {
|
||||
console.log('blur', ev)
|
||||
})
|
||||
knob.addEventListener('ionChange', function(ev) {
|
||||
console.log('change', ev)
|
||||
})
|
||||
|
||||
function elTest() {
|
||||
var range = document.getElementById('range');
|
||||
range.disabled = !range.disabled;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user