Skip to content

Commit

Permalink
feat(range): add neutral point (#17400)
Browse files Browse the repository at this point in the history
* feat(Range): add neutral point

* feat(Range): generate proxies and api

* fix(): check positive case in neutralPointChanged

* fix(Range): neutralPoint to min if neutralPoint < min

* fix(Range): active bar style

* fix(Range): tick styling
  • Loading branch information
KillerCodeMonkey authored and liamdebeasi committed Feb 12, 2019
1 parent 39fbc32 commit 15acb4b
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 36 deletions.
4 changes: 2 additions & 2 deletions angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ export class IonRadioGroup {
proxyInputs(IonRadioGroup, ['allowEmptySelection', 'name', 'value']);

export declare interface IonRange extends StencilComponents<'IonRange'> {}
@Component({ selector: 'ion-range', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value'] })
@Component({ selector: 'ion-range', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'neutralPoint', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value'] })
export class IonRange {
ionChange!: EventEmitter<CustomEvent>;
ionFocus!: EventEmitter<CustomEvent>;
Expand All @@ -598,7 +598,7 @@ export class IonRange {
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
}
}
proxyInputs(IonRange, ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value']);
proxyInputs(IonRange, ['color', 'mode', 'neutralPoint', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value']);

export declare interface IonRefresher extends StencilComponents<'IonRefresher'> {}
@Component({ selector: 'ion-refresher', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['pullMin', 'pullMax', 'closeDuration', 'snapbackDuration', 'disabled'] })
Expand Down
3 changes: 2 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -800,10 +800,11 @@ ion-range,prop,max,number,100,false,false
ion-range,prop,min,number,0,false,false
ion-range,prop,mode,"ios" | "md",undefined,false,false
ion-range,prop,name,string,'',false,false
ion-range,prop,neutralPoint,number,0,false,false
ion-range,prop,pin,boolean,false,false,false
ion-range,prop,snaps,boolean,false,false,false
ion-range,prop,step,number,1,false,false
ion-range,prop,value,number | { lower: number; upper: number; },0,false,false
ion-range,prop,value,null | number | { lower: number; upper: number; },null,false,false
ion-range,event,ionBlur,void,true
ion-range,event,ionChange,RangeChangeEventDetail,true
ion-range,event,ionFocus,void,true
Expand Down
12 changes: 10 additions & 2 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3309,6 +3309,10 @@ export namespace Components {
*/
'name': string;
/**
* The neutral point of the range slider. Default: value is `0` or the `min` when `neutralPoint < min` or `max` when `max < neutralPoint`.
*/
'neutralPoint': number;
/**
* If `true`, a pin with integer value is shown when the knob is pressed.
*/
'pin': boolean;
Expand All @@ -3323,7 +3327,7 @@ export namespace Components {
/**
* the value of the range.
*/
'value': RangeValue;
'value': RangeValue | null;
}
interface IonRangeAttributes extends StencilHTMLAttributes {
/**
Expand Down Expand Up @@ -3359,6 +3363,10 @@ export namespace Components {
*/
'name'?: string;
/**
* The neutral point of the range slider. Default: value is `0` or the `min` when `neutralPoint < min` or `max` when `max < neutralPoint`.
*/
'neutralPoint'?: number;
/**
* Emitted when the range loses focus.
*/
'onIonBlur'?: (event: CustomEvent<void>) => void;
Expand All @@ -3385,7 +3393,7 @@ export namespace Components {
/**
* the value of the range.
*/
'value'?: RangeValue;
'value'?: RangeValue | null;
}

interface IonRefresherContent {
Expand Down
99 changes: 82 additions & 17 deletions core/src/components/range/range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ export class Range implements ComponentInterface {
*/
@Prop() mode!: Mode;

/**
* The neutral point of the range slider.
* Default: value is `0` or the `min` when `neutralPoint < min` or `max` when `max < neutralPoint`.
*/
@Prop() neutralPoint = 0;
protected neutralPointChanged() {
if (this.noUpdate) {
return;
}
const { min, max, neutralPoint } = this;

if (max < neutralPoint) {
this.neutralPoint = max;
}
if (neutralPoint < min) {
this.neutralPoint = min;
}
}

/**
* How long, in milliseconds, to wait to trigger the
* `ionChange` event after each change in the range value.
Expand Down Expand Up @@ -119,7 +138,7 @@ export class Range implements ComponentInterface {
/**
* the value of the range.
*/
@Prop({ mutable: true }) value: RangeValue = 0;
@Prop({ mutable: true }) value: RangeValue | null = null;
@Watch('value')
protected valueChanged(value: RangeValue) {
if (!this.noUpdate) {
Expand Down Expand Up @@ -210,14 +229,14 @@ export class Range implements ComponentInterface {
}

private getValue(): RangeValue {
const value = this.value || 0;
const value = this.value || this.neutralPoint || 0;
if (this.dualKnobs) {
if (typeof value === 'object') {
return value;
}
return {
lower: 0,
upper: value
lower: this.value === null ? this.neutralPoint : 0,
upper: this.value === null ? this.neutralPoint : value
};
} else {
if (typeof value === 'object') {
Expand Down Expand Up @@ -361,25 +380,67 @@ export class Range implements ComponentInterface {
}
};
}
protected getActiveBarPosition() {
const { min, max, neutralPoint, ratioLower, ratioUpper } = this;
const neutralPointRatio = valueToRatio(neutralPoint, min, max);

// dual knob handling
let left = `${ratioLower * 100}%`;
let right = `${100 - ratioUpper * 100}%`;

// single knob handling
if (!this.dualKnobs) {
if (this.ratioA < neutralPointRatio) {
right = `${neutralPointRatio * 100}%`;
left = `${this.ratioA * 100}%`;
} else {
right = `${100 - this.ratioA * 100}%`;
left = `${neutralPointRatio * 100}%`;
}
}

render() {
const { min, max, step, ratioLower, ratioUpper } = this;
return {
left,
right
};
}

const barStart = `${ratioLower * 100}%`;
const barEnd = `${100 - ratioUpper * 100}%`;
protected isTickActive(stepRatio: number) {
const { min, max, neutralPoint, ratioLower, ratioUpper } = this;
const neutralPointRatio = valueToRatio(neutralPoint, min, max);

if (this.dualKnobs) {
return (stepRatio >= ratioLower && stepRatio <= ratioUpper);
}

if (this.ratioA <= neutralPointRatio && stepRatio >= this.ratioA && stepRatio <= neutralPointRatio) {
return true;
}

if (this.ratioA >= neutralPointRatio && stepRatio <= this.ratioA && stepRatio >= neutralPointRatio) {
return true;
}

return false;
}

render() {
const { min, max, neutralPoint, step } = this;
const barPosition = this.getActiveBarPosition();

const isRTL = document.dir === 'rtl';
const start = isRTL ? 'right' : 'left';
const end = isRTL ? 'left' : 'right';

const ticks = [];
const ticks: any[] = [];

if (this.snaps) {
for (let value = min; value <= max; value += step) {
const ratio = valueToRatio(value, min, max);

const tick: any = {
ratio,
active: ratio >= ratioLower && ratio <= ratioUpper,
active: this.isTickActive(ratio),
};

tick[start] = `${ratio * 100}%`;
Expand All @@ -390,7 +451,6 @@ export class Range implements ComponentInterface {

const tickStyle = (tick: any) => {
const style: any = {};

style[start] = tick[start];

return style;
Expand All @@ -399,8 +459,8 @@ export class Range implements ComponentInterface {
const barStyle = () => {
const style: any = {};

style[start] = barStart;
style[end] = barEnd;
style[start] = barPosition[start];
style[end] = barPosition[end];

return style;
};
Expand Down Expand Up @@ -435,7 +495,8 @@ export class Range implements ComponentInterface {
disabled: this.disabled,
handleKeyboard: this.handleKeyboard,
min,
max
max,
neutralPoint
})}

{ this.dualKnobs && renderKnob({
Expand All @@ -447,7 +508,8 @@ export class Range implements ComponentInterface {
disabled: this.disabled,
handleKeyboard: this.handleKeyboard,
min,
max
max,
neutralPoint
})}
</div>,
<slot name="end"></slot>
Expand All @@ -461,14 +523,15 @@ interface RangeKnob {
ratio: number;
min: number;
max: number;
neutralPoint: number;
disabled: boolean;
pressed: boolean;
pin: boolean;

handleKeyboard: (name: KnobName, isIncrease: boolean) => void;
}

function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) {
function renderKnob({ knob, value, ratio, min, max, neutralPoint, disabled, pressed, pin, handleKeyboard }: RangeKnob) {
const isRTL = document.dir === 'rtl';
const start = isRTL ? 'right' : 'left';

Expand Down Expand Up @@ -501,7 +564,8 @@ function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, hand
'range-knob-b': knob === 'B',
'range-knob-pressed': pressed,
'range-knob-min': value === min,
'range-knob-max': value === max
'range-knob-max': value === max,
'range-knob-neutral': value === neutralPoint
}}
style={knobStyle()}
role="slider"
Expand All @@ -510,6 +574,7 @@ function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, hand
aria-valuemax={max}
aria-disabled={disabled ? 'true' : null}
aria-valuenow={value}
aria-valueneutral={neutralPoint}
>
{pin && <div class="range-pin" role="presentation">{Math.round(value)}</div>}
<div class="range-knob" role="presentation" />
Expand Down
Loading

0 comments on commit 15acb4b

Please sign in to comment.