mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 21:15:24 +08:00
feat(range): basic range component
This commit is contained in:
73
packages/core/src/components/range/range-knob.tsx
Normal file
73
packages/core/src/components/range/range-knob.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Component, Event, EventEmitter, Listen, Prop } from '@stencil/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: `ion-range-knob`
|
||||||
|
})
|
||||||
|
export class RangeKnob {
|
||||||
|
@Prop() pressed: boolean;
|
||||||
|
@Prop() pin: boolean;
|
||||||
|
@Prop() min: number;
|
||||||
|
@Prop() max: number;
|
||||||
|
@Prop() val: number;
|
||||||
|
@Prop() disabled: boolean;
|
||||||
|
@Prop() labelId: string;
|
||||||
|
@Prop() ratio: number;
|
||||||
|
|
||||||
|
@Event() ionIncrease: EventEmitter;
|
||||||
|
@Event() ionDecrease: EventEmitter;
|
||||||
|
|
||||||
|
@Listen('keydown')
|
||||||
|
handleKeyBoard(ev: KeyboardEvent) {
|
||||||
|
const keyCode = ev.keyCode;
|
||||||
|
if (keyCode === KEY_LEFT || keyCode === KEY_DOWN) {
|
||||||
|
this.ionDecrease.emit({isIncrease: false});
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
} else if (keyCode === KEY_RIGHT || keyCode === KEY_UP) {
|
||||||
|
this.ionIncrease.emit({isIncrease: true});
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftPos(val: number) {
|
||||||
|
return `${val * 100}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
hostData() {
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
'range-knob-pressed': this.pressed,
|
||||||
|
'range-knob-min': this.val === this.min || this.val === undefined,
|
||||||
|
'range-knob-max': this.val === this.max
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
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}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.pin) {
|
||||||
|
return [
|
||||||
|
<div class="range-pin" role="presentation">{this.val}</div>,
|
||||||
|
<div class="range-knob" role="presentation" />
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return <div class="range-knob" role="presentation" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KEY_LEFT = 37;
|
||||||
|
export const KEY_UP = 38;
|
||||||
|
export const KEY_RIGHT = 39;
|
||||||
|
export const KEY_DOWN = 40;
|
279
packages/core/src/components/range/range.md.scss
Normal file
279
packages/core/src/components/range/range.md.scss
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
@import "../../themes/ionic.globals.md";
|
||||||
|
@import "./range";
|
||||||
|
// Material Design Range
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
/// @prop - Padding top/bottom of the range
|
||||||
|
$range-md-padding-vertical: 8px !default;
|
||||||
|
|
||||||
|
/// @prop - Padding start/end of the range
|
||||||
|
$range-md-padding-horizontal: 8px !default;
|
||||||
|
|
||||||
|
/// @prop - Height of the range slider
|
||||||
|
$range-md-slider-height: 42px !default;
|
||||||
|
|
||||||
|
/// @prop - Width of the area that will select the range knob
|
||||||
|
$range-md-hit-width: 42px !default;
|
||||||
|
|
||||||
|
/// @prop - Height of the area that will select the range knob
|
||||||
|
$range-md-hit-height: $range-md-slider-height !default;
|
||||||
|
|
||||||
|
/// @prop - Height of the range bar
|
||||||
|
$range-md-bar-height: 2px !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range bar
|
||||||
|
$range-md-bar-background-color: #bdbdbd !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the active range bar
|
||||||
|
$range-md-bar-active-background-color: color($colors-md, primary) !default;
|
||||||
|
|
||||||
|
/// @prop - Width of the range knob
|
||||||
|
$range-md-knob-width: 18px !default;
|
||||||
|
|
||||||
|
/// @prop - Height of the range knob
|
||||||
|
$range-md-knob-height: $range-md-knob-width !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range knob
|
||||||
|
$range-md-knob-background-color: $range-md-bar-active-background-color !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range knob when the value is the minimum
|
||||||
|
$range-md-knob-min-background-color: $background-md-color !default;
|
||||||
|
|
||||||
|
/// @prop - Border of the range knob when the value is the minimum
|
||||||
|
$range-md-knob-min-border: 2px solid $range-md-bar-background-color !default;
|
||||||
|
|
||||||
|
/// @prop - Width of the range tick
|
||||||
|
$range-md-tick-width: 2px !default;
|
||||||
|
|
||||||
|
/// @prop - Height of the range tick
|
||||||
|
$range-md-tick-height: $range-md-tick-width !default;
|
||||||
|
|
||||||
|
/// @prop - Border radius of the range tick
|
||||||
|
$range-md-tick-border-radius: 50% !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range tick
|
||||||
|
$range-md-tick-background-color: #000 !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the active range tick
|
||||||
|
$range-md-tick-active-background-color: $range-md-tick-background-color !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range pin
|
||||||
|
$range-md-pin-background-color: $range-md-bar-active-background-color !default;
|
||||||
|
|
||||||
|
/// @prop - Color of the range pin
|
||||||
|
$range-md-pin-color: color-contrast($colors-md, $range-md-bar-active-background-color) !default;
|
||||||
|
|
||||||
|
/// @prop - Font size of the range pin
|
||||||
|
$range-md-pin-font-size: 12px !default;
|
||||||
|
|
||||||
|
/// @prop - Padding top/bottom of the range pin
|
||||||
|
$range-md-pin-padding-vertical: 8px !default;
|
||||||
|
|
||||||
|
/// @prop - Padding start/end of the range pin
|
||||||
|
$range-md-pin-padding-horizontal: 0 !default;
|
||||||
|
|
||||||
|
/// @prop - Background of the range pin when the value is the minimum
|
||||||
|
$range-md-pin-min-background-color: $range-md-bar-background-color !default;
|
||||||
|
|
||||||
|
|
||||||
|
.range-md {
|
||||||
|
@include padding($range-md-padding-vertical, $range-md-padding-horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md [slot="range-start"] {
|
||||||
|
@include margin(0, 12px, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md [slot="range-end"] {
|
||||||
|
@include margin(0, 0, 0, 12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md.range-has-pin {
|
||||||
|
@include padding($range-md-padding-vertical + $range-md-pin-font-size + $range-md-pin-padding-vertical, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-slider {
|
||||||
|
height: $range-md-slider-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-bar {
|
||||||
|
@include position(($range-md-slider-height / 2), null, null, 0);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: $range-md-bar-height;
|
||||||
|
|
||||||
|
background: $range-md-bar-background-color;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md.range-pressed .range-bar-active {
|
||||||
|
will-change: left, right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md.range-pressed .range-knob-handle {
|
||||||
|
will-change: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-bar-active {
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
background: $range-md-bar-active-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-knob-handle {
|
||||||
|
@include position(($range-md-slider-height / 2), null, null, 0);
|
||||||
|
@include margin(-($range-md-hit-height / 2), null, null, -($range-md-hit-width / 2));
|
||||||
|
@include text-align(center);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: $range-md-hit-width;
|
||||||
|
height: $range-md-hit-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-knob {
|
||||||
|
@include position(($range-md-hit-height / 2) - ($range-md-knob-height / 2) + ($range-md-bar-height / 2),
|
||||||
|
null, null, ($range-md-hit-width / 2) - ($range-md-knob-width / 2));
|
||||||
|
@include border-radius(50%);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
width: $range-md-knob-width;
|
||||||
|
height: $range-md-knob-height;
|
||||||
|
|
||||||
|
background: $range-md-knob-background-color;
|
||||||
|
|
||||||
|
transform: scale(.67);
|
||||||
|
transition-duration: 120ms;
|
||||||
|
transition-property: transform, background-color, border;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-tick {
|
||||||
|
@include margin-horizontal(-($range-md-tick-width / 2), null);
|
||||||
|
@include border-radius($range-md-tick-border-radius);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: ($range-md-hit-height / 2) - ($range-md-tick-height / 2) + ($range-md-bar-height / 2);
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
width: $range-md-tick-width;
|
||||||
|
height: $range-md-tick-height;
|
||||||
|
|
||||||
|
background: $range-md-tick-background-color;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-tick-active {
|
||||||
|
background: $range-md-tick-active-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-pin {
|
||||||
|
@include padding($range-md-pin-padding-vertical, $range-md-pin-padding-horizontal);
|
||||||
|
@include text-align(center);
|
||||||
|
@include border-radius(50%);
|
||||||
|
@include transform(translate3d(0, 28px, 0), scale(.01));
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: -20px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
font-size: $range-md-pin-font-size;
|
||||||
|
|
||||||
|
color: $range-md-pin-color;
|
||||||
|
|
||||||
|
background: $range-md-pin-background-color;
|
||||||
|
|
||||||
|
transition: transform 120ms ease, background-color 120ms ease;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@include position(3px, null, null, 50%);
|
||||||
|
@include border-radius(50%, 50%, 50%, 0);
|
||||||
|
@include margin-horizontal(-13px, null);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
|
||||||
|
background: $range-md-pin-background-color;
|
||||||
|
|
||||||
|
content: "";
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transition: background-color 120ms ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md .range-knob-pressed .range-pin {
|
||||||
|
@include transform(translate3d(0, 0, 0), scale(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-md:not(.range-has-pin) .range-knob-pressed .range-knob {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin md-range-min() {
|
||||||
|
.range-md .range-knob-min.range-knob-min {
|
||||||
|
.range-knob {
|
||||||
|
border: $range-md-knob-min-border;
|
||||||
|
background: $range-md-knob-min-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-pin,
|
||||||
|
.range-pin::before {
|
||||||
|
color: color-contrast($colors-md, $range-md-pin-min-background-color);
|
||||||
|
background: $range-md-pin-min-background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include md-range-min();
|
||||||
|
|
||||||
|
.range-md.range-disabled {
|
||||||
|
.range-bar-active {
|
||||||
|
background-color: $range-md-bar-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-knob {
|
||||||
|
outline: 5px solid #fff;
|
||||||
|
background-color: $range-md-bar-background-color;
|
||||||
|
transform: scale(.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Generate Material Design Range Colors
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
|
||||||
|
|
||||||
|
.range-md-#{$color-name} {
|
||||||
|
@include md-range-min();
|
||||||
|
|
||||||
|
.range-bar-active,
|
||||||
|
.range-knob,
|
||||||
|
.range-pin,
|
||||||
|
.range-pin::before {
|
||||||
|
background: $color-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
packages/core/src/components/range/range.scss
Normal file
56
packages/core/src/components/range/range.scss
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
@import "../../themes/ionic.globals";
|
||||||
|
|
||||||
|
// Range
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.item .item-inner {
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item .input-wrapper {
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item ion-range {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item ion-range ion-label {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-range {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
|
||||||
|
ion-label {
|
||||||
|
flex: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
min-height: 2.4rem;
|
||||||
|
|
||||||
|
font-size: 2.4rem;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-gesture,
|
||||||
|
.range-slider {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
383
packages/core/src/components/range/range.tsx
Normal file
383
packages/core/src/components/range/range.tsx
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
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'
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
theme: 'range'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class Range implements BaseInputComponent {
|
||||||
|
activated: boolean = false;
|
||||||
|
hasFocus: boolean = false;
|
||||||
|
id: string;
|
||||||
|
labelId: string;
|
||||||
|
startX: number;
|
||||||
|
styleTmr: any;
|
||||||
|
|
||||||
|
@Element() rangeEl: HTMLElement;
|
||||||
|
|
||||||
|
@State() _barL: string;
|
||||||
|
@State() _barR: string;
|
||||||
|
@State() _valA: number = 0;
|
||||||
|
@State() _valB: number = 0;
|
||||||
|
@State() _ratioA: number = 0;
|
||||||
|
@State() _ratioB: number = 0;
|
||||||
|
@State() _ticks: any[];
|
||||||
|
@State() _activeB: boolean;
|
||||||
|
@State() _rect: ClientRect;
|
||||||
|
|
||||||
|
@State() _pressed: boolean;
|
||||||
|
@State() _pressedA: boolean;
|
||||||
|
@State() _pressedB: boolean;
|
||||||
|
|
||||||
|
@Event() ionChange: EventEmitter;
|
||||||
|
@Event() ionStyle: EventEmitter;
|
||||||
|
@Event() ionFocus: EventEmitter;
|
||||||
|
@Event() ionBlur: EventEmitter;
|
||||||
|
//
|
||||||
|
// @Prop() color: string;
|
||||||
|
// @Prop() mode: string;
|
||||||
|
|
||||||
|
@Prop({ state: true })
|
||||||
|
value: any;
|
||||||
|
@Prop() disabled: boolean = false;
|
||||||
|
@Prop() min: number = 0;
|
||||||
|
@Prop() max: number = 100;
|
||||||
|
@Prop() steps: number = 1;
|
||||||
|
@Prop() dualKnobs: boolean = false;
|
||||||
|
@Prop() pin: boolean = false;
|
||||||
|
@Prop() snaps: boolean = false;
|
||||||
|
@Prop() debounce: number = 0;
|
||||||
|
|
||||||
|
private canStart() {
|
||||||
|
return !this.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireBlur() {
|
||||||
|
if (this.hasFocus) {
|
||||||
|
this.hasFocus = false;
|
||||||
|
this.ionBlur.emit();
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PropDidChange('disabled')
|
||||||
|
disabledChanged() {
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillLoad() {
|
||||||
|
this._inputUpdated();
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitStyle() {
|
||||||
|
clearTimeout(this.styleTmr);
|
||||||
|
|
||||||
|
this.styleTmr = setTimeout(() => {
|
||||||
|
this.ionStyle.emit({
|
||||||
|
'range-disabled': this.disabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fireFocus() {
|
||||||
|
if (!this.hasFocus) {
|
||||||
|
this.hasFocus = true;
|
||||||
|
this.ionFocus.emit();
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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);
|
||||||
|
} else {
|
||||||
|
this._valA = val;
|
||||||
|
this._ratioA = this._valueToRatio(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateBar() {
|
||||||
|
const ratioA = this._ratioA;
|
||||||
|
const ratioB = this._ratioB;
|
||||||
|
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
_creatTicks() {
|
||||||
|
if (this.snaps) {
|
||||||
|
this._ticks = [];
|
||||||
|
for (let value = this.min; value <= this.max; value += this.steps) {
|
||||||
|
let ratio = this._valueToRatio(value);
|
||||||
|
this._ticks.push({
|
||||||
|
ratio,
|
||||||
|
left: `${ratio * 100}%`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._updateTicks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTicks() {
|
||||||
|
const ticks = this._ticks;
|
||||||
|
const ratio = this.ratio;
|
||||||
|
|
||||||
|
if (this.snaps && ticks) {
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
let upperRatio = this.ratioUpper();
|
||||||
|
|
||||||
|
ticks.forEach(t => {
|
||||||
|
t.active = t.ratio >= ratio && t.ratio <= upperRatio;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ticks.forEach(t => {
|
||||||
|
t.active = t.ratio <= ratio;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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) {
|
||||||
|
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 {
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
val = parseFloat(val);
|
||||||
|
return isNaN(val) ? undefined : val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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);
|
||||||
|
|
||||||
|
if (this.snaps) {
|
||||||
|
// snaps the ratio to the current value
|
||||||
|
ratio = this._valueToRatio(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update which knob is pressed
|
||||||
|
this._pressed = isPressed;
|
||||||
|
let 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
|
||||||
|
let value;
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
// dual knobs have an lower and upper value
|
||||||
|
value = {
|
||||||
|
lower: Math.min(this._valA, this._valB),
|
||||||
|
upper: Math.max(this._valA, this._valB)
|
||||||
|
};
|
||||||
|
|
||||||
|
console.debug(
|
||||||
|
`range, updateKnob: ${ratio}, lower: ${this.value.lower}, upper: ${this
|
||||||
|
.value.upper}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// single knob only has one value
|
||||||
|
value = this._valA;
|
||||||
|
console.debug(`range, updateKnob: ${ratio}, value: ${this.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update input value
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
ratio(): number {
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
return Math.min(this._ratioA, this._ratioB);
|
||||||
|
}
|
||||||
|
return this._ratioA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
ratioUpper() {
|
||||||
|
if (this.dualKnobs) {
|
||||||
|
return Math.max(this._ratioA, this._ratioB);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('ionIncrease, ionDecrease')
|
||||||
|
_keyChg(ev: any) {
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
this._valA -= step;
|
||||||
|
}
|
||||||
|
this._valA = clamp(this.min, this._valA, this.max);
|
||||||
|
this._ratioA = this._valueToRatio(this._valA);
|
||||||
|
// }
|
||||||
|
this._updateBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPress(detail: GestureDetail) {
|
||||||
|
console.log('on press')
|
||||||
|
if (this.disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.fireFocus();
|
||||||
|
|
||||||
|
const current = { x: detail.currentX, y: detail.currentY };
|
||||||
|
const rect = (this._rect = this.rangeEl.getBoundingClientRect());
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// return true so the pointer events
|
||||||
|
// know everything's still valid
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return [
|
||||||
|
<slot name="range-start" />,
|
||||||
|
|
||||||
|
<ion-gesture
|
||||||
|
props={{
|
||||||
|
disableScroll: true,
|
||||||
|
onPress: this.onPress.bind(this),
|
||||||
|
onMove: this.onDragMove.bind(this),
|
||||||
|
onEnd: this.onDragEnd.bind(this),
|
||||||
|
gestureName: 'range',
|
||||||
|
gesturePriority: 30,
|
||||||
|
type: 'pan,press',
|
||||||
|
direction: 'x',
|
||||||
|
threshold: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="range-slider">
|
||||||
|
<div class="range-bar" role="presentation" />
|
||||||
|
<div
|
||||||
|
class="range-bar range-bar-active"
|
||||||
|
style={{
|
||||||
|
left: this._barL,
|
||||||
|
right: this._barR
|
||||||
|
}}
|
||||||
|
role="presentation"
|
||||||
|
/>
|
||||||
|
<ion-range-knob
|
||||||
|
class="range-knob-handle"
|
||||||
|
pressed={this._pressedA}
|
||||||
|
ratio={this._ratioA}
|
||||||
|
val={this._valA}
|
||||||
|
pin={this.pin}
|
||||||
|
min={this.min}
|
||||||
|
max={this.max}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ion-gesture>,
|
||||||
|
<slot name="range-end" />
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
38
packages/core/src/components/range/test/basic.html
Normal file
38
packages/core/src/components/range/test/basic.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ionic Range</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<script src="/dist/ionic.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Range</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
<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-item>
|
||||||
|
</ion-list>
|
||||||
|
<button onClick='elFunction()'> Helllo</button>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
<script>
|
||||||
|
function elFunction(){
|
||||||
|
var range = document.querySelector('ion-range');
|
||||||
|
console.log(range.ratio())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,5 +1,9 @@
|
|||||||
import { StencilElement } from '..';
|
import { StencilElement } from '..';
|
||||||
|
|
||||||
|
export function clamp(min: number, n: number, max: number) {
|
||||||
|
return Math.max(min, Math.min(n, max));
|
||||||
|
}
|
||||||
|
|
||||||
export function isDef(v: any): boolean { return v !== undefined && v !== null; }
|
export function isDef(v: any): boolean { return v !== undefined && v !== null; }
|
||||||
|
|
||||||
export function isUndef(v: any): boolean { return v === undefined || v === null; }
|
export function isUndef(v: any): boolean { return v === undefined || v === null; }
|
||||||
|
@ -28,6 +28,7 @@ exports.config = {
|
|||||||
{ components: ['ion-select', 'ion-select-option', 'ion-select-popover'] },
|
{ components: ['ion-select', 'ion-select-option', 'ion-select-popover'] },
|
||||||
{ components: ['ion-slides', 'ion-slide'] },
|
{ components: ['ion-slides', 'ion-slide'] },
|
||||||
{ components: ['ion-spinner'] },
|
{ components: ['ion-spinner'] },
|
||||||
|
{ components: ['ion-range', 'ion-range-knob']},
|
||||||
{ components: ['ion-tabs', 'ion-tab', 'ion-tab-bar', 'ion-tab-button', 'ion-tab-highlight'] },
|
{ components: ['ion-tabs', 'ion-tab', 'ion-tab-bar', 'ion-tab-button', 'ion-tab-highlight'] },
|
||||||
{ components: ['ion-toggle'] },
|
{ components: ['ion-toggle'] },
|
||||||
{ components: ['ion-nav', 'ion-nav-controller', 'stencil-ion-nav-delegate','page-one', 'page-two', 'page-three'] },
|
{ components: ['ion-nav', 'ion-nav-controller', 'stencil-ion-nav-delegate','page-one', 'page-two', 'page-three'] },
|
||||||
|
Reference in New Issue
Block a user