preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
282 lines (226 loc) • 7.21 kB
text/typescript
/*
* HSRangeSlider
* @version: 4.2.0
* @author: Preline Labs Ltd.
* @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html)
* Copyright 2024 Preline Labs Ltd.
*/
import type { cssClasses, target } from 'nouislider';
import {
IRangeSlider,
IRangeSliderCssClassesObject,
IRangeSliderOptions,
} from './interfaces';
import HSBasePlugin from '../base-plugin';
class HSRangeSlider
extends HSBasePlugin<IRangeSliderOptions>
implements IRangeSlider
{
private readonly concatOptions: IRangeSliderOptions;
private readonly wrapper: HTMLElement | null;
private readonly currentValue: HTMLElement[] | null;
private format: any | null;
private readonly icons: {
handle?: string;
};
constructor(el: HTMLElement, options?: IRangeSliderOptions, events?: {}) {
super(el, options || ({} as IRangeSliderOptions), events);
if (!HSRangeSlider.isAvailable())
throw new Error(HSRangeSlider.unavailableMessage);
const data = el.getAttribute('data-hs-range-slider');
const dataOptions: IRangeSliderOptions = data ? JSON.parse(data) : {};
this.concatOptions = {
...dataOptions,
...options,
cssClasses: {
...noUiSlider.cssClasses,
...this.processClasses(dataOptions.cssClasses),
},
};
this.wrapper =
this.concatOptions.wrapper ||
el.closest('.hs-range-slider-wrapper') ||
null;
this.currentValue = this.concatOptions.currentValue
? Array.from(this.concatOptions.currentValue)
: Array.from(
this.wrapper?.querySelectorAll('.hs-range-slider-current-value') ||
[],
);
this.icons = this.concatOptions.icons || {};
this.init();
}
get formattedValue() {
const values: number | string | (string | number)[] = (
this.el as target
).noUiSlider.get();
if (Array.isArray(values) && this.format) {
const updateValues: (string | number)[] = [];
values.forEach((val) => {
updateValues.push(this.format.to(val));
});
return updateValues;
} else if (this.format) {
return this.format.to(values);
} else {
return values;
}
}
private processClasses(cl: typeof cssClasses) {
const mergedClasses: IRangeSliderCssClassesObject = {};
Object.keys(cl).forEach((key: keyof typeof noUiSlider.cssClasses) => {
if (key) mergedClasses[key] = `${noUiSlider.cssClasses[key]} ${cl[key]}`;
});
return mergedClasses as typeof cssClasses;
}
private init() {
this.createCollection(window.$hsRangeSliderCollection, this);
if (
typeof this.concatOptions?.formatter === 'object'
? this.concatOptions?.formatter?.type ===
'thousandsSeparatorAndDecimalPoints'
: this.concatOptions?.formatter === 'thousandsSeparatorAndDecimalPoints'
) {
this.thousandsSeparatorAndDecimalPointsFormatter();
} else if (
typeof this.concatOptions?.formatter === 'object'
? this.concatOptions?.formatter?.type === 'integer'
: this.concatOptions?.formatter === 'integer'
) {
this.integerFormatter();
} else if (
typeof this.concatOptions?.formatter === 'object' &&
(this.concatOptions?.formatter?.prefix ||
this.concatOptions?.formatter?.postfix)
) {
this.prefixOrPostfixFormatter();
}
noUiSlider.create(this.el, this.concatOptions);
if (this.currentValue && this.currentValue.length > 0) {
(this.el as target).noUiSlider.on(
'update',
(values: (string | number)[]) => {
this.updateCurrentValue(values);
},
);
}
if (this.concatOptions.disabled) this.setDisabled();
if (this.icons.handle) this.buildHandleIcon();
}
private formatValue(val: number | string) {
let result = '';
if (typeof this.concatOptions?.formatter === 'object') {
if (this.concatOptions?.formatter?.prefix) {
result += this.concatOptions?.formatter?.prefix;
}
result += val;
if (this.concatOptions?.formatter?.postfix) {
result += this.concatOptions?.formatter?.postfix;
}
} else result += val;
return result;
}
private integerFormatter() {
this.format = {
to: (val: number) => this.formatValue(Math.round(val)),
from: (val: string) => Math.round(+val),
};
if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format;
}
private prefixOrPostfixFormatter() {
this.format = {
to: (val: number) => this.formatValue(val),
from: (val: string) => +val,
};
if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format;
}
private thousandsSeparatorAndDecimalPointsFormatter() {
this.format = {
to: (val: number) =>
this.formatValue(
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(val),
),
from: (val: string) => parseFloat(val.replace(/,/g, '')),
};
if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format;
}
private setDisabled() {
this.el.setAttribute('disabled', 'disabled');
this.el.classList.add('disabled');
}
private buildHandleIcon() {
if (!this.icons.handle) return false;
const handle = this.el.querySelector('.noUi-handle');
if (!handle) return false;
handle.innerHTML = this.icons.handle;
}
private updateCurrentValue(values: (string | number)[]) {
if (!this.currentValue || this.currentValue.length === 0) return;
values.forEach((value, index) => {
const element = this.currentValue?.[index];
if (!element) return;
const formattedValue = this.format
? this.format.to(value).toString()
: value.toString();
if (element instanceof HTMLInputElement) {
element.value = formattedValue;
} else {
element.textContent = formattedValue;
}
});
}
// Public methods
public destroy() {
(this.el as target).noUiSlider.destroy();
this.format = null;
window.$hsRangeSliderCollection = window.$hsRangeSliderCollection.filter(
({ element }) => element.el !== this.el,
);
}
// Static methods
private static readonly unavailableMessage =
'HSRangeSlider: noUiSlider is not available, please add it to the page.';
private static isAvailable() {
return typeof noUiSlider !== 'undefined';
}
static getInstance(target: HTMLElement | string, isInstance = false) {
const elInCollection = window.$hsRangeSliderCollection.find(
(el) =>
el.element.el ===
(typeof target === 'string' ? document.querySelector(target) : target),
);
return elInCollection
? isInstance
? elInCollection
: elInCollection.element.el
: null;
}
static autoInit() {
const selector = '[data-hs-range-slider]:not(.--prevent-on-load-init)';
if (!HSRangeSlider.isAvailable()) {
if (document.querySelectorAll(selector).length) {
console.error(HSRangeSlider.unavailableMessage);
}
return;
}
if (!window.$hsRangeSliderCollection) window.$hsRangeSliderCollection = [];
if (window.$hsRangeSliderCollection) {
window.$hsRangeSliderCollection = window.$hsRangeSliderCollection.filter(
({ element }) => document.contains(element.el),
);
}
document.querySelectorAll(selector).forEach((el: HTMLElement) => {
if (
!window.$hsRangeSliderCollection.find(
(elC) => (elC?.element?.el as HTMLElement) === el,
)
) {
new HSRangeSlider(el);
}
});
}
}
export default HSRangeSlider;