Skip to content

Instantly share code, notes, and snippets.

@vcardins
Created May 9, 2024 13:09
Show Gist options
  • Save vcardins/70d56ff86d963c79f0a5338ab641ed76 to your computer and use it in GitHub Desktop.
Save vcardins/70d56ff86d963c79f0a5338ab641ed76 to your computer and use it in GitHub Desktop.
export namespace TsIonRangeSlider
{
let pluginCount = 0;
export interface ISlider {
destroy(): void;
reset(): void;
update(option: IonRangeSliderOptions): void;
}
export interface IonRangeSliderOptions {
skin?: 'flat' | 'big' | 'modern' | 'round' | 'sharp' | 'square'; // Set slider theme [Default: flat]
type?: string; // Choose slider type, could be `single` - for one handle, or `double` for two handles [Default: single]
min?: number; // Set slider minimum value [Default: 10]
max?: number; // Set slider maximum value [Default: 100]
from?: number; // Set start position for left handle (or for single handle) [Default: min]
to?: number; // Set start position for right handle [Default: max]
step?: number; // Set sliders step. Always > 0. Could be fractional [Default: 1]
min_interval?: number; // Set minimum diapason between sliders. Only for **double** type [Default: -]
max_interval?: number; // Set minimum maximum between sliders. Only for **double** type [Default: -]
drag_interval?: boolean; // Allow user to drag whole range. Only for **double** type [Default: false]
values?: any[]; // Set up your own array of possible slider values. They could be numbers or strings. If the values array is set up, min, max and step param, can no longer be changed [Default: []]
from_fixed?: boolean; // Fix position of left (or single) handle [Default: false]
from_min?: number; // Set minimum limit for left (or single) handle [Default: min]
from_max?: number; // Set maximum limit for left (or single) handle [Default: max]
from_shadow?: boolean; // Highlight the limits for left handle [Default: false]
to_fixed?: boolean; // Fix position of right handle [Default: false]
to_min?: number; // Set minimum limit for right handle [Default: min]
to_max?: number; // Set maximum limit for right handle [Default: max]
to_shadow?: boolean; // Highlight the right handle [Default: false]
prettify_enabled?: boolean; // Improve readability of long numbers: 10000000 → 10 000 000 [Default: true]
prettify_separator?: string; // Set up your own separator for long numbers: 10000000 → 10,000,000 etc. [Default: ]
prettify?: (num: number) => string; // Set up your own prettify function. Can be anything. For example, you can set up unix time as slider values and than transform them to cool looking dates [Default: null]
force_edges?: boolean; // Sliders handles and tooltips will be always inside it's container [Default: false]
keyboard?: boolean; // Activates keyboard controls. Move left: ←, ↓, A, S. Move right: →, ↑, W, D. [Default: true]
grid?: boolean; // Enables grid of values above the slider [Default: true]
grid_margin?: boolean; // Set left and right grid gaps [Default: true]
grid_num?: number; // Number of grid units [Default: 4]
grid_snap?: boolean; // Snap grid to sliders step (step param). If activated, grid_num will not be used. Max steps = 50 [Default: false]
hide_min_max?: boolean; // Hides **min** and **max** labels [Default: false]
hide_from_to?: boolean; // Hides **from** and **to** labels [Default: false]
prefix?: string; // Set prefix for values. Will be set up right before the number: **$**100 [Default: ]
postfix?: string; // Set postfix for values. Will be set up right after the number: 100**k** [Default: ]
max_postfix?: string; // Special postfix, used only for maximum value. Will be showed after handle will reach maximum right position. For example **0 — 100+** [Default: ]
decorate_both?: boolean; // Used for **double** type and only if prefix or postfix was set up. Determine how to decorate close values. For example: **$10k — $100k** or **$10 — 100k** [Default: true]
values_separator?: string; // Set your own separator for close values. Used for **double** type. Default: **10 — 100**. Or you may set: **10 to 100, 10 + 100, 10 → 100** etc. [Default: - ]
input_values_separator?: string; // Separator for **double** values in input value property. `<input value="25;42"> [Default: ; ]
disable?: boolean; // Locks slider and makes it inactive. Input is disabled too. Invisible to forms [Default: false]
block?: boolean; // Locks slider and makes it inactive. Input is NOT disabled. Can be send with forms [Default: false]
extra_classes?: string; // Traverse extra CSS-classes to sliders container [Default: —]
scope?: any; // Scope for callbacks. Pass any object [Default: null]
onStart?: (obj: IonRangeSliderEvent) => void; // Callback. Is called on slider start. Gets all slider data as a 1st attribute [Default: null]
onChange?: (obj: IonRangeSliderEvent) => void; // Callback. IS called on each values change. Gets all slider data as a 1st attribute [Default: null]
onFinish?: (obj: IonRangeSliderEvent) => void; // Callback. Is called when user releases handle. Gets all slider data as a 1st attribute [Default: null]
onUpdate?: (obj: IonRangeSliderEvent) => void; // Callback. Is called when slider is modified by external methods `update` or `reset [Default: null]
}
export interface IonRangeSliderEvent {
min: any; // MIN value
max: any; // MAX value
from: number; // FROM value (left or single handle)
from_percent: number; // FROM value in percents
from_value: number; // FROM index in values array (if used)
to: number; // TO value (right handle in double type)
to_percent: number; // TO value in percents
to_value: number; // TO index in values array (if used)
}
export class Slider implements ISlider
{
private plugin_count = 0;
private input: HTMLInputElement;
private current_plugin = 0;
private calc_count = 0;
private update_tm: any = 0;
private old_from = 0;
private old_to = 0;
private old_min_interval = null;
private raf_id = null;
private dragging = false;
private force_redraw = false;
private no_diapason = false;
private has_tab_index = true;
private is_key = false;
private is_update = false;
private is_start = true;
private is_finish = false;
private is_active = false;
private is_resize = false;
private is_click = false;
private options: any;
private cache: any;
private labels: any;
private coords: any;
private update_check: any;
private result: any;
private target: string;
private getIsOldIe(): boolean
{
const n = navigator.userAgent,
r = /msie\s\d+/i;
let v: string;
if (n.search(r) > 0)
{
v = r.exec(n).toString();
v = v.split(' ')[1];
if (parseFloat(v) < 9)
{
document.querySelector('html').classList.add('lt-ie9');
return true;
}
}
return false;
}
// =================================================================================================================
// Template
// =================================================================================================================
private base_html =
'<span class="irs">' +
'<span class="irs-line" tabindex="0"></span>' +
'<span class="irs-min">0</span><span class="irs-max">1</span>' +
'<span class="irs-from">0</span><span class="irs-to">0</span><span class="irs-single">0</span>' +
'</span>' +
'<span class="irs-grid"></span>';
private single_html =
'<span class="irs-bar irs-bar--single"></span>' +
'<span class="irs-shadow shadow-single"></span>' +
'<span class="irs-handle single"><i></i><i></i><i></i></span>';
private double_html =
'<span class="irs-bar"></span>' +
'<span class="irs-shadow shadow-from"></span>' +
'<span class="irs-shadow shadow-to"></span>' +
'<span class="irs-handle from"><i></i><i></i><i></i></span>' +
'<span class="irs-handle to"><i></i><i></i><i></i></span>';
private disable_html = '<span class="irs-disable-mask"></span>';
// =================================================================================================================
// Core
// =================================================================================================================
constructor(input: HTMLInputElement, options?: IonRangeSliderOptions)
{
this.rangeSlider(input, options);
}
private rangeSlider(input: HTMLInputElement, options)
{
this.input = input;
this.plugin_count = pluginCount++;
options = options || {};
// cache for links to all DOM elements
this.cache = {
win : window,
body : document.body,
input : input,
cont : null,
rs : null,
min : null,
max : null,
from : null,
to : null,
single : null,
bar : null,
line : null,
s_single : null,
s_from : null,
s_to : null,
shad_single: null,
shad_from : null,
shad_to : null,
edge : null,
grid : null,
grid_labels: []
};
// storage for measure variables
this.coords = {
// left
x_gap : 0,
x_pointer: 0,
// width
w_rs : 0,
w_rs_old: 0,
w_handle: 0,
// percents
p_gap : 0,
p_gap_left : 0,
p_gap_right : 0,
p_step : 0,
p_pointer : 0,
p_handle : 0,
p_single_fake: 0,
p_single_real: 0,
p_from_fake : 0,
p_from_real : 0,
p_to_fake : 0,
p_to_real : 0,
p_bar_x : 0,
p_bar_w : 0,
// grid
grid_gap: 0,
big_num : 0,
big : [],
big_w : [],
big_p : [],
big_x : []
};
// storage for labels measure variables
this.labels = {
// width
w_min : 0,
w_max : 0,
w_from : 0,
w_to : 0,
w_single: 0,
// percents
p_min : 0,
p_max : 0,
p_from_fake : 0,
p_from_left : 0,
p_to_fake : 0,
p_to_left : 0,
p_single_fake: 0,
p_single_left: 0
};
/**
* get and validate config
*/
const $inp = this.cache.input;
let val = $inp.value, config;
// default config
config = {
skin: 'flat',
type: 'single',
min : 10,
max : 100,
from: null,
to : null,
step: 1,
min_interval : 0,
max_interval : 0,
drag_interval: false,
values : [],
p_values: [],
from_fixed : false,
from_min : null,
from_max : null,
from_shadow: false,
to_fixed : false,
to_min : null,
to_max : null,
to_shadow: false,
prettify_enabled : true,
prettify_separator: ' ',
prettify : null,
force_edges: false,
keyboard: true,
grid : false,
grid_margin: true,
grid_num : 4,
grid_snap : false,
hide_min_max: false,
hide_from_to: false,
prefix : '',
postfix : '',
max_postfix : '',
decorate_both : true,
values_separator: ' — ',
input_values_separator: ';',
disable: false,
block : false,
extra_classes: '',
scope : null,
onStart : null,
onChange: null,
onFinish: null,
onUpdate: null
};
// check if base element is input
if ($inp.nodeName !== 'INPUT')
{
console.warn('Base element should be <input>!', $inp[0]);
}
// input value extends default config
if (val !== undefined && val !== '')
{
val = val.split(options.input_values_separator || ';');
if (val[0] && val[0] === +val[0])
{
val[0] = +val[0];
}
if (val[1] && val[1] === +val[1])
{
val[1] = +val[1];
}
if (options && options.values && options.values.length)
{
config.from = val[0] && options.values.indexOf(val[0]);
config.to = val[1] && options.values.indexOf(val[1]);
}
else
{
config.from = val[0] && +val[0];
config.to = val[1] && +val[1];
}
}
// mergeObjects
// js config extends default config
config = Object.assign({}, config, options);
this.options = config;
// validate config, to be sure that all data types are correct
this.update_check = {};
this.validate();
// default result object, returned to callbacks
this.result = {
input : this.cache.input,
slider: null,
min: this.options.min,
max: this.options.max,
from : this.options.from,
from_percent: 0,
from_value : null,
to : this.options.to,
to_percent: 0,
to_value : null
};
this.init();
}
/**
* Starts or updates the plugin instance
*/
private init(is_update?: boolean): void
{
this.no_diapason = false;
this.coords.p_step = this.convertToPercent(this.options.step, true);
this.target = 'base';
this.toggleInput();
this.append();
this.setMinMax();
if (is_update)
{
this.force_redraw = true;
this.calc(true);
// callbacks called
this.callOnUpdate();
}
else
{
this.force_redraw = true;
this.calc(true);
// callbacks called
this.callOnStart();
}
this.updateScene();
}
/**
* Appends slider template to a DOM
*/
private append(): void
{
const container_html = '<span class="irs irs--' + this.options.skin + ' js-irs-' + this.plugin_count + ' ' + this.options.extra_classes + '"></span>';
this.cache.input.insertAdjacentHTML('beforebegin', container_html);
this.cache.input.readOnly = true;
this.cache.cont = this.cache.input.previousElementSibling;
this.result.slider = this.cache.cont;
this.cache.cont.innerHTML = this.base_html;
this.cache.rs = this.cache.cont.querySelector('.irs');
this.cache.min = this.cache.cont.querySelector('.irs-min');
this.cache.max = this.cache.cont.querySelector('.irs-max');
this.cache.from = this.cache.cont.querySelector('.irs-from');
this.cache.to = this.cache.cont.querySelector('.irs-to');
this.cache.single = this.cache.cont.querySelector('.irs-single');
this.cache.line = this.cache.cont.querySelector('.irs-line');
this.cache.grid = this.cache.cont.querySelector('.irs-grid');
if (this.options.type === 'single')
{
this.cache.cont.insertAdjacentHTML('beforeend', this.single_html);
this.cache.bar = this.cache.cont.querySelector('.irs-bar');
this.cache.edge = this.cache.cont.querySelector('.irs-bar-edge');
this.cache.s_single = this.cache.cont.querySelector('.single');
this.cache.from.style.visibility = 'hidden';
this.cache.to.style.visibility = 'hidden';
this.cache.shad_single = this.cache.cont.querySelector('.shadow-single');
}
else
{
this.cache.cont.insertAdjacentHTML('beforeend', this.double_html);
this.cache.bar = this.cache.cont.querySelector('.irs-bar');
this.cache.s_from = this.cache.cont.querySelector('.from');
this.cache.s_to = this.cache.cont.querySelector('.to');
this.cache.shad_from = this.cache.cont.querySelector('.shadow-from');
this.cache.shad_to = this.cache.cont.querySelector('.shadow-to');
this.setTopHandler();
}
if (this.options.hide_from_to)
{
this.cache.from.style.display = 'none';
this.cache.to.style.display = 'none';
this.cache.single.style.display = 'none';
}
this.appendGrid();
if (this.options.disable)
{
this.appendDisableMask();
this.cache.input.disabled = true;
}
else
{
this.cache.input.disabled = false;
this.removeDisableMask();
this.bindEvents();
}
// block only if not disabled
if (!this.options.disable)
{
if (this.options.block)
{
this.appendDisableMask();
}
else
{
this.removeDisableMask();
}
}
if (this.options.drag_interval)
{
this.cache.bar.style.cursor = 'ew-resize';
}
}
/**
* Determine which handler has a priority
* works only for double slider type
*/
private setTopHandler(): void
{
const min = this.options.min,
max = this.options.max,
from = this.options.from,
to = this.options.to;
if (from > min && to === max)
{
this.cache.s_from.classList.add('type_last');
}
else if (to < max)
{
this.cache.s_to.classList.add('type_last');
}
}
/**
* Determine which handles was clicked last
* and which handler should have hover effect
*/
private changeLevel(target: string): void
{
switch (target)
{
case 'single':
this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_single_fake);
this.cache.s_single.classList.add('state_hover');
break;
case 'from':
this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_from_fake);
this.cache.s_from.classList.add('state_hover');
this.cache.s_from.classList.add('type_last');
this.cache.s_to.classList.remove('type_last');
break;
case 'to':
this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_to_fake);
this.cache.s_to.classList.add('state_hover');
this.cache.s_to.classList.add('type_last');
this.cache.s_from.classList.remove('type_last');
break;
case 'both':
this.coords.p_gap_left = this.toFixed(this.coords.p_pointer - this.coords.p_from_fake);
this.coords.p_gap_right = this.toFixed(this.coords.p_to_fake - this.coords.p_pointer);
this.cache.s_to.classList.remove('type_last');
this.cache.s_from.classList.remove('type_last');
break;
}
}
/**
* Then slider is disabled
* appends extra layer with opacity
*/
private appendDisableMask(): void
{
this.cache.cont.innerHTML = this.disable_html;
this.cache.cont.classList.add('irs-disabled');
}
/**
* Then slider is not disabled
* remove disable mask
*/
private removeDisableMask(): void
{
this.removeElement(this.cache.cont.querySelector('.irs-disable-mask'));
this.cache.cont.classList.remove('irs-disabled');
}
private removeElement(element): void
{
if (element)
{
element.parentNode.removeChild(element);
}
}
/**
* Remove slider instance
* and unbind all events
*/
private remove(): void
{
this.removeElement(this.cache.cont);
this.cache.cont = null;
this.unbindEvens();
this.cache.grid_labels = [];
this.coords.big = [];
this.coords.big_w = [];
this.coords.big_p = [];
this.coords.big_x = [];
cancelAnimationFrame(this.raf_id);
}
/**
* bind all slider events
*/
private bindEvents(): void
{
if (this.no_diapason)
{
return;
}
this.cache.body.addEventListener('touchmove', e => this.pointerMove(e));
this.cache.body.addEventListener('mousemove', e => this.pointerMove(e));
this.cache.win.addEventListener('touchend', e => this.pointerUp(e));
this.cache.win.addEventListener('mouseup', e => this.pointerUp(e));
this.cache.line.addEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.line.addEventListener('mousedown', e => this.pointerClick('click', e));
this.cache.line.addEventListener('focus', e => this.pointerFocus(e));
if (this.options.drag_interval && this.options.type === 'double')
{
this.cache.bar.addEventListener('touchstart', e => this.pointerDown('both', e));
this.cache.bar.addEventListener('mousedown', e => this.pointerDown('both', e));
}
else
{
this.cache.bar.addEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.bar.addEventListener('mousedown', e => this.pointerClick('click', e));
}
if (this.options.type === 'single')
{
this.cache.single.addEventListener('touchstart', e => this.pointerDown('single', e));
this.cache.s_single.addEventListener('touchstart', e => this.pointerDown('single', e));
this.cache.shad_single.addEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.single.addEventListener('mousedown', e => this.pointerDown('single', e));
this.cache.s_single.addEventListener('mousedown', e => this.pointerDown('single', e));
this.cache.shad_single.addEventListener('mousedown', e => this.pointerClick.bind('click', e));
if (this.cache.edge)
{
this.cache.edge.addEventListener('mousedown', e => this.pointerClick('click', e));
}
}
else
{
this.cache.single.addEventListener('touchstart', e => this.pointerDown(null, e));
this.cache.single.addEventListener('mousedown', e => this.pointerDown(null, e));
this.cache.from.addEventListener('touchstart', e => this.pointerDown('from', e));
this.cache.s_from.addEventListener('touchstart', e => this.pointerDown('from', e));
this.cache.to.addEventListener('touchstart', e => this.pointerDown('to', e));
this.cache.s_to.addEventListener('touchstart', e => this.pointerDown('to', e));
this.cache.shad_from.addEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.shad_to.addEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.from.addEventListener('mousedown', e => this.pointerDown('from', e));
this.cache.s_from.addEventListener('mousedown', e => this.pointerDown('from', e));
this.cache.to.addEventListener('mousedown', e => this.pointerDown('to', e));
this.cache.s_to.addEventListener('mousedown', e => this.pointerDown('to', e));
this.cache.shad_from.addEventListener('mousedown', e => this.pointerClick('click', e));
this.cache.shad_to.addEventListener('mousedown', e => this.pointerClick('click', e));
}
if (this.options.keyboard)
{
this.cache.line.addEventListener('keydown', e => this.key('keyboard', e));
}
if (this.getIsOldIe())
{
this.cache.body.addEventListener('mouseup', e => this.pointerUp(e));
this.cache.body.addEventListener('mouseleave', e => this.pointerUp(e));
}
}
/**
* Focus with tabIndex
*/
private pointerFocus(e: any): void
{
if (!this.target)
{
let x;
let $handle;
if (this.options.type === 'single')
{
$handle = this.cache.single;
}
else
{
$handle = this.cache.from;
}
x = this.getOffset($handle).left;
x += ($handle.width() / 2) - 1;
this.pointerClick('single', {
preventDefault: function ()
{
}, pageX : x
});
}
}
private getOffset(element: any): {top: number, left: number}
{
const rect = element.getBoundingClientRect();
return {
top : rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
}
/**
* Mousemove or touchmove
* only for handlers
*/
private pointerMove(e: any): any
{
if (!this.dragging)
{
return;
}
const x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
this.coords.x_pointer = x - this.coords.x_gap;
this.calc();
}
/**
* Mouseup or touchend
* only for handlers
*/
private pointerUp(e: any): void
{
if (this.current_plugin !== this.plugin_count)
{
return;
}
if (this.is_active)
{
this.is_active = false;
}
else
{
return;
}
this.removeClass(this.cache.cont.querySelector('.state_hover'), 'state_hover');
this.force_redraw = true;
this.updateScene();
this.restoreOriginalMinInterval();
// callbacks call
if (this.contains(this.cache.cont[0], e.target) || this.dragging)
{
this.callOnFinish();
}
this.dragging = false;
}
private removeClass(element: HTMLElement|any, klass: string): void
{
if (element && element.classList)
{
element.classList.remove(klass);
}
}
private contains(selector, text): boolean
{
const elements = document.querySelectorAll(selector);
return [].filter.call(elements, element => RegExp(text).test(element.textContent));
}
/**
* Mousedown or touchstart
* only for handlers
*/
private pointerDown(target: string|null, e: any): void
{
e.preventDefault();
const x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
if (e.button === 2)
{
return;
}
if (target === 'both')
{
this.setTempMinInterval();
}
if (!target)
{
target = this.target || 'from';
}
this.current_plugin = this.plugin_count;
this.target = target;
this.is_active = true;
this.dragging = true;
this.coords.x_gap = this.getOffset(this.cache.rs).left;
this.coords.x_pointer = x - this.coords.x_gap;
this.calcPointerPercent();
this.changeLevel(target);
this.trigger('focus', this.cache.line);
this.updateScene();
}
/**
* Mousedown or touchstart
* for other slider elements, like diapason line
*/
private pointerClick(target: string, e: any): void
{
e.preventDefault();
const x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
if (e.button === 2)
{
return;
}
this.current_plugin = this.plugin_count;
this.target = target;
this.is_click = true;
this.coords.x_gap = this.getOffset(this.cache.rs).left;
this.coords.x_pointer = +(x - this.coords.x_gap).toFixed();
this.force_redraw = true;
this.calc();
this.trigger('focus', this.cache.line);
}
private trigger(type: string, element: any)
{
const evt = document.createEvent('HTMLEvents');
evt.initEvent(type, true, true);
element.dispatchEvent(evt);
}
/**
* Keyborard controls for focused slider
*/
private key(target: string, e: any): boolean
{
if (this.current_plugin !== this.plugin_count || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey)
{
return;
}
switch (e.which)
{
case 83: // W
case 65: // A
case 40: // DOWN
case 37: // LEFT
e.preventDefault();
this.moveByKey(false);
break;
case 87: // S
case 68: // D
case 38: // UP
case 39: // RIGHT
e.preventDefault();
this.moveByKey(true);
break;
}
return true;
}
/**
* Move by key
*/
private moveByKey(right: boolean): void
{
let p = this.coords.p_pointer;
let p_step = (this.options.max - this.options.min) / 100;
p_step = this.options.step / p_step;
if (right)
{
p += p_step;
}
else
{
p -= p_step;
}
this.coords.x_pointer = this.toFixed(this.coords.w_rs / 100 * p);
this.is_key = true;
this.calc();
}
/**
* Set visibility and content
* of Min and Max labels
*/
private setMinMax(): void
{
if (!this.options)
{
return;
}
if (this.options.hide_min_max)
{
this.cache.min.style.display = 'none';
this.cache.max.style.display = 'none';
return;
}
if (this.options.values.length)
{
this.cache.min.innerHTML = this.decorate(this.options.p_values[this.options.min]);
this.cache.max.innerHTML = this.decorate(this.options.p_values[this.options.max]);
}
else
{
const min_pretty = this._prettify(this.options.min);
const max_pretty = this._prettify(this.options.max);
this.result.min_pretty = min_pretty;
this.result.max_pretty = max_pretty;
this.cache.min.innerHTML = this.decorate(min_pretty, this.options.min);
this.cache.max.innerHTML = this.decorate(max_pretty, this.options.max);
}
this.labels.w_min = this.outerWidth(this.cache.min);
this.labels.w_max = this.outerWidth(this.cache.max);
}
private outerWidth(el, includeMargin = false): number
{
let width = el.offsetWidth;
if (includeMargin)
{
const style = getComputedStyle(el);
width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
}
return width;
}
/**
* Then dragging interval, prevent interval collapsing
* using min_interval option
*/
private setTempMinInterval(): void
{
const interval = this.result.to - this.result.from;
if (this.old_min_interval === null)
{
this.old_min_interval = this.options.min_interval;
}
this.options.min_interval = interval;
}
/**
* Restore min_interval option to original
*/
private restoreOriginalMinInterval(): void
{
if (this.old_min_interval !== null)
{
this.options.min_interval = this.old_min_interval;
this.old_min_interval = null;
}
}
// =============================================================================================================
// Calculations
// =============================================================================================================
/**
* All calculations and measures start here
*/
private calc(update = false)
{
if (!this.options)
{
return;
}
this.calc_count++;
if (this.calc_count === 10 || update)
{
this.calc_count = 0;
this.coords.w_rs = this.outerWidth(this.cache.rs, false);
this.calcHandlePercent();
}
if (!this.coords.w_rs)
{
return;
}
this.calcPointerPercent();
let handle_x = this.getHandleX();
if (this.target === 'both')
{
this.coords.p_gap = 0;
handle_x = this.getHandleX();
}
if (this.target === 'click')
{
this.coords.p_gap = this.coords.p_handle / 2;
handle_x = this.getHandleX();
if (this.options.drag_interval)
{
this.target = 'both_one';
}
else
{
this.target = this.chooseHandle(handle_x);
}
}
switch (this.target)
{
case 'base':
const w = (this.options.max - this.options.min) / 100,
f = (this.result.from - this.options.min) / w,
t = (this.result.to - this.options.min) / w;
this.coords.p_single_real = this.toFixed(f);
this.coords.p_from_real = this.toFixed(f);
this.coords.p_to_real = this.toFixed(t);
this.coords.p_single_real = this.checkDiapason(this.coords.p_single_real, this.options.from_min, this.options.from_max);
this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
this.coords.p_single_fake = this.convertToFakePercent(this.coords.p_single_real);
this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
this.target = null;
break;
case 'single':
if (this.options.from_fixed)
{
break;
}
this.coords.p_single_real = this.convertToRealPercent(handle_x);
this.coords.p_single_real = this.calcWithStep(this.coords.p_single_real);
this.coords.p_single_real = this.checkDiapason(this.coords.p_single_real, this.options.from_min, this.options.from_max);
this.coords.p_single_fake = this.convertToFakePercent(this.coords.p_single_real);
break;
case 'from':
if (this.options.from_fixed)
{
break;
}
this.coords.p_from_real = this.convertToRealPercent(handle_x);
this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
if (this.coords.p_from_real > this.coords.p_to_real)
{
this.coords.p_from_real = this.coords.p_to_real;
}
this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
this.coords.p_from_real = this.checkMinInterval(this.coords.p_from_real, this.coords.p_to_real, 'from');
this.coords.p_from_real = this.checkMaxInterval(this.coords.p_from_real, this.coords.p_to_real, 'from');
this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
break;
case 'to':
if (this.options.to_fixed)
{
break;
}
this.coords.p_to_real = this.convertToRealPercent(handle_x);
this.coords.p_to_real = this.calcWithStep(this.coords.p_to_real);
if (this.coords.p_to_real < this.coords.p_from_real)
{
this.coords.p_to_real = this.coords.p_from_real;
}
this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
this.coords.p_to_real = this.checkMinInterval(this.coords.p_to_real, this.coords.p_from_real, 'to');
this.coords.p_to_real = this.checkMaxInterval(this.coords.p_to_real, this.coords.p_from_real, 'to');
this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
break;
case 'both':
if (this.options.from_fixed || this.options.to_fixed)
{
break;
}
handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.001));
this.coords.p_from_real = this.convertToRealPercent(handle_x) - this.coords.p_gap_left;
this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
this.coords.p_from_real = this.checkMinInterval(this.coords.p_from_real, this.coords.p_to_real, 'from');
this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
this.coords.p_to_real = this.convertToRealPercent(handle_x) + this.coords.p_gap_right;
this.coords.p_to_real = this.calcWithStep(this.coords.p_to_real);
this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
this.coords.p_to_real = this.checkMinInterval(this.coords.p_to_real, this.coords.p_from_real, 'to');
this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
break;
case 'both_one':
if (this.options.from_fixed || this.options.to_fixed)
{
break;
}
const real_x = this.convertToRealPercent(handle_x),
from = this.result.from_percent,
to = this.result.to_percent,
full = to - from,
half = full / 2;
let new_from = real_x - half,
new_to = real_x + half;
if (new_from < 0)
{
new_from = 0;
new_to = new_from + full;
}
if (new_to > 100)
{
new_to = 100;
new_from = new_to - full;
}
this.coords.p_from_real = this.calcWithStep(new_from);
this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
this.coords.p_to_real = this.calcWithStep(new_to);
this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
break;
}
if (this.options.type === 'single')
{
this.coords.p_bar_x = (this.coords.p_handle / 2);
this.coords.p_bar_w = this.coords.p_single_fake;
this.result.from_percent = this.coords.p_single_real;
this.result.from = this.convertToValue(this.coords.p_single_real);
this.result.from_pretty = this._prettify(this.result.from);
if (this.options.values.length)
{
this.result.from_value = this.options.values[this.result.from];
}
}
else
{
this.coords.p_bar_x = this.toFixed(this.coords.p_from_fake + (this.coords.p_handle / 2));
this.coords.p_bar_w = this.toFixed(this.coords.p_to_fake - this.coords.p_from_fake);
this.result.from_percent = this.coords.p_from_real;
this.result.from = this.convertToValue(this.coords.p_from_real);
this.result.from_pretty = this._prettify(this.result.from);
this.result.to_percent = this.coords.p_to_real;
this.result.to = this.convertToValue(this.coords.p_to_real);
this.result.to_pretty = this._prettify(this.result.to);
if (this.options.values.length)
{
this.result.from_value = this.options.values[this.result.from];
this.result.to_value = this.options.values[this.result.to];
}
}
this.calcMinMax();
this.calcLabels();
}
/**
* calculates pointer X in percent
*/
private calcPointerPercent(): void
{
if (!this.coords.w_rs)
{
this.coords.p_pointer = 0;
return;
}
if (this.coords.x_pointer < 0 || isNaN(this.coords.x_pointer))
{
this.coords.x_pointer = 0;
}
else if (this.coords.x_pointer > this.coords.w_rs)
{
this.coords.x_pointer = this.coords.w_rs;
}
this.coords.p_pointer = this.toFixed(this.coords.x_pointer / this.coords.w_rs * 100);
}
private convertToRealPercent(fake): number
{
const full = 100 - this.coords.p_handle;
return fake / full * 100;
}
private convertToFakePercent(real): number
{
const full = 100 - this.coords.p_handle;
return real / 100 * full;
}
private getHandleX(): number
{
const max = 100 - this.coords.p_handle;
let x = this.toFixed(this.coords.p_pointer - this.coords.p_gap);
if (x < 0)
{
x = 0;
}
else if (x > max)
{
x = max;
}
return x;
}
private calcHandlePercent(): void
{
if (this.options.type === 'single')
{
this.coords.w_handle = this.outerWidth(this.cache.s_single, false);
}
else
{
this.coords.w_handle = this.outerWidth(this.cache.s_from, false);
}
this.coords.p_handle = this.toFixed(this.coords.w_handle / this.coords.w_rs * 100);
}
/**
* Find closest handle to pointer click
*/
private chooseHandle(real_x: number): string
{
if (this.options.type === 'single')
{
return 'single';
}
else
{
const m_point = this.coords.p_from_real + ((this.coords.p_to_real - this.coords.p_from_real) / 2);
if (real_x >= m_point)
{
return this.options.to_fixed ? 'from' : 'to';
}
else
{
return this.options.from_fixed ? 'to' : 'from';
}
}
}
/**
* Measure Min and Max labels width in percent
*/
private calcMinMax(): void
{
if (!this.coords.w_rs)
{
return;
}
this.labels.p_min = this.labels.w_min / this.coords.w_rs * 100;
this.labels.p_max = this.labels.w_max / this.coords.w_rs * 100;
}
/**
* Measure labels width and X in percent
*/
private calcLabels(): void
{
if (!this.coords.w_rs || this.options.hide_from_to)
{
return;
}
if (this.options.type === 'single')
{
this.labels.w_single = this.outerWidth(this.cache.single, false);
this.labels.p_single_fake = this.labels.w_single / this.coords.w_rs * 100;
this.labels.p_single_left = this.coords.p_single_fake + (this.coords.p_handle / 2) - (this.labels.p_single_fake / 2);
this.labels.p_single_left = this.checkEdges(this.labels.p_single_left, this.labels.p_single_fake);
}
else
{
this.labels.w_from = this.outerWidth(this.cache.from, false);
this.labels.p_from_fake = this.labels.w_from / this.coords.w_rs * 100;
this.labels.p_from_left = this.coords.p_from_fake + (this.coords.p_handle / 2) - (this.labels.p_from_fake / 2);
this.labels.p_from_left = this.toFixed(this.labels.p_from_left);
this.labels.p_from_left = this.checkEdges(this.labels.p_from_left, this.labels.p_from_fake);
this.labels.w_to = this.outerWidth(this.cache.to, false);
this.labels.p_to_fake = this.labels.w_to / this.coords.w_rs * 100;
this.labels.p_to_left = this.coords.p_to_fake + (this.coords.p_handle / 2) - (this.labels.p_to_fake / 2);
this.labels.p_to_left = this.toFixed(this.labels.p_to_left);
this.labels.p_to_left = this.checkEdges(this.labels.p_to_left, this.labels.p_to_fake);
this.labels.w_single = this.outerWidth(this.cache.single, false);
this.labels.p_single_fake = this.labels.w_single / this.coords.w_rs * 100;
this.labels.p_single_left = ((this.labels.p_from_left + this.labels.p_to_left + this.labels.p_to_fake) / 2) - (this.labels.p_single_fake / 2);
this.labels.p_single_left = this.toFixed(this.labels.p_single_left);
this.labels.p_single_left = this.checkEdges(this.labels.p_single_left, this.labels.p_single_fake);
}
}
// =============================================================================================================
// Drawings
// =============================================================================================================
/**
* Main function called in request animation frame
* to update everything
*/
private updateScene(): void
{
if (this.raf_id)
{
cancelAnimationFrame(this.raf_id);
this.raf_id = null;
}
clearTimeout(this.update_tm);
this.update_tm = null;
if (!this.options)
{
return;
}
this.drawHandles();
if (this.is_active)
{
this.raf_id = requestAnimationFrame(this.updateScene.bind(this));
}
else
{
this.update_tm = setTimeout(this.updateScene.bind(this), 300);
}
}
/**
* Draw handles
*/
private drawHandles(): void
{
this.coords.w_rs = this.outerWidth(this.cache.rs, false);
if (!this.coords.w_rs)
{
return;
}
if (this.coords.w_rs !== this.coords.w_rs_old)
{
this.target = 'base';
this.is_resize = true;
}
if (this.coords.w_rs !== this.coords.w_rs_old || this.force_redraw)
{
this.setMinMax();
this.calc(true);
this.drawLabels();
if (this.options.grid)
{
this.calcGridMargin();
this.calcGridLabels();
}
this.force_redraw = true;
this.coords.w_rs_old = this.coords.w_rs;
this.drawShadow();
}
if (!this.coords.w_rs)
{
return;
}
if (!this.dragging && !this.force_redraw && !this.is_key)
{
return;
}
if (this.old_from !== this.result.from || this.old_to !== this.result.to || this.force_redraw || this.is_key)
{
this.drawLabels();
this.cache.bar.style.left = this.coords.p_bar_x + '%';
this.cache.bar.style.width = this.coords.p_bar_w + '%';
if (this.options.type === 'single')
{
this.cache.bar.style.left = 0;
this.cache.bar.style.width = this.coords.p_bar_w + this.coords.p_bar_x + '%';
this.cache.s_single.style.left = this.coords.p_single_fake + '%';
this.cache.single.style.left = this.labels.p_single_left + '%';
}
else
{
this.cache.s_from.style.left = this.coords.p_from_fake + '%';
this.cache.s_to.style.left = this.coords.p_to_fake + '%';
if (this.old_from !== this.result.from || this.force_redraw)
{
this.cache.from.style.left = this.labels.p_from_left + '%';
}
if (this.old_to !== this.result.to || this.force_redraw)
{
this.cache.to.style.left = this.labels.p_to_left + '%';
}
this.cache.single.style.left = this.labels.p_single_left + '%';
}
this.writeToInput();
if ((this.old_from !== this.result.from || this.old_to !== this.result.to) && !this.is_start)
{
// Override this event in component
// this.trigger('change', this.cache.input);
this.trigger('input', this.cache.input);
}
this.old_from = this.result.from;
this.old_to = this.result.to;
// callbacks call
if (!this.is_resize && !this.is_update && !this.is_start && !this.is_finish)
{
this.callOnChange();
}
if (this.is_key || this.is_click)
{
this.is_key = false;
this.is_click = false;
this.callOnFinish();
}
this.is_update = false;
this.is_resize = false;
this.is_finish = false;
}
this.is_start = false;
this.is_key = false;
this.is_click = false;
this.force_redraw = false;
}
/**
* Draw labels
* measure labels collisions
* collapse close labels
*/
private drawLabels(): void
{
if (!this.options)
{
return;
}
const values_num = this.options.values.length;
const p_values = this.options.p_values;
let text_single;
let text_from;
let text_to;
let from_pretty;
let to_pretty;
if (this.options.hide_from_to)
{
return;
}
if (this.options.type === 'single')
{
if (values_num)
{
text_single = this.decorate(p_values[this.result.from]);
this.cache.single.innerHTML = text_single;
}
else
{
from_pretty = this._prettify(this.result.from);
text_single = this.decorate(from_pretty, this.result.from);
this.cache.single.innerHTML = text_single;
}
this.calcLabels();
if (this.labels.p_single_left < this.labels.p_min + 1)
{
this.cache.min.style.visibility = 'hidden';
}
else
{
this.cache.min.style.visibility = 'visible';
}
if (this.labels.p_single_left + this.labels.p_single_fake > 100 - this.labels.p_max - 1)
{
this.cache.max.style.visibility = 'hidden';
}
else
{
this.cache.max.style.visibility = 'visible';
}
}
else
{
if (values_num)
{
if (this.options.decorate_both)
{
text_single = this.decorate(p_values[this.result.from]);
text_single += this.options.values_separator;
text_single += this.decorate(p_values[this.result.to]);
}
else
{
text_single = this.decorate(p_values[this.result.from] + this.options.values_separator + p_values[this.result.to]);
}
text_from = this.decorate(p_values[this.result.from]);
text_to = this.decorate(p_values[this.result.to]);
this.cache.single.innerHTML = text_single;
this.cache.from.innerHTML = text_from;
this.cache.to.innerHTML = text_to;
}
else
{
from_pretty = this._prettify(this.result.from);
to_pretty = this._prettify(this.result.to);
if (this.options.decorate_both)
{
text_single = this.decorate(from_pretty, this.result.from);
text_single += this.options.values_separator;
text_single += this.decorate(to_pretty, this.result.to);
}
else
{
text_single = this.decorate(from_pretty + this.options.values_separator + to_pretty, this.result.to);
}
text_from = this.decorate(from_pretty, this.result.from);
text_to = this.decorate(to_pretty, this.result.to);
this.cache.single.innerHTML = text_single;
this.cache.from.innerHTML = text_from;
this.cache.to.innerHTML = text_to;
}
this.calcLabels();
const min = Math.min(this.labels.p_single_left, this.labels.p_from_left),
single_left = this.labels.p_single_left + this.labels.p_single_fake,
to_left = this.labels.p_to_left + this.labels.p_to_fake;
let max = Math.max(single_left, to_left);
if (this.labels.p_from_left + this.labels.p_from_fake >= this.labels.p_to_left)
{
this.cache.from.style.visibility = 'hidden';
this.cache.to.style.visibility = 'hidden';
this.cache.single.style.visibility = 'visible';
if (this.result.from === this.result.to)
{
if (this.target === 'from')
{
this.cache.from.style.visibility = 'visible';
}
else if (this.target === 'to')
{
this.cache.to.style.visibility = 'visible';
}
else if (!this.target)
{
this.cache.from.style.visibility = 'visible';
}
this.cache.single.style.visibility = 'hidden';
max = to_left;
}
else
{
this.cache.from.style.visibility = 'hidden';
this.cache.to.style.visibility = 'hidden';
this.cache.single.style.visibility = 'visible';
max = Math.max(single_left, to_left);
}
}
else
{
this.cache.from.style.visibility = 'visible';
this.cache.to.style.visibility = 'visible';
this.cache.single.style.visibility = 'hidden';
}
if (min < this.labels.p_min + 1)
{
this.cache.min.style.visibility = 'hidden';
}
else
{
this.cache.min.style.visibility = 'visible';
}
if (max > 100 - this.labels.p_max - 1)
{
this.cache.max.style.visibility = 'hidden';
}
else
{
this.cache.max.style.visibility = 'visible';
}
}
}
/**
* Draw shadow intervals
*/
private drawShadow(): void
{
const o = this.options,
c = this.cache,
is_from_min = typeof o.from_min === 'number' && !isNaN(o.from_min),
is_from_max = typeof o.from_max === 'number' && !isNaN(o.from_max),
is_to_min = typeof o.to_min === 'number' && !isNaN(o.to_min),
is_to_max = typeof o.to_max === 'number' && !isNaN(o.to_max);
let from_min, from_max, to_min, to_max;
if (o.type === 'single')
{
if (o.from_shadow && (is_from_min || is_from_max))
{
from_min = this.convertToPercent(is_from_min ? o.from_min : o.min);
from_max = this.convertToPercent(is_from_max ? o.from_max : o.max) - from_min;
from_min = this.toFixed(from_min - (this.coords.p_handle / 100 * from_min));
from_max = this.toFixed(from_max - (this.coords.p_handle / 100 * from_max));
from_min = from_min + (this.coords.p_handle / 2);
c.shad_single.style.display = 'block';
c.shad_single.style.left = from_min + '%';
c.shad_single.style.width = from_max + '%';
}
else
{
c.shad_single.style.display = 'none';
}
}
else
{
if (o.from_shadow && (is_from_min || is_from_max))
{
from_min = this.convertToPercent(is_from_min ? o.from_min : o.min);
from_max = this.convertToPercent(is_from_max ? o.from_max : o.max) - from_min;
from_min = this.toFixed(from_min - (this.coords.p_handle / 100 * from_min));
from_max = this.toFixed(from_max - (this.coords.p_handle / 100 * from_max));
from_min = from_min + (this.coords.p_handle / 2);
c.shad_from.style.display = 'block';
c.shad_from.style.left = from_min + '%';
c.shad_from.style.width = from_max + '%';
}
else
{
c.shad_from.style.display = 'none';
}
if (o.to_shadow && (is_to_min || is_to_max))
{
to_min = this.convertToPercent(is_to_min ? o.to_min : o.min);
to_max = this.convertToPercent(is_to_max ? o.to_max : o.max) - to_min;
to_min = this.toFixed(to_min - (this.coords.p_handle / 100 * to_min));
to_max = this.toFixed(to_max - (this.coords.p_handle / 100 * to_max));
to_min = to_min + (this.coords.p_handle / 2);
c.shad_to.style.display = 'block';
c.shad_to.style.left = to_min + '%';
c.shad_to.style.width = to_max + '%';
}
else
{
c.shad_to.style.display = 'none';
}
}
}
/**
* Write values to input element
*/
private writeToInput(): void
{
if (this.options.type === 'single')
{
if (this.options.values.length)
{
this.cache.input.value = this.result.from_value;
}
else
{
this.cache.input.value = this.result.from;
}
this.cache.input.dataset.from = this.result.from;
}
else
{
if (this.options.values.length)
{
this.cache.input.value = this.result.from_value + this.options.input_values_separator + this.result.to_value;
}
else
{
this.cache.input.value = this.result.from + this.options.input_values_separator + this.result.to;
}
this.cache.input.dataset.from = this.result.from;
this.cache.input.dataset.to = this.result.to;
}
}
// =============================================================================================================
// Callbacks
// =============================================================================================================
private callOnStart(): void
{
this.writeToInput();
if (this.options.onStart && typeof this.options.onStart === 'function')
{
if (this.options.scope)
{
this.options.onStart.call(this.options.scope, this.result);
}
else
{
this.options.onStart(this.result);
}
}
}
private callOnChange(): void
{
this.writeToInput();
if (this.options.onChange && typeof this.options.onChange === 'function')
{
if (this.options.scope)
{
this.options.onChange.call(this.options.scope, this.result);
}
else
{
this.options.onChange(this.result);
}
}
}
private callOnFinish(): void
{
this.writeToInput();
if (this.options.onFinish && typeof this.options.onFinish === 'function')
{
if (this.options.scope)
{
this.options.onFinish.call(this.options.scope, this.result);
}
else
{
this.options.onFinish(this.result);
}
}
}
private callOnUpdate(): void
{
this.writeToInput();
if (this.options.onUpdate && typeof this.options.onUpdate === 'function')
{
if (this.options.scope)
{
this.options.onUpdate.call(this.options.scope, this.result);
}
else
{
this.options.onUpdate(this.result);
}
}
}
// =============================================================================================================
// Service methods
// =============================================================================================================
private toggleInput(): void
{
this.cache.input.classList.toggle('irs-hidden-input');
if (this.has_tab_index)
{
this.cache.input.tabindex = -1;
}
else
{
this.cache.input.removeAttribute('tabindex');
}
this.has_tab_index = !this.has_tab_index;
}
/**
* Convert real value to percent
*/
private convertToPercent(value: number, no_min = false): number
{
const diapason = this.options.max - this.options.min,
one_percent = diapason / 100;
let val, percent;
if (!diapason)
{
this.no_diapason = true;
return 0;
}
if (no_min)
{
val = value;
}
else
{
val = value - this.options.min;
}
percent = val / one_percent;
return this.toFixed(percent);
}
/**
* Convert percent to real values
*/
private convertToValue(percent: number): number
{
let min = this.options.min,
max = this.options.max,
min_length, max_length,
avg_decimals = 0,
abs = 0;
const min_decimals = min.toString().split('.')[1],
max_decimals = max.toString().split('.')[1];
if (percent === 0)
{
return this.options.min;
}
if (percent === 100)
{
return this.options.max;
}
if (min_decimals)
{
min_length = min_decimals.length;
avg_decimals = min_length;
}
if (max_decimals)
{
max_length = max_decimals.length;
avg_decimals = max_length;
}
if (min_length && max_length)
{
avg_decimals = (min_length >= max_length) ? min_length : max_length;
}
if (min < 0)
{
abs = Math.abs(min);
min = +(min + abs).toFixed(avg_decimals);
max = +(max + abs).toFixed(avg_decimals);
}
let number = ((max - min) / 100 * percent) + min,
result;
const string = this.options.step.toString().split('.')[1];
if (string)
{
number = +number.toFixed(string.length);
}
else
{
number = number / this.options.step;
number = number * this.options.step;
number = +number.toFixed(0);
}
if (abs)
{
number -= abs;
}
if (string)
{
result = +number.toFixed(string.length);
}
else
{
result = this.toFixed(number);
}
if (result < this.options.min)
{
result = this.options.min;
}
else if (result > this.options.max)
{
result = this.options.max;
}
return result;
}
/**
* Round percent value with step
*/
private calcWithStep(percent: number): number
{
let rounded = Math.round(percent / this.coords.p_step) * this.coords.p_step;
if (rounded > 100)
{
rounded = 100;
}
if (percent === 100)
{
rounded = 100;
}
return this.toFixed(rounded);
}
private checkMinInterval(p_current, p_next, type): number
{
const o = this.options;
let current, next;
if (!o.min_interval)
{
return p_current;
}
current = this.convertToValue(p_current);
next = this.convertToValue(p_next);
if (type === 'from')
{
if (next - current < o.min_interval)
{
current = next - o.min_interval;
}
}
else
{
if (current - next < o.min_interval)
{
current = next + o.min_interval;
}
}
return this.convertToPercent(current);
}
private checkMaxInterval(p_current, p_next, type): number
{
const o = this.options;
let current, next;
if (!o.max_interval)
{
return p_current;
}
current = this.convertToValue(p_current);
next = this.convertToValue(p_next);
if (type === 'from')
{
if (next - current > o.max_interval)
{
current = next - o.max_interval;
}
}
else
{
if (current - next > o.max_interval)
{
current = next + o.max_interval;
}
}
return this.convertToPercent(current);
}
private checkDiapason(p_num: number, min: number, max: number)
{
let num = this.convertToValue(p_num);
const o = this.options;
if (typeof min !== 'number')
{
min = o.min;
}
if (typeof max !== 'number')
{
max = o.max;
}
if (num < min)
{
num = min;
}
if (num > max)
{
num = max;
}
return this.convertToPercent(num);
}
private toFixed(num): number
{
num = num.toFixed(20);
return +num;
}
private _prettify(num: number): string
{
if (!this.options.prettify_enabled)
{
return num.toString();
}
if (this.options.prettify && typeof this.options.prettify === 'function')
{
return this.options.prettify(num);
}
else
{
return this.prettify(num);
}
}
private prettify(num: number): string
{
const n = num.toString();
return n.replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, '$1' + this.options.prettify_separator);
}
private checkEdges(left: number, width: number): number
{
if (!this.options.force_edges)
{
return this.toFixed(left);
}
if (left < 0)
{
left = 0;
}
else if (left > 100 - width)
{
left = 100 - width;
}
return this.toFixed(left);
}
private validate(): void
{
const o = this.options,
r = this.result,
v = o.values,
vl = v.length;
let value, i;
if (typeof o.min === 'string') o.min = +o.min;
if (typeof o.max === 'string') o.max = +o.max;
if (typeof o.from === 'string') o.from = +o.from;
if (typeof o.to === 'string') o.to = +o.to;
if (typeof o.step === 'string') o.step = +o.step;
if (typeof o.from_min === 'string') o.from_min = +o.from_min;
if (typeof o.from_max === 'string') o.from_max = +o.from_max;
if (typeof o.to_min === 'string') o.to_min = +o.to_min;
if (typeof o.to_max === 'string') o.to_max = +o.to_max;
if (typeof o.grid_num === 'string') o.grid_num = +o.grid_num;
if (o.max < o.min)
{
o.max = o.min;
}
if (vl)
{
o.p_values = [];
o.min = 0;
o.max = vl - 1;
o.step = 1;
o.grid_num = o.max;
o.grid_snap = true;
for (i = 0; i < vl; i++)
{
value = +v[i];
if (!isNaN(value))
{
console.log({ v, i, value });
v[i] = value;
value = this._prettify(value);
}
else
{
value = v[i];
}
o.p_values.push(value);
}
}
if (typeof o.from !== 'number' || isNaN(o.from))
{
o.from = o.min;
}
if (typeof o.to !== 'number' || isNaN(o.to))
{
o.to = o.max;
}
if (o.type === 'single')
{
if (o.from < o.min) o.from = o.min;
if (o.from > o.max) o.from = o.max;
}
else
{
if (o.from < o.min) o.from = o.min;
if (o.from > o.max) o.from = o.max;
if (o.to < o.min) o.to = o.min;
if (o.to > o.max) o.to = o.max;
if (this.update_check.from)
{
if (this.update_check.from !== o.from)
{
if (o.from > o.to) o.from = o.to;
}
if (this.update_check.to !== o.to)
{
if (o.to < o.from) o.to = o.from;
}
}
if (o.from > o.to) o.from = o.to;
if (o.to < o.from) o.to = o.from;
}
if (typeof o.step !== 'number' || isNaN(o.step) || !o.step || o.step < 0)
{
o.step = 1;
}
if (typeof o.from_min === 'number' && o.from < o.from_min)
{
o.from = o.from_min;
}
if (typeof o.from_max === 'number' && o.from > o.from_max)
{
o.from = o.from_max;
}
if (typeof o.to_min === 'number' && o.to < o.to_min)
{
o.to = o.to_min;
}
if (typeof o.to_max === 'number' && o.from > o.to_max)
{
o.to = o.to_max;
}
if (r)
{
if (r.min !== o.min)
{
r.min = o.min;
}
if (r.max !== o.max)
{
r.max = o.max;
}
if (r.from < r.min || r.from > r.max)
{
r.from = o.from;
}
if (r.to < r.min || r.to > r.max)
{
r.to = o.to;
}
}
if (typeof o.min_interval !== 'number' || isNaN(o.min_interval) || !o.min_interval || o.min_interval < 0)
{
o.min_interval = 0;
}
if (typeof o.max_interval !== 'number' || isNaN(o.max_interval) || !o.max_interval || o.max_interval < 0)
{
o.max_interval = 0;
}
if (o.min_interval && o.min_interval > o.max - o.min)
{
o.min_interval = o.max - o.min;
}
if (o.max_interval && o.max_interval > o.max - o.min)
{
o.max_interval = o.max - o.min;
}
}
private decorate(num: number|string, original?: number): string
{
let decorated = '';
const o = this.options;
if (o.prefix)
{
decorated += o.prefix;
}
decorated += num;
if (o.max_postfix)
{
if (o.values.length && num === o.p_values[o.max])
{
decorated += o.max_postfix;
if (o.postfix)
{
decorated += ' ';
}
}
else if (original === o.max)
{
decorated += o.max_postfix;
if (o.postfix)
{
decorated += ' ';
}
}
}
if (o.postfix)
{
decorated += o.postfix;
}
return decorated;
}
private updateFrom(): void
{
this.result.from = this.options.from;
this.result.from_percent = this.convertToPercent(this.result.from);
this.result.from_pretty = this._prettify(this.result.from);
if (this.options.values)
{
this.result.from_value = this.options.values[this.result.from];
}
}
private updateTo(): void
{
this.result.to = this.options.to;
this.result.to_percent = this.convertToPercent(this.result.to);
this.result.to_pretty = this._prettify(this.result.to);
if (this.options.values)
{
this.result.to_value = this.options.values[this.result.to];
}
}
private updateResult(): void
{
this.result.min = this.options.min;
this.result.max = this.options.max;
this.updateFrom();
this.updateTo();
}
// =============================================================================================================
// Grid
// =============================================================================================================
private appendGrid(): void
{
if (!this.options.grid)
{
return;
}
const o = this.options,
total = o.max - o.min;
let big_num = o.grid_num,
big_p = 0,
small_max = 4,
big_w = 0,
small_w = 0,
html = '',
i, z, local_small_max, small_p, result;
this.calcGridMargin();
if (o.grid_snap)
{
big_num = total / o.step;
}
if (big_num > 50) big_num = 50;
big_p = this.toFixed(100 / big_num);
if (big_num > 4)
{
small_max = 3;
}
if (big_num > 7)
{
small_max = 2;
}
if (big_num > 14)
{
small_max = 1;
}
if (big_num > 28)
{
small_max = 0;
}
for (i = 0; i < big_num + 1; i++)
{
local_small_max = small_max;
big_w = this.toFixed(big_p * i);
if (big_w > 100)
{
big_w = 100;
}
this.coords.big[i] = big_w;
small_p = (big_w - (big_p * (i - 1))) / (local_small_max + 1);
for (z = 1; z <= local_small_max; z++)
{
if (big_w === 0)
{
break;
}
small_w = this.toFixed(big_w - (small_p * z));
html += '<span class="irs-grid-pol small" style="left: ' + small_w + '%"></span>';
}
html += '<span class="irs-grid-pol" style="left: ' + big_w + '%"></span>';
result = this.convertToValue(big_w);
if (o.values.length)
{
result = o.p_values[result];
}
else
{
result = this._prettify(result);
}
html += '<span class="irs-grid-text js-grid-text-' + i + '" style="left: ' + big_w + '%">' + result + '</span>';
}
this.coords.big_num = Math.ceil(big_num + 1);
this.cache.cont.classList.toggle('irs-with-grid');
this.cache.grid.innerHTML = html;
this.cacheGridLabels();
}
private cacheGridLabels(): void
{
let $label, i;
const num = this.coords.big_num;
for (i = 0; i < num; i++)
{
$label = this.cache.grid.querySelector('.js-grid-text-' + i);
this.cache.grid_labels.push($label);
}
this.calcGridLabels();
}
private calcGridLabels(): void
{
const start = [], finish = [], num = this.coords.big_num;
let label, i;
for (i = 0; i < num; i++)
{
this.coords.big_w[i] = this.outerWidth(this.cache.grid_labels[i], false);
this.coords.big_p[i] = this.toFixed(this.coords.big_w[i] / this.coords.w_rs * 100);
this.coords.big_x[i] = this.toFixed(this.coords.big_p[i] / 2);
start[i] = this.toFixed(this.coords.big[i] - this.coords.big_x[i]);
finish[i] = this.toFixed(start[i] + this.coords.big_p[i]);
}
if (this.options.force_edges)
{
if (start[0] < -this.coords.grid_gap)
{
start[0] = -this.coords.grid_gap;
finish[0] = this.toFixed(start[0] + this.coords.big_p[0]);
this.coords.big_x[0] = this.coords.grid_gap;
}
if (finish[num - 1] > 100 + this.coords.grid_gap)
{
finish[num - 1] = 100 + this.coords.grid_gap;
start[num - 1] = this.toFixed(finish[num - 1] - this.coords.big_p[num - 1]);
this.coords.big_x[num - 1] = this.toFixed(this.coords.big_p[num - 1] - this.coords.grid_gap);
}
}
this.calcGridCollision(2, start, finish);
this.calcGridCollision(4, start, finish);
for (i = 0; i < num; i++)
{
label = this.cache.grid_labels[i];
if (this.coords.big_x[i] !== Number.POSITIVE_INFINITY)
{
label.style.marginLeft = -this.coords.big_x[i] + '%';
}
}
}
private calcGridCollision(step, start, finish): void
{
let i, next_i, label;
const num = this.coords.big_num;
for (i = 0; i < num; i += step)
{
next_i = i + (step / 2);
if (next_i >= num)
{
break;
}
label = this.cache.grid_labels[next_i];
if (finish[i] <= start[next_i])
{
label.style.visibility = 'visible';
}
else
{
label.style.visibility = 'hidden';
}
}
}
private calcGridMargin(): void
{
if (!this.options.grid_margin)
{
return;
}
this.coords.w_rs = this.outerWidth(this.cache.rs, false);
if (!this.coords.w_rs)
{
return;
}
if (this.options.type === 'single')
{
this.coords.w_handle = this.outerWidth(this.cache.s_single, false);
}
else
{
this.coords.w_handle = this.outerWidth(this.cache.s_from, false);
}
this.coords.p_handle = this.toFixed(this.coords.w_handle / this.coords.w_rs * 100);
this.coords.grid_gap = this.toFixed((this.coords.p_handle / 2) - 0.1);
this.cache.grid.style.width = this.toFixed(100 - this.coords.p_handle) + '%';
this.cache.grid.style.left = this.coords.grid_gap + '%';
}
// =============================================================================================================
// Public methods
// =============================================================================================================
update(options?: any): void
{
if (!this.input)
{
return;
}
this.is_update = true;
this.options.from = this.result.from;
this.options.to = this.result.to;
this.update_check.from = this.result.from;
this.update_check.to = this.result.to;
this.options = Object.assign({}, this.options, options);
this.validate();
this.updateResult();
this.toggleInput();
this.remove();
this.init(true);
}
reset(): void
{
if (!this.input)
{
return;
}
this.updateResult();
this.update();
}
destroy(): void
{
if (!this.input)
{
return;
}
this.toggleInput();
this.cache.input.readonly = false;
// this.input.dataset.ionRangeSlider = null;
this.remove();
this.input = null;
this.options = null;
}
private unbindEvens(): void
{
if (this.no_diapason)
{
return;
}
this.cache.body.removeEventListener('touchmove', e => this.pointerMove(e));
this.cache.body.removeEventListener('mousemove', e => this.pointerMove(e));
this.cache.win.removeEventListener('touchend', e => this.pointerUp(e));
this.cache.win.removeEventListener('mouseup', e => this.pointerUp(e));
this.cache.line.removeEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.line.removeEventListener('mousedown', e => this.pointerClick('click', e));
this.cache.line.removeEventListener('focus', e => this.pointerFocus(e));
if (this.options.drag_interval && this.options.type === 'double')
{
this.cache.bar.removeEventListener('touchstart', e => this.pointerDown('both', e));
this.cache.bar.removeEventListener('mousedown', e => this.pointerDown('both', e));
}
else
{
this.cache.bar.removeEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.bar.removeEventListener('mousedown', e => this.pointerClick('click', e));
}
if (this.options.type === 'single')
{
this.cache.single.removeEventListener('touchstart', e => this.pointerDown('single', e));
this.cache.s_single.removeEventListener('touchstart', e => this.pointerDown('single', e));
this.cache.shad_single.removeEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.single.removeEventListener('mousedown', e => this.pointerDown('single', e));
this.cache.s_single.removeEventListener('mousedown', e => this.pointerDown('single', e));
this.cache.shad_single.removeEventListener('mousedown', e => this.pointerClick.bind('click', e));
if (this.cache.edge)
{
this.cache.edge.removeEventListener('mousedown', e => this.pointerClick('click', e));
}
}
else
{
this.cache.single.removeEventListener('touchstart', e => this.pointerDown(null, e));
this.cache.single.removeEventListener('mousedown', e => this.pointerDown(null, e));
this.cache.from.removeEventListener('touchstart', e => this.pointerDown('from', e));
this.cache.s_from.removeEventListener('touchstart', e => this.pointerDown('from', e));
this.cache.to.removeEventListener('touchstart', e => this.pointerDown('to', e));
this.cache.s_to.removeEventListener('touchstart', e => this.pointerDown('to', e));
this.cache.shad_from.removeEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.shad_to.removeEventListener('touchstart', e => this.pointerClick('click', e));
this.cache.from.removeEventListener('mousedown', e => this.pointerDown('from', e));
this.cache.s_from.removeEventListener('mousedown', e => this.pointerDown('from', e));
this.cache.to.removeEventListener('mousedown', e => this.pointerDown('to', e));
this.cache.s_to.removeEventListener('mousedown', e => this.pointerDown('to', e));
this.cache.shad_from.removeEventListener('mousedown', e => this.pointerClick('click', e));
this.cache.shad_to.removeEventListener('mousedown', e => this.pointerClick('click', e));
}
if (this.options.keyboard)
{
this.cache.line.removeEventListener('keydown', e => this.key('keyboard', e));
}
if (this.getIsOldIe())
{
this.cache.body.removeEventListener('mouseup', e => this.pointerUp(e));
this.cache.body.removeEventListener('mouseleave', e => this.pointerUp(e));
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment