/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import { styles } from './nile-slider.css'; import NileElement from '../internal/nile-element'; import { classMap } from 'lit/directives/class-map.js'; import { html, CSSResultArray, TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { TooltipPosition } from './types/nile-slider.types'; import { NileSliderEvents } from './types/nile-slider.enums'; import { rangeSlider, singleSlider, lableContaier } from './nile-slider.template'; import { handleSingleSlider, handleRangeOne, handleRangeTwo, getHtmlElements, removeMoveListeners, valueToPercent, handleTwoThumbClick, addMoveListeners } from './utils/nile-slider.utils' /** * Nile slider component. * * @tag nile-slider */ @customElement('nile-slider') export class NileSlider extends NileElement { public static get styles(): CSSResultArray { return [styles]; } @property({ type: Number }) minValue: number = 0; @property({ type: Number }) value: number = this.minValue; @property({ type: Number }) maxValue: number = 100; @property({ type: Number }) rangeOneValue: number = this.minValue; @property({ type: Number }) rangeTwoValue: number = this.maxValue; @property({ type: Boolean}) showLabel: boolean = false; @property({ type: String }) labelStart: string = ""; @property({ type: String }) labelEnd: string = ""; @property({ type: Boolean}) rangeSlider: boolean = false; @property({ type: String }) labelPosition: string = "top"; @property({ type: String }) tooltipPosition: TooltipPosition = "top"; public buttonOne!: HTMLElement; public buttonTwo!: HTMLElement; public range!: HTMLElement; public completed!: HTMLElement; public activeThumb: 'one' | 'two' | null = null; connectedCallback(): void { super.connectedCallback(); this.emit(NileSliderEvents.NILE_INIT); if(this.rangeSlider) { this.checkRangeValidity(); } } firstUpdated(changedProps: Map): void { getHtmlElements(this); if (!this.rangeSlider && changedProps.has('value')) { this.value = this.checkValueValidity(this.value, this.minValue, this.maxValue); const percent = valueToPercent(this.value, this); this.buttonOne.style.left = `${percent}%`; this.completed.style.width = `${percent}%`; } else if (this.rangeSlider) { const percentOne = valueToPercent(this.rangeOneValue, this); const percentTwo = valueToPercent(this.rangeTwoValue, this); if (this.buttonOne) this.buttonOne.style.left = `${percentOne}%`; if (this.buttonTwo) this.buttonTwo.style.left = `${percentTwo}%`; const left = Math.min(percentOne, percentTwo); const width = Math.abs(percentTwo - percentOne); this.completed.style.left = `${left}%`; this.completed.style.width = `${width}%`; } } checkValueValidity(value: number, min: number, max: number): number { if(max - min <= 1) { return Math.max(min, Math.min(value, max)); } return Math.floor(Math.max(min, Math.min(value, max))); } checkRangeValidity(): void { if(this.rangeTwoValue > this.maxValue) { this.rangeTwoValue = this.maxValue; } else if(this.rangeTwoValue < this.rangeOneValue) { this.rangeTwoValue = this.rangeOneValue; } if(this.rangeOneValue < this.minValue) { this.rangeOneValue = this.minValue; } else if(this.rangeOneValue > this.rangeTwoValue) { this.rangeOneValue = this.rangeTwoValue; } } public onMouseMove = (e: MouseEvent): void => { const rect = this.range.getBoundingClientRect(); if (!this.rangeSlider) { handleSingleSlider(e, rect, this); } else { if (this.activeThumb === 'one') { handleRangeOne(e, rect, this); } else if (this.activeThumb === 'two') { handleRangeTwo(e, rect, this); } const start = valueToPercent(this.rangeOneValue, this); const end = valueToPercent(this.rangeTwoValue, this); const left = Math.min(start, end); const width = Math.abs(end - start); if(this.rangeTwoValue >= this.rangeOneValue) { this.completed.style.left = `${left}%`; this.completed.style.width = `${width}%`; } } }; public onMouseUp = (): void => { if(!this.rangeSlider) this.emit(NileSliderEvents.NILE_CHANGE_END, { value: this.value }); else { this.emit(NileSliderEvents.NILE_BUTTON_FIRST_CHANGE_END, { value: this.rangeOneValue }); this.emit(NileSliderEvents.NILE_BUTTON_LAST_CHANGE_END, { value: this.rangeTwoValue }); } this.activeThumb = null; removeMoveListeners(this); }; public onMouseDown = (e: MouseEvent): void => { const rect = this.range.getBoundingClientRect(); const clickX = e.clientX - rect.left; const percent = (clickX / rect.width) * 100; const value = this.minValue + (percent / 100) * (this.maxValue - this.minValue); if (!this.rangeSlider) { handleSingleSlider(e, rect, this); this.activeThumb = 'one'; addMoveListeners(this); } else { handleTwoThumbClick(value, this); const distToOne = Math.abs(value - this.rangeOneValue); const distToTwo = Math.abs(value - this.rangeTwoValue); this.activeThumb = distToOne <= distToTwo ? 'one' : 'two'; addMoveListeners(this); } }; public render(): TemplateResult { const hasLabels = this.showLabel; const ariaLabelledby = hasLabels ? 'label-start label-end' : undefined; return html`
${hasLabels ? lableContaier(this) : html``}
${this.rangeSlider ? rangeSlider(this) : singleSlider(this)}
`; } disconnectedCallback(): void { super.disconnectedCallback(); this.emit(NileSliderEvents.NILE_DESTROY); } } export default NileSlider; declare global { interface HTMLElementTagNameMap { 'nile-slider': NileSlider; } }