mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 11:17:19 +08:00
refactor(range): move logic to stateless
- float steps are possible - improved performance - state is in a single place - fix when value changes dynamically - fix jumpy gesture
This commit is contained in:
17
core/src/components.d.ts
vendored
17
core/src/components.d.ts
vendored
@ -42,6 +42,7 @@ import {
|
||||
GestureConfig,
|
||||
GestureDetail,
|
||||
InputChangeEvent,
|
||||
Knob,
|
||||
LoadingOptions,
|
||||
Menu,
|
||||
MenuChangeEventDetail,
|
||||
@ -4494,14 +4495,14 @@ declare global {
|
||||
namespace StencilComponents {
|
||||
interface IonRangeKnob {
|
||||
'disabled': boolean;
|
||||
'knob': string;
|
||||
'knob': Knob;
|
||||
'labelId': string;
|
||||
'max': number;
|
||||
'min': number;
|
||||
'pin': boolean;
|
||||
'pressed': boolean;
|
||||
'ratio': number;
|
||||
'val': number;
|
||||
'value': number;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4525,7 +4526,7 @@ declare global {
|
||||
namespace JSXElements {
|
||||
export interface IonRangeKnobAttributes extends HTMLAttributes {
|
||||
'disabled'?: boolean;
|
||||
'knob'?: string;
|
||||
'knob'?: Knob;
|
||||
'labelId'?: string;
|
||||
'max'?: number;
|
||||
'min'?: number;
|
||||
@ -4534,7 +4535,7 @@ declare global {
|
||||
'pin'?: boolean;
|
||||
'pressed'?: boolean;
|
||||
'ratio'?: number;
|
||||
'val'?: number;
|
||||
'value'?: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4580,14 +4581,6 @@ declare global {
|
||||
* If true, a pin with integer value is shown when the knob is pressed. Defaults to `false`.
|
||||
*/
|
||||
'pin': boolean;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'ratio': () => number;
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
'ratioUpper': () => number | null;
|
||||
/**
|
||||
* If true, the knob snaps to tick marks evenly spaced based on the step property value. Defaults to `false`.
|
||||
*/
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { Component, Event, EventEmitter, Listen, Prop } from '@stencil/core';
|
||||
import { Knob } from '../../interface';
|
||||
|
||||
@Component({
|
||||
tag: `ion-range-knob`
|
||||
})
|
||||
export class RangeKnob {
|
||||
|
||||
@Prop() pressed = false;
|
||||
@Prop() pin = false;
|
||||
@Prop() pressed!: boolean;
|
||||
@Prop() pin!: boolean;
|
||||
@Prop() min!: number;
|
||||
@Prop() max!: number;
|
||||
@Prop() val!: number;
|
||||
@Prop() disabled = false;
|
||||
@Prop() labelId!: string;
|
||||
@Prop() knob!: string;
|
||||
@Prop() value!: number;
|
||||
@Prop() ratio!: number;
|
||||
@Prop() disabled!: boolean;
|
||||
@Prop() labelId!: string;
|
||||
@Prop() knob!: Knob;
|
||||
|
||||
@Event() ionIncrease!: EventEmitter;
|
||||
@Event() ionDecrease!: EventEmitter;
|
||||
@ -25,6 +26,7 @@ export class RangeKnob {
|
||||
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, knob: this.knob});
|
||||
ev.preventDefault();
|
||||
@ -32,34 +34,33 @@ export class RangeKnob {
|
||||
}
|
||||
}
|
||||
|
||||
leftPos(val: number) {
|
||||
return `${val * 100}%`;
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const {value, min, max} = this;
|
||||
const pos = this.ratio * 100;
|
||||
return {
|
||||
class: {
|
||||
'range-knob-handle': true,
|
||||
'range-knob-pressed': this.pressed,
|
||||
'range-knob-min': this.val === this.min || this.val === undefined,
|
||||
'range-knob-max': this.val === this.max
|
||||
'range-knob-min': value === min,
|
||||
'range-knob-max': value === max
|
||||
},
|
||||
style: {
|
||||
'left': this.leftPos(this.ratio)
|
||||
'left': `${pos}%`
|
||||
},
|
||||
'role': 'slider',
|
||||
'tabindex': this.disabled ? -1 : 0,
|
||||
'aria-valuemin': this.min,
|
||||
'aria-valuemax': this.max,
|
||||
'aria-valuemin': min,
|
||||
'aria-valuemax': max,
|
||||
'aria-disabled': this.disabled,
|
||||
'aria-labelledby': this.labelId,
|
||||
'aria-valuenow': this.val
|
||||
'aria-valuenow': value
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.pin) {
|
||||
return [
|
||||
<div class="range-pin" role="presentation">{this.val}</div>,
|
||||
<div class="range-pin" role="presentation">{Math.round(this.value)}</div>,
|
||||
<div class="range-knob" role="presentation" />
|
||||
];
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ boolean
|
||||
|
||||
#### knob
|
||||
|
||||
string
|
||||
number
|
||||
|
||||
|
||||
#### labelId
|
||||
@ -47,7 +47,7 @@ boolean
|
||||
number
|
||||
|
||||
|
||||
#### val
|
||||
#### value
|
||||
|
||||
number
|
||||
|
||||
@ -61,7 +61,7 @@ boolean
|
||||
|
||||
#### knob
|
||||
|
||||
string
|
||||
number
|
||||
|
||||
|
||||
#### label-id
|
||||
@ -94,7 +94,7 @@ boolean
|
||||
number
|
||||
|
||||
|
||||
#### val
|
||||
#### value
|
||||
|
||||
number
|
||||
|
||||
|
13
core/src/components/range/range-interface.ts
Normal file
13
core/src/components/range/range-interface.ts
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
export const enum Knob {
|
||||
None,
|
||||
A,
|
||||
B
|
||||
}
|
||||
|
||||
export type RangeValue = number | {lower: number, upper: number};
|
||||
|
||||
export interface RangeEventDetail extends Event {
|
||||
isIncrease: boolean;
|
||||
knob: Knob;
|
||||
}
|
@ -1,12 +1,7 @@
|
||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State, Watch } from '@stencil/core';
|
||||
import { Component, Element, Event, EventEmitter, Listen, Prop, State, Watch } from '@stencil/core';
|
||||
import { BaseInput, GestureDetail, Mode, RangeInputChangeEvent, StyleEvent } from '../../interface';
|
||||
import { clamp, debounceEvent, deferEvent } from '../../utils/helpers';
|
||||
|
||||
export interface Tick {
|
||||
ratio: number | (() => number);
|
||||
left: string;
|
||||
active?: boolean;
|
||||
}
|
||||
import { Knob, RangeEventDetail, RangeValue } from './range-interface';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-range',
|
||||
@ -20,24 +15,15 @@ export interface Tick {
|
||||
})
|
||||
export class Range implements BaseInput {
|
||||
|
||||
hasFocus = false;
|
||||
private noUpdate = false;
|
||||
private rect!: ClientRect;
|
||||
private hasFocus = false;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@State() barL!: string;
|
||||
@State() barR!: string;
|
||||
@State() valA = 0;
|
||||
@State() valB = 0;
|
||||
@State() ratioA = 0;
|
||||
@State() ratioB = 0;
|
||||
@State() ticks: Tick[] = [];
|
||||
@State() activeB = false;
|
||||
@State() rect!: ClientRect;
|
||||
|
||||
@State() pressed = false;
|
||||
@State() pressedA = false;
|
||||
@State() pressedB = false;
|
||||
@Element() el!: HTMLStencilElement;
|
||||
|
||||
@State() private ratioA = 0;
|
||||
@State() private ratioB = 0;
|
||||
@State() private pressedKnob = Knob.None;
|
||||
|
||||
/**
|
||||
* The color to use from your Sass `$colors` map.
|
||||
@ -74,16 +60,16 @@ export class Range implements BaseInput {
|
||||
*/
|
||||
@Prop() dualKnobs = false;
|
||||
|
||||
/**
|
||||
* Maximum integer value of the range. Defaults to `100`.
|
||||
*/
|
||||
@Prop() max = 100;
|
||||
|
||||
/**
|
||||
* Minimum integer value of the range. Defaults to `0`.
|
||||
*/
|
||||
@Prop() min = 0;
|
||||
|
||||
/**
|
||||
* Maximum integer value of the range. Defaults to `100`.
|
||||
*/
|
||||
@Prop() max = 100;
|
||||
|
||||
/**
|
||||
* If true, a pin with integer value is shown when the knob
|
||||
* is pressed. Defaults to `false`.
|
||||
@ -113,11 +99,12 @@ export class Range implements BaseInput {
|
||||
/**
|
||||
* the value of the range.
|
||||
*/
|
||||
@Prop({ mutable: true }) value: any;
|
||||
@Prop({ mutable: true }) value: any = 0;
|
||||
@Watch('value')
|
||||
protected valueChanged(value: any) {
|
||||
this.inputUpdated();
|
||||
this.emitStyle();
|
||||
protected valueChanged(value: RangeValue) {
|
||||
if (!this.noUpdate) {
|
||||
this.updateRatio();
|
||||
}
|
||||
this.ionChange.emit({value});
|
||||
}
|
||||
|
||||
@ -146,12 +133,45 @@ export class Range implements BaseInput {
|
||||
componentWillLoad() {
|
||||
this.ionStyle = deferEvent(this.ionStyle);
|
||||
|
||||
this.inputUpdated();
|
||||
this.createTicks();
|
||||
this.updateRatio();
|
||||
this.debounceChanged();
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@Listen('ionIncrease')
|
||||
@Listen('ionDecrease')
|
||||
keyChng(ev: CustomEvent<RangeEventDetail>) {
|
||||
let step = this.step;
|
||||
step = step > 0 ? step : 1;
|
||||
step = step / (this.max - this.min);
|
||||
if (!ev.detail.isIncrease) {
|
||||
step *= -1;
|
||||
}
|
||||
if (ev.detail.knob === Knob.A) {
|
||||
this.ratioA += step;
|
||||
} else {
|
||||
this.ratioB += step;
|
||||
}
|
||||
}
|
||||
|
||||
private getValue(): RangeValue {
|
||||
const value = this.value || 0;
|
||||
if (this.dualKnobs) {
|
||||
if (typeof value === 'object') {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
lower: 0,
|
||||
upper: value,
|
||||
};
|
||||
} else {
|
||||
if (typeof value === 'object') {
|
||||
return value.upper;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'range-disabled': this.disabled
|
||||
@ -174,244 +194,130 @@ export class Range implements BaseInput {
|
||||
}
|
||||
}
|
||||
|
||||
private 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();
|
||||
private onDragStart(detail: GestureDetail) {
|
||||
this.fireFocus();
|
||||
|
||||
const el = this.el.querySelector('.range-slider')!;
|
||||
const rect = this.rect = el.getBoundingClientRect() as any;
|
||||
const currentX = detail.currentX;
|
||||
|
||||
// figure out which knob they started closer to
|
||||
const ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
|
||||
this.pressedKnob = (!this.dualKnobs || (Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio)))
|
||||
? Knob.A
|
||||
: Knob.B;
|
||||
|
||||
// update the active knob's position
|
||||
this.update(currentX);
|
||||
}
|
||||
|
||||
private 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}%`;
|
||||
private onDragMove(detail: GestureDetail) {
|
||||
this.update(detail.currentX);
|
||||
}
|
||||
|
||||
this.updateTicks();
|
||||
private onDragEnd(detail: GestureDetail) {
|
||||
this.update(detail.currentX);
|
||||
this.pressedKnob = Knob.None;
|
||||
this.fireBlur();
|
||||
}
|
||||
|
||||
private createTicks() {
|
||||
if (this.snaps) {
|
||||
for (let value = this.min; value <= this.max; value += this.step) {
|
||||
const ratio = this.valueToRatio(value);
|
||||
this.ticks.push({
|
||||
ratio,
|
||||
left: `${ratio * 100}%`
|
||||
});
|
||||
}
|
||||
this.updateTicks();
|
||||
}
|
||||
}
|
||||
|
||||
private updateTicks() {
|
||||
const ticks = this.ticks;
|
||||
const ratio = this.ratio;
|
||||
if (this.snaps && ticks) {
|
||||
if (this.dualKnobs) {
|
||||
const upperRatio = this.ratioUpper()!;
|
||||
|
||||
ticks.forEach(t => {
|
||||
t.active = t.ratio >= ratio && t.ratio <= upperRatio;
|
||||
});
|
||||
} else {
|
||||
ticks.forEach(t => {
|
||||
t.active = t.ratio <= ratio;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private valueToRatio(value: number) {
|
||||
value = Math.round((value - this.min) / this.step) * this.step;
|
||||
value = value / (this.max - this.min);
|
||||
return clamp(0, value, 1);
|
||||
}
|
||||
|
||||
private ratioToValue(ratio: number) {
|
||||
ratio = Math.round((this.max - this.min) * ratio);
|
||||
ratio = Math.round(ratio / this.step) * this.step + this.min;
|
||||
return clamp(this.min, ratio, this.max);
|
||||
}
|
||||
|
||||
// private inputNormalize(val: any): any {
|
||||
// if (this.dualKnobs) {
|
||||
// return val;
|
||||
// } else {
|
||||
// val = parseFloat(val);
|
||||
// return isNaN(val) ? undefined : val;
|
||||
// }
|
||||
// }
|
||||
|
||||
private update(current: { x: number; y: number }, rect: ClientRect, isPressed: boolean) {
|
||||
private update(currentX: number) {
|
||||
// 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);
|
||||
const val = this.ratioToValue(ratio);
|
||||
|
||||
const rect = this.rect;
|
||||
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
|
||||
if (this.snaps) {
|
||||
// snaps the ratio to the current value
|
||||
ratio = this.valueToRatio(val);
|
||||
const value = ratioToValue(ratio, this.min, this.max, this.step);
|
||||
ratio = valueToRatio(value, this.min, this.max);
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (this.pressedKnob === Knob.A) {
|
||||
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)
|
||||
};
|
||||
|
||||
} else {
|
||||
// single knob only has one value
|
||||
value = this.valA;
|
||||
this.ratioB = ratio;
|
||||
}
|
||||
|
||||
// Update input value
|
||||
this.value = value;
|
||||
|
||||
return true;
|
||||
this.updateValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Method()
|
||||
ratio(): number {
|
||||
private get valA() {
|
||||
return ratioToValue(this.ratioA, this.min, this.max, this.step);
|
||||
}
|
||||
|
||||
private get valB() {
|
||||
return ratioToValue(this.ratioB, this.min, this.max, this.step);
|
||||
}
|
||||
|
||||
private get ratioLower() {
|
||||
if (this.dualKnobs) {
|
||||
return Math.min(this.ratioA, this.ratioB);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private get ratioUpper() {
|
||||
if (this.dualKnobs) {
|
||||
return Math.max(this.ratioA, this.ratioB);
|
||||
}
|
||||
return this.ratioA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
@Method()
|
||||
ratioUpper() {
|
||||
private updateRatio() {
|
||||
const value = this.getValue() as any;
|
||||
const {min, max} = this;
|
||||
if (this.dualKnobs) {
|
||||
return Math.max(this.ratioA, this.ratioB);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Listen('ionIncrease, ionDecrease')
|
||||
keyChng(ev: RangeEvent) {
|
||||
const step = this.step;
|
||||
if (ev.detail.knob === 'knobB') {
|
||||
if (ev.detail.isIncrease) {
|
||||
this.valB += step;
|
||||
this.ratioA = valueToRatio(value.lower, min, max);
|
||||
this.ratioB = valueToRatio(value.upper, min, max);
|
||||
} else {
|
||||
this.valB -= step;
|
||||
this.ratioA = valueToRatio(value, min, max);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
private onDragStart(detail: GestureDetail) {
|
||||
if (this.disabled) return false;
|
||||
this.fireFocus();
|
||||
private updateValue() {
|
||||
this.noUpdate = true;
|
||||
|
||||
const current = { x: detail.currentX, y: detail.currentY };
|
||||
const el = this.el.querySelector('.range-slider')!;
|
||||
const rect = el.getBoundingClientRect();
|
||||
this.rect = rect;
|
||||
const {valA, valB} = this;
|
||||
this.value = (!this.dualKnobs)
|
||||
? valA
|
||||
: {
|
||||
lower: Math.min(valA, valB),
|
||||
upper: Math.max(valA, valB)
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private onDragEnd(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();
|
||||
}
|
||||
|
||||
private onDragMove(detail: GestureDetail) {
|
||||
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.noUpdate = false;
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
class: {
|
||||
'range-disabled': this.disabled,
|
||||
'range-pressed': this.pressed,
|
||||
'range-pressed': this.pressedKnob !== Knob.None,
|
||||
'range-has-pin': this.pin
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {min, max, step, ratioLower, ratioUpper} = this;
|
||||
|
||||
const barL = `${ratioLower * 100}%`;
|
||||
const barR = `${100 - ratioUpper * 100}%`;
|
||||
|
||||
const ticks = [];
|
||||
if (this.snaps) {
|
||||
for (let value = min; value <= max; value += step) {
|
||||
const ratio = valueToRatio(value, min, max);
|
||||
ticks.push({
|
||||
ratio,
|
||||
active: ratio >= ratioLower && ratio <= ratioUpper,
|
||||
left: `${ratio * 100}%`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
<slot name="start"></slot>,
|
||||
<ion-gesture
|
||||
@ -426,12 +332,14 @@ export class Range implements BaseInput {
|
||||
threshold={0}>
|
||||
|
||||
<div class="range-slider">
|
||||
{this.ticks.map(t =>
|
||||
{ticks.map(t =>
|
||||
<div
|
||||
style={{ left: t.left! }}
|
||||
style={{ left: t.left }}
|
||||
role="presentation"
|
||||
class={{ 'range-tick': true, 'range-tick-active': !!t.active }}
|
||||
/>
|
||||
class={{
|
||||
'range-tick': true,
|
||||
'range-tick-active': t.active
|
||||
}}/>
|
||||
)}
|
||||
|
||||
<div class="range-bar" role="presentation" />
|
||||
@ -439,33 +347,28 @@ export class Range implements BaseInput {
|
||||
class="range-bar range-bar-active"
|
||||
role="presentation"
|
||||
style={{
|
||||
left: this.barL,
|
||||
right: this.barR
|
||||
left: barL,
|
||||
right: barR
|
||||
}}
|
||||
/>
|
||||
<ion-range-knob
|
||||
class="range-knob-handle"
|
||||
knob="knobA"
|
||||
pressed={this.pressedA}
|
||||
knob={Knob.A}
|
||||
pressed={this.pressedKnob === Knob.A}
|
||||
value={this.valA}
|
||||
ratio={this.ratioA}
|
||||
val={this.valA}
|
||||
pin={this.pin}
|
||||
min={this.min}
|
||||
max={this.max}
|
||||
/>
|
||||
min={min}
|
||||
max={max}/>
|
||||
|
||||
{this.dualKnobs
|
||||
? <ion-range-knob
|
||||
class="range-knob-handle"
|
||||
knob="knobB"
|
||||
pressed={this.pressedB}
|
||||
{ this.dualKnobs &&
|
||||
<ion-range-knob
|
||||
knob={Knob.B}
|
||||
pressed={this.pressedKnob === Knob.B}
|
||||
value={this.valB}
|
||||
ratio={this.ratioB}
|
||||
val={this.valB}
|
||||
pin={this.pin}
|
||||
min={this.min}
|
||||
max={this.max}
|
||||
/>
|
||||
: null}
|
||||
min={min}
|
||||
max={max} /> }
|
||||
</div>
|
||||
</ion-gesture>,
|
||||
<slot name="end"></slot>
|
||||
@ -473,9 +376,15 @@ export class Range implements BaseInput {
|
||||
}
|
||||
}
|
||||
|
||||
export interface RangeEvent extends Event {
|
||||
detail: {
|
||||
isIncrease: boolean,
|
||||
knob: string
|
||||
};
|
||||
|
||||
export function ratioToValue(ratio: number, min: number, max: number, step: number): number {
|
||||
let value = ((max - min) * ratio);
|
||||
if (step > 0) {
|
||||
value = Math.round(value / step) * step + min;
|
||||
}
|
||||
return clamp(min, value, max);
|
||||
}
|
||||
|
||||
export function valueToRatio(value: number, min: number, max: number): number {
|
||||
return clamp(0, (value - min) / (max - min), 1);
|
||||
}
|
||||
|
@ -254,22 +254,6 @@ Emitted when the range has focus.
|
||||
Emitted when the styles change.
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
#### ratio()
|
||||
|
||||
Returns the ratio of the knob's is current location, which is a number
|
||||
between `0` and `1`. If two knobs are used, this property represents
|
||||
the lower value.
|
||||
|
||||
|
||||
#### ratioUpper()
|
||||
|
||||
Returns the ratio of the upper value's is current location, which is
|
||||
a number between `0` and `1`. If there is only one knob, then this
|
||||
will return `null`.
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -26,10 +26,10 @@
|
||||
<ion-range value="20"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="60" color="light"></ion-range>
|
||||
<ion-range value="60" color="light" step="10" pin="true"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="80" color="dark"></ion-range>
|
||||
<ion-range value="80" color="dark" step="10" snaps="true" pin="true"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range pin="true" color="secondary" value="86">
|
||||
@ -48,7 +48,7 @@
|
||||
Dynamic Value
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-range pin="true" color="secondary" id="progressValue"></ion-range>
|
||||
<ion-range pin="true" step="0" color="secondary" id="progressValue"></ion-range>
|
||||
<div id="progressValueResult" slot="end"></div>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
@ -103,21 +103,32 @@
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
var ranges = ['progressValue', 'brightnessValue', 'contrastValue'];
|
||||
const progressValue = document.getElementById('progressValue');
|
||||
const brightnessValue = document.getElementById('brightnessValue');
|
||||
const contrastValue = document.getElementById('contrastValue');
|
||||
|
||||
const ranges = [progressValue, brightnessValue, contrastValue];
|
||||
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var el = document.getElementById(ranges[i]);
|
||||
var el = ranges[i];
|
||||
el.value = (i + 1) * 10;
|
||||
updateRangeResult(el);
|
||||
|
||||
el.addEventListener('ionChange', function(ev) {
|
||||
updateRangeResult(ev.currentTarget);
|
||||
updateRangeResult(ev.target);
|
||||
});
|
||||
}
|
||||
|
||||
progressValue.addEventListener('ionChange', function(ev) {
|
||||
console.log(ev.detail.value);
|
||||
brightnessValue.value = ev.detail.value;
|
||||
contrastValue.value = ev.target.value;
|
||||
});
|
||||
|
||||
|
||||
function updateRangeResult(el) {
|
||||
var resultEl = document.getElementById(`${el.id}Result`);
|
||||
resultEl.innerHTML = el.value;
|
||||
resultEl.innerHTML = Math.round(el.value);
|
||||
}
|
||||
|
||||
function increaseRangeValues() {
|
||||
|
1
core/src/interface.d.ts
vendored
1
core/src/interface.d.ts
vendored
@ -12,6 +12,7 @@ export * from './components/loading/loading-interface';
|
||||
export * from './components/popover/popover-interface';
|
||||
export * from './components/nav/nav-interface';
|
||||
export * from './components/router/utils/interface';
|
||||
export * from './components/range/range-interface';
|
||||
export * from './components/select/select-interface';
|
||||
export * from './components/select-popover/select-popover-interface';
|
||||
export * from './components/toast/toast-interface';
|
||||
|
Reference in New Issue
Block a user