import { queryOne, queryAll } from '@ecl/dom-utils';/** * @param {HTMLElement} element DOM element for component instantiation and scope * @param {Object} options * @param {String} options.rangeInputSelector Selector for the range input * @param {String} options.currentValueSelector Selector for the current value area * @param {String} options.bubbleSelector Selector for the value bubble * @param {Boolean} options.attachChangeListener Whether or not to bind change events on range * @param {Boolean} options.attachHoverListener Whether or not to bind hover events */export class Range { /** * @static * Shorthand for instance creation and initialisation. * * @param {HTMLElement} root DOM element for component instantiation and scope * * @return {Range} An instance of Range. */ static autoInit(root, { RANGE: defaultOptions = {} } = {}) { const range = new Range(root, defaultOptions); range.init(); root.ECLRange = range; return range; } constructor( element, { rangeInputSelector = '[data-ecl-range-input]', currentValueSelector = '[data-ecl-range-value-current]', bubbleSelector = '[data-ecl-range-bubble]', attachChangeListener = true, attachHoverListener = true, } = {}, ) { // Check element if (!element || element.nodeType !== Node.ELEMENT_NODE) { throw new TypeError( 'DOM element should be given to initialize this widget.', ); } this.element = element; // Options this.rangeInputSelector = rangeInputSelector; this.currentValueSelector = currentValueSelector; this.bubbleSelector = bubbleSelector; this.attachChangeListener = attachChangeListener; this.attachHoverListener = attachHoverListener; // Private variables this.rangeInput = null; this.currentValue = null; this.bubble = null; this.direction = 'ltr'; // Bind `this` for use in callbacks this.placeBubble = this.placeBubble.bind(this); this.handleChange = this.handleChange.bind(this); this.handleHoverOn = this.handleHoverOn.bind(this); this.handleHoverOff = this.handleHoverOff.bind(this); } /** * Initialise component. */ init() { if (!ECL) { throw new TypeError('Called init but ECL is not present'); } ECL.components = ECL.components || new Map(); this.rangeInput = queryOne(this.rangeInputSelector, this.element); this.currentValue = queryAll(this.currentValueSelector, this.element); this.bubble = queryOne(this.bubbleSelector, this.element); if (this.rangeInput && this.currentValue) { // Display default value this.currentValue.forEach((element) => { element.innerHTML = this.rangeInput.value; }); // Bind change and hover event on range if (this.attachChangeListener) { this.rangeInput.addEventListener('input', this.handleChange); } if (this.attachHoverListener) { this.rangeInput.addEventListener('mouseover', this.handleHoverOn); this.rangeInput.addEventListener('mouseout', this.handleHoverOff); } } // RTL this.direction = getComputedStyle(this.element).direction; // Set ecl initialized attribute this.element.setAttribute('data-ecl-auto-initialized', 'true'); ECL.components.set(this.element, this); } /** * Destroy component. */ destroy() { if (this.rangeInput && this.currentValue) { if (this.attachChangeListener) { this.rangeInput.removeEventListener('input', this.handleChange); } if (this.attachHoverListener) { this.rangeInput.removeEventListener('mouseover', this.handleHoverOn); this.rangeInput.removeEventListener('mouseout', this.handleHoverOff); } } if (this.element) { this.element.removeAttribute('data-ecl-auto-initialized'); ECL.components.delete(this.element); } } /** * Place value bubble */ placeBubble() { // Quite complex calculus here // see https://stackoverflow.com/questions/46448994/get-the-offset-position-of-an-html5-range-slider-handle // Fixed values const halfThumbWidth = 8; // 1rem / 2 const halfLabelWidth = this.bubble.offsetWidth / 2; // Get range input width const rect = this.rangeInput.getBoundingClientRect(); const center = rect.width / 2; // Get position from center const percentOfRange = (this.rangeInput.value - this.rangeInput.min) / (this.rangeInput.max - this.rangeInput.min); const valuePxPosition = percentOfRange * rect.width; const distFromCenter = valuePxPosition - center; const percentDistFromCenter = distFromCenter / center; // Calculate bubble position const offset = percentDistFromCenter * halfThumbWidth; let pos = 0; if (this.direction === 'rtl') { pos = rect.right - valuePxPosition - halfLabelWidth + offset; } else { pos = rect.left + valuePxPosition - halfLabelWidth - offset; } this.bubble.style.left = `${pos}px`; } /** * Handle mouse hover */ handleHoverOn() { // Display value bubble this.bubble.classList.add('ecl-range__bubble--visible'); this.placeBubble(); } handleHoverOff() { // Hide value bubble this.bubble.classList.remove('ecl-range__bubble--visible'); } /** * Display value when changed */ handleChange() { // Update value this.currentValue.forEach((element) => { element.innerHTML = this.rangeInput.value; }); this.placeBubble(); }}export default Range;