@ionic/core
Version:
Base components for Ionic
404 lines (403 loc) • 12.8 kB
JavaScript
import { clamp, debounceEvent } from '../../utils/helpers';
import { createColorClasses, hostContext } from '../../utils/theme';
export class Range {
constructor() {
this.noUpdate = false;
this.hasFocus = false;
this.ratioA = 0;
this.ratioB = 0;
this.debounce = 0;
this.name = '';
this.dualKnobs = false;
this.min = 0;
this.max = 100;
this.pin = false;
this.snaps = false;
this.step = 1;
this.disabled = false;
this.value = 0;
this.handleKeyboard = (knob, isIncrease) => {
let step = this.step;
step = step > 0 ? step : 1;
step = step / (this.max - this.min);
if (!isIncrease) {
step *= -1;
}
if (knob === 'A') {
this.ratioA = clamp(0, this.ratioA + step, 1);
}
else {
this.ratioB = clamp(0, this.ratioB + step, 1);
}
this.updateValue();
};
}
debounceChanged() {
this.ionChange = debounceEvent(this.ionChange, this.debounce);
}
minChanged() {
if (!this.noUpdate) {
this.updateRatio();
}
}
maxChanged() {
if (!this.noUpdate) {
this.updateRatio();
}
}
disabledChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
}
this.emitStyle();
}
valueChanged(value) {
if (!this.noUpdate) {
this.updateRatio();
}
this.ionChange.emit({ value });
}
onBlur() {
if (this.hasFocus) {
this.hasFocus = false;
this.ionBlur.emit();
this.emitStyle();
}
}
onFocus() {
if (!this.hasFocus) {
this.hasFocus = true;
this.ionFocus.emit();
this.emitStyle();
}
}
componentWillLoad() {
this.updateRatio();
this.debounceChanged();
this.emitStyle();
}
async componentDidLoad() {
this.gesture = (await import('../../utils/gesture')).createGesture({
el: this.rangeSlider,
queue: this.queue,
gestureName: 'range',
gesturePriority: 100,
threshold: 0,
onStart: ev => this.onStart(ev),
onMove: ev => this.onMove(ev),
onEnd: ev => this.onEnd(ev),
});
this.gesture.setDisabled(this.disabled);
}
componentDidUnload() {
if (this.gesture) {
this.gesture.destroy();
this.gesture = undefined;
}
}
getValue() {
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;
}
}
emitStyle() {
this.ionStyle.emit({
'interactive': true,
'interactive-disabled': this.disabled
});
}
onStart(detail) {
const rect = this.rect = this.rangeSlider.getBoundingClientRect();
const currentX = detail.currentX;
const ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
this.pressedKnob =
!this.dualKnobs ||
Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio)
? 'A'
: 'B';
this.setFocus(this.pressedKnob);
this.update(currentX);
}
onMove(detail) {
this.update(detail.currentX);
}
onEnd(detail) {
this.update(detail.currentX);
this.pressedKnob = undefined;
}
update(currentX) {
const rect = this.rect;
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
if (this.snaps) {
ratio = valueToRatio(ratioToValue(ratio, this.min, this.max, this.step), this.min, this.max);
}
if (this.pressedKnob === 'A') {
this.ratioA = ratio;
}
else {
this.ratioB = ratio;
}
this.updateValue();
}
get valA() {
return ratioToValue(this.ratioA, this.min, this.max, this.step);
}
get valB() {
return ratioToValue(this.ratioB, this.min, this.max, this.step);
}
get ratioLower() {
if (this.dualKnobs) {
return Math.min(this.ratioA, this.ratioB);
}
return 0;
}
get ratioUpper() {
if (this.dualKnobs) {
return Math.max(this.ratioA, this.ratioB);
}
return this.ratioA;
}
updateRatio() {
const value = this.getValue();
const { min, max } = this;
if (this.dualKnobs) {
this.ratioA = valueToRatio(value.lower, min, max);
this.ratioB = valueToRatio(value.upper, min, max);
}
else {
this.ratioA = valueToRatio(value, min, max);
}
}
updateValue() {
this.noUpdate = true;
const { valA, valB } = this;
this.value = !this.dualKnobs
? valA
: {
lower: Math.min(valA, valB),
upper: Math.max(valA, valB)
};
this.noUpdate = false;
}
setFocus(knob) {
if (this.el.shadowRoot) {
const knobEl = this.el.shadowRoot.querySelector(knob === 'A' ? '.range-knob-a' : '.range-knob-b');
if (knobEl) {
knobEl.focus();
}
}
}
hostData() {
return {
class: Object.assign({}, createColorClasses(this.color), { 'in-item': hostContext('ion-item', this.el), 'range-disabled': this.disabled, 'range-pressed': this.pressedKnob !== undefined, '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 [
h("slot", { name: "start" }),
h("div", { class: "range-slider", ref: el => this.rangeSlider = el },
ticks.map(t => (h("div", { style: { left: t.left }, role: "presentation", class: {
'range-tick': true,
'range-tick-active': t.active
} }))),
h("div", { class: "range-bar", role: "presentation" }),
h("div", { class: "range-bar range-bar-active", role: "presentation", style: {
left: barL,
right: barR
} }),
renderKnob({
knob: 'A',
pressed: this.pressedKnob === 'A',
value: this.valA,
ratio: this.ratioA,
pin: this.pin,
disabled: this.disabled,
handleKeyboard: this.handleKeyboard,
min,
max
}),
this.dualKnobs && renderKnob({
knob: 'B',
pressed: this.pressedKnob === 'B',
value: this.valB,
ratio: this.ratioB,
pin: this.pin,
disabled: this.disabled,
handleKeyboard: this.handleKeyboard,
min,
max
})),
h("slot", { name: "end" })
];
}
static get is() { return "ion-range"; }
static get encapsulation() { return "shadow"; }
static get properties() { return {
"color": {
"type": String,
"attr": "color"
},
"debounce": {
"type": Number,
"attr": "debounce",
"watchCallbacks": ["debounceChanged"]
},
"disabled": {
"type": Boolean,
"attr": "disabled",
"watchCallbacks": ["disabledChanged"]
},
"dualKnobs": {
"type": Boolean,
"attr": "dual-knobs"
},
"el": {
"elementRef": true
},
"max": {
"type": Number,
"attr": "max",
"watchCallbacks": ["maxChanged"]
},
"min": {
"type": Number,
"attr": "min",
"watchCallbacks": ["minChanged"]
},
"mode": {
"type": String,
"attr": "mode"
},
"name": {
"type": String,
"attr": "name"
},
"pin": {
"type": Boolean,
"attr": "pin"
},
"pressedKnob": {
"state": true
},
"queue": {
"context": "queue"
},
"ratioA": {
"state": true
},
"ratioB": {
"state": true
},
"snaps": {
"type": Boolean,
"attr": "snaps"
},
"step": {
"type": Number,
"attr": "step"
},
"value": {
"type": Number,
"attr": "value",
"mutable": true,
"watchCallbacks": ["valueChanged"]
}
}; }
static get events() { return [{
"name": "ionChange",
"method": "ionChange",
"bubbles": true,
"cancelable": true,
"composed": true
}, {
"name": "ionStyle",
"method": "ionStyle",
"bubbles": true,
"cancelable": true,
"composed": true
}, {
"name": "ionFocus",
"method": "ionFocus",
"bubbles": true,
"cancelable": true,
"composed": true
}, {
"name": "ionBlur",
"method": "ionBlur",
"bubbles": true,
"cancelable": true,
"composed": true
}]; }
static get listeners() { return [{
"name": "focusout",
"method": "onBlur"
}, {
"name": "focusin",
"method": "onFocus"
}]; }
static get style() { return "/**style-placeholder:ion-range:**/"; }
static get styleMode() { return "/**style-id-placeholder:ion-range:**/"; }
}
function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }) {
return (h("div", { onKeyDown: (ev) => {
const key = ev.key;
if (key === 'ArrowLeft' || key === 'ArrowDown') {
handleKeyboard(knob, false);
ev.preventDefault();
ev.stopPropagation();
}
else if (key === 'ArrowRight' || key === 'ArrowUp') {
handleKeyboard(knob, true);
ev.preventDefault();
ev.stopPropagation();
}
}, class: {
'range-knob-handle': true,
'range-knob-a': knob === 'A',
'range-knob-b': knob === 'B',
'range-knob-pressed': pressed,
'range-knob-min': value === min,
'range-knob-max': value === max
}, style: {
'left': `${ratio * 100}%`
}, role: "slider", tabindex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled ? 'true' : null, "aria-valuenow": value },
pin && h("div", { class: "range-pin", role: "presentation" }, Math.round(value)),
h("div", { class: "range-knob", role: "presentation" })));
}
function ratioToValue(ratio, min, max, step) {
let value = (max - min) * ratio;
if (step > 0) {
value = Math.round(value / step) * step + min;
}
return clamp(min, value, max);
}
function valueToRatio(value, min, max) {
return clamp(0, (value - min) / (max - min), 1);
}