UNPKG

@angular-slider/ngx-slider

Version:

Self-contained, mobile friendly slider component for Angular based on angularjs-slider

1 lines 222 kB
{"version":3,"file":"angular-slider-ngx-slider.mjs","sources":["../../../src/ngx-slider/lib/options.ts","../../../src/ngx-slider/lib/pointer-type.ts","../../../src/ngx-slider/lib/change-context.ts","../../../src/ngx-slider/lib/value-helper.ts","../../../src/ngx-slider/lib/compatibility-helper.ts","../../../src/ngx-slider/lib/math-helper.ts","../../../src/ngx-slider/lib/event-listener.ts","../../../src/ngx-slider/lib/event-listener-helper.ts","../../../src/ngx-slider/lib/slider-element.directive.ts","../../../src/ngx-slider/lib/slider-handle.directive.ts","../../../src/ngx-slider/lib/slider-label.directive.ts","../../../src/ngx-slider/lib/tooltip-wrapper.component.ts","../../../src/ngx-slider/lib/tooltip-wrapper.component.html","../../../src/ngx-slider/lib/slider.component.ts","../../../src/ngx-slider/lib/slider.component.html","../../../src/ngx-slider/lib/slider.module.ts","../../../src/ngx-slider/lib/angular-slider-ngx-slider.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { PointerType } from './pointer-type';\n\n/** Label type */\nexport enum LabelType {\n /** Label above low pointer */\n Low,\n /** Label above high pointer */\n High,\n /** Label for minimum slider value */\n Floor,\n /** Label for maximum slider value */\n Ceil,\n /** Label below legend tick */\n TickValue,\n}\n\n/** Function to translate label value into text */\nexport type TranslateFunction = (value: number, label: LabelType) => string;\n/** Function to combind */\nexport type CombineLabelsFunction = (\n minLabel: string,\n maxLabel: string\n) => string;\n/** Function to provide legend */\nexport type GetLegendFunction = (value: number) => string;\nexport type GetStepLegendFunction = (step: CustomStepDefinition) => string;\n\n/** Function converting slider value to slider position */\nexport type ValueToPositionFunction = (\n val: number,\n minVal: number,\n maxVal: number\n) => number;\n\n/** Function converting slider position to slider value */\nexport type PositionToValueFunction = (\n percent: number,\n minVal: number,\n maxVal: number\n) => number;\n\n/**\n * Custom step definition\n *\n * This can be used to specify custom values and legend values for slider ticks\n */\nexport interface CustomStepDefinition {\n /** Value */\n value: number;\n /** Legend (label for the value) */\n legend?: string;\n}\n\n/** Slider options */\nexport class Options {\n /** Minimum value for a slider.\n Not applicable when using stepsArray. */\n floor?: number = 0;\n\n /** Maximum value for a slider.\n Not applicable when using stepsArray. */\n ceil?: number = null;\n\n /** Step between each value.\n Not applicable when using stepsArray. */\n step?: number = 1;\n\n /** The minimum range authorized on the slider.\n Applies to range slider only.\n When using stepsArray, expressed as index into stepsArray. */\n minRange?: number = null;\n\n /** The maximum range authorized on the slider.\n Applies to range slider only.\n When using stepsArray, expressed as index into stepsArray. */\n maxRange?: number = null;\n\n /** Set to true to have a push behavior. When the min handle goes above the max,\n the max is moved as well (and vice-versa). The range between min and max is\n defined by the step option (defaults to 1) and can also be overriden by\n the minRange option. Applies to range slider only. */\n pushRange?: boolean = false;\n\n /** The minimum value authorized on the slider.\n When using stepsArray, expressed as index into stepsArray. */\n minLimit?: number = null;\n\n /** The maximum value authorized on the slider.\n When using stepsArray, expressed as index into stepsArray. */\n maxLimit?: number = null;\n\n /** Custom translate function. Use this if you want to translate values displayed\n on the slider. */\n translate?: TranslateFunction = null;\n\n /** Custom function for combining overlapping labels in range slider.\n It takes the min and max values (already translated with translate fuction)\n and should return how these two values should be combined.\n If not provided, the default function will join the two values with\n ' - ' as separator. */\n combineLabels?: CombineLabelsFunction = null;\n\n /** Use to display legend under ticks (thus, it needs to be used along with\n showTicks or showTicksValues). The function will be called with each tick\n value and returned content will be displayed under the tick as a legend.\n If the returned value is null, then no legend is displayed under\n the corresponding tick.You can also directly provide the legend values\n in the stepsArray option. */\n getLegend?: GetLegendFunction = null;\n\n /** Use to display a custom legend of a stepItem from stepsArray.\n It will be the same as getLegend but for stepsArray. */\n getStepLegend?: GetStepLegendFunction = null;\n\n /** If you want to display a slider with non linear/number steps.\n Just pass an array with each slider value and that's it; the floor, ceil and step settings\n of the slider will be computed automatically.\n By default, the value model and valueHigh model values will be the value of the selected item\n in the stepsArray.\n They can also be bound to the index of the selected item by setting the bindIndexForStepsArray\n option to true. */\n stepsArray?: CustomStepDefinition[] = null;\n\n /** Set to true to bind the index of the selected item to value model and valueHigh model. */\n bindIndexForStepsArray?: boolean = false;\n\n /** When set to true and using a range slider, the range can be dragged by the selection bar.\n Applies to range slider only. */\n draggableRange?: boolean = false;\n\n /** Same as draggableRange but the slider range can't be changed.\n Applies to range slider only. */\n draggableRangeOnly?: boolean = false;\n\n /** Set to true to always show the selection bar before the slider handle. */\n showSelectionBar?: boolean = false;\n\n /** Set to true to always show the selection bar after the slider handle. */\n showSelectionBarEnd?: boolean = false;\n\n /** Set a number to draw the selection bar between this value and the slider handle.\n When using stepsArray, expressed as index into stepsArray. */\n showSelectionBarFromValue?: number = null;\n\n /** Only for range slider. Set to true to visualize in different colour the areas\n on the left/right (top/bottom for vertical range slider) of selection bar between the handles. */\n showOuterSelectionBars?: boolean = false;\n\n /** Set to true to hide pointer labels */\n hidePointerLabels?: boolean = false;\n\n /** Set to true to hide min / max labels */\n hideLimitLabels?: boolean = false;\n\n /** Set to false to disable the auto-hiding behavior of the limit labels. */\n autoHideLimitLabels?: boolean = true;\n\n /** Set to true to make the slider read-only. */\n readOnly?: boolean = false;\n\n /** Set to true to disable the slider. */\n disabled?: boolean = false;\n\n /** Set to true to display a tick for each step of the slider. */\n showTicks?: boolean = false;\n\n /** Set to true to display a tick and the step value for each step of the slider.. */\n showTicksValues?: boolean = false;\n\n /* The step between each tick to display. If not set, the step value is used.\n Not used when ticksArray is specified. */\n tickStep?: number = null;\n\n /* The step between displaying each tick step value.\n If not set, then tickStep or step is used, depending on which one is set. */\n tickValueStep?: number = null;\n\n /** Use to display ticks at specific positions.\n The array contains the index of the ticks that should be displayed.\n For example, [0, 1, 5] will display a tick for the first, second and sixth values. */\n ticksArray?: number[] = null;\n\n /** Used to display a tooltip when a tick is hovered.\n Set to a function that returns the tooltip content for a given value. */\n ticksTooltip?: (value: number) => string = null;\n\n /** Same as ticksTooltip but for ticks values. */\n ticksValuesTooltip?: (value: number) => string = null;\n\n /** Set to true to display the slider vertically.\n The slider will take the full height of its parent.\n Changing this value at runtime is not currently supported. */\n vertical?: boolean = false;\n\n /** Function that returns the current color of the selection bar.\n If your color won't change, don't use this option but set it through CSS.\n If the returned color depends on a model value (either value or valueHigh),\n you should use the argument passed to the function.\n Indeed, when the function is called, there is no certainty that the model\n has already been updated.*/\n getSelectionBarColor?: (minValue: number, maxValue?: number) => string = null;\n\n /** Function that returns the color of a tick. showTicks must be enabled. */\n getTickColor?: (value: number) => string = null;\n\n /** Function that returns the current color of a pointer.\n If your color won't change, don't use this option but set it through CSS.\n If the returned color depends on a model value (either value or valueHigh),\n you should use the argument passed to the function.\n Indeed, when the function is called, there is no certainty that the model has already been updated.\n To handle range slider pointers independently, you should evaluate pointerType within the given\n function where \"min\" stands for value model and \"max\" for valueHigh model values. */\n getPointerColor?: (value: number, pointerType: PointerType) => string = null;\n\n /** Handles are focusable (on click or with tab) and can be modified using the following keyboard controls:\n Left/bottom arrows: -1\n Right/top arrows: +1\n Page-down: -10%\n Page-up: +10%\n Home: minimum value\n End: maximum value\n */\n keyboardSupport?: boolean = true;\n\n /** If you display the slider in an element that uses transform: scale(0.5), set the scale value to 2\n so that the slider is rendered properly and the events are handled correctly. */\n scale?: number = 1;\n\n /** If you display the slider in an element that uses transform: rotate(90deg), set the rotate value to 90\n so that the slider is rendered properly and the events are handled correctly. Value is in degrees. */\n rotate?: number = 0;\n\n /** Set to true to force the value(s) to be rounded to the step, even when modified from the outside.\n When set to false, if the model values are modified from outside the slider, they are not rounded\n and can be between two steps. */\n enforceStep?: boolean = true;\n\n /** Set to true to force the value(s) to be normalised to allowed range (floor to ceil), even when modified from the outside.\n When set to false, if the model values are modified from outside the slider, and they are outside allowed range,\n the slider may be rendered incorrectly. However, setting this to false may be useful if you want to perform custom normalisation. */\n enforceRange?: boolean = true;\n\n /** Set to true to force the value(s) to be rounded to the nearest step value, even when modified from the outside.\n When set to false, if the model values are modified from outside the slider, and they are outside allowed range,\n the slider may be rendered incorrectly. However, setting this to false may be useful if you want to perform custom normalisation. */\n enforceStepsArray?: boolean = true;\n\n /** Set to true to prevent to user from switching the min and max handles. Applies to range slider only. */\n noSwitching?: boolean = false;\n\n /** Set to true to only bind events on slider handles. */\n onlyBindHandles?: boolean = false;\n\n /** Set to true to show graphs right to left.\n If vertical is true it will be from top to bottom and left / right arrow functions reversed. */\n rightToLeft?: boolean = false;\n\n /** Set to true to reverse keyboard navigation:\n Right/top arrows: -1\n Left/bottom arrows: +1\n Page-up: -10%\n Page-down: +10%\n End: minimum value\n Home: maximum value\n */\n reversedControls?: boolean = false;\n\n /** Set to true to keep the slider labels inside the slider bounds. */\n boundPointerLabels?: boolean = true;\n\n /** Set to true to use a logarithmic scale to display the slider. */\n logScale?: boolean = false;\n\n /** Function that returns the position on the slider for a given value.\n The position must be a percentage between 0 and 1.\n The function should be monotonically increasing or decreasing; otherwise the slider may behave incorrectly. */\n customValueToPosition?: ValueToPositionFunction = null;\n\n /** Function that returns the value for a given position on the slider.\n The position is a percentage between 0 and 1.\n The function should be monotonically increasing or decreasing; otherwise the slider may behave incorrectly. */\n customPositionToValue?: PositionToValueFunction = null;\n\n /** Precision limit for calculated values.\n Values used in calculations will be rounded to this number of significant digits\n to prevent accumulating small floating-point errors. */\n precisionLimit?: number = 12;\n\n /** Use to display the selection bar as a gradient.\n The given object must contain from and to properties which are colors. */\n selectionBarGradient?: { from: string; to: string } = null;\n\n /** Use to add a label directly to the slider for accessibility. Adds the aria-label attribute. */\n ariaLabel?: string = 'ngx-slider';\n\n /** Use instead of ariaLabel to reference the id of an element which will be used to label the slider.\n Adds the aria-labelledby attribute. */\n ariaLabelledBy?: string = null;\n\n /** Use to add a label directly to the slider range for accessibility. Adds the aria-label attribute. */\n ariaLabelHigh?: string = 'ngx-slider-max';\n\n /** Use instead of ariaLabelHigh to reference the id of an element which will be used to label the slider range.\n Adds the aria-labelledby attribute. */\n ariaLabelledByHigh?: string = null;\n\n /** Use to increase rendering performance. If the value is not provided, the slider calculates the with/height of the handle */\n handleDimension?: number = null;\n\n /** Use to increase rendering performance. If the value is not provided, the slider calculates the with/height of the bar */\n barDimension?: number = null;\n\n /** Enable/disable CSS animations */\n animate?: boolean = true;\n\n /** Enable/disable CSS animations while moving the slider */\n animateOnMove?: boolean = false;\n}\n\nexport const AllowUnsafeHtmlInSlider = new InjectionToken<boolean>(\n 'AllowUnsafeHtmlInSlider'\n);\n","/** Pointer type */\nexport enum PointerType {\n /** Low pointer */\n Min,\n /** High pointer */\n Max\n}\n","import { PointerType } from './pointer-type';\n\nexport class ChangeContext {\n value: number;\n highValue?: number;\n pointerType: PointerType;\n}\n","import { CustomStepDefinition } from './options';\n\n/**\n * Collection of functions to handle conversions/lookups of values\n */\nexport class ValueHelper {\n static isNullOrUndefined(value: any): boolean {\n return value === undefined || value === null;\n }\n\n static areArraysEqual(array1: any[], array2: any[]): boolean {\n if (array1.length !== array2.length) {\n return false;\n }\n\n for (let i: number = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false;\n }\n }\n\n return true;\n }\n\n static linearValueToPosition(val: number, minVal: number, maxVal: number): number {\n const range: number = maxVal - minVal;\n return (val - minVal) / range;\n }\n\n static logValueToPosition(val: number, minVal: number, maxVal: number): number {\n val = Math.log(val);\n minVal = Math.log(minVal);\n maxVal = Math.log(maxVal);\n const range: number = maxVal - minVal;\n return (val - minVal) / range;\n }\n\n static linearPositionToValue(percent: number, minVal: number, maxVal: number): number {\n return percent * (maxVal - minVal) + minVal;\n }\n\n static logPositionToValue(percent: number, minVal: number, maxVal: number): number {\n minVal = Math.log(minVal);\n maxVal = Math.log(maxVal);\n const value: number = percent * (maxVal - minVal) + minVal;\n return Math.exp(value);\n }\n\n static findStepIndex(modelValue: number, stepsArray: CustomStepDefinition[]): number {\n const differences: number[] = stepsArray.map((step: CustomStepDefinition): number => Math.abs(modelValue - step.value));\n\n let minDifferenceIndex: number = 0;\n for (let index: number = 0; index < stepsArray.length; index++) {\n if (differences[index] !== differences[minDifferenceIndex] && differences[index] < differences[minDifferenceIndex]) {\n minDifferenceIndex = index;\n }\n }\n\n return minDifferenceIndex;\n }\n}\n","// Declaration for ResizeObserver a new API available in some of newest browsers:\n// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare class ResizeObserver {\n}\n\n/** Helper with compatibility functions to support different browsers */\nexport class CompatibilityHelper {\n /** Workaround for TouchEvent constructor sadly not being available on all browsers (e.g. Firefox, Safari) */\n public static isTouchEvent(event: any): boolean {\n if ((window as any).TouchEvent !== undefined) {\n return event instanceof TouchEvent;\n }\n\n return event.touches !== undefined;\n }\n\n /** Detect presence of ResizeObserver API */\n public static isResizeObserverAvailable(): boolean {\n return (window as any).ResizeObserver !== undefined;\n }\n}\n","/** Helper with mathematical functions */\nexport class MathHelper {\n /* Round numbers to a given number of significant digits */\n static roundToPrecisionLimit(value: number, precisionLimit: number): number {\n return +( value.toPrecision(precisionLimit) );\n }\n\n static isModuloWithinPrecisionLimit(value: number, modulo: number, precisionLimit: number): boolean {\n const limit: number = Math.pow(10, -precisionLimit);\n return Math.abs(value % modulo) <= limit || Math.abs(Math.abs(value % modulo) - modulo) <= limit;\n }\n\n static clampToRange(value: number, floor: number, ceil: number): number {\n return Math.min(Math.max(value, floor), ceil);\n }\n}\n","import { Subject, Subscription } from 'rxjs';\n\nexport class EventListener {\n eventName: string = null;\n events: Subject<Event> = null;\n eventsSubscription: Subscription = null;\n teardownCallback: () => void = null;\n}\n","import { Renderer2 } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { throttleTime, tap } from 'rxjs/operators';\nimport { supportsPassiveEvents } from 'detect-passive-events';\n\nimport { EventListener } from './event-listener';\nimport { ValueHelper } from './value-helper';\n\n/**\n * Helper class to attach event listeners to DOM elements with debounce support using rxjs\n */\nexport class EventListenerHelper {\n constructor(private renderer: Renderer2) {\n }\n\n public attachPassiveEventListener(nativeElement: any, eventName: string, callback: (event: any) => void,\n throttleInterval?: number): EventListener {\n // Only use passive event listeners if the browser supports it\n if (supportsPassiveEvents !== true) {\n return this.attachEventListener(nativeElement, eventName, callback, throttleInterval);\n }\n\n // Angular doesn't support passive event handlers (yet), so we need to roll our own code using native functions\n const listener: EventListener = new EventListener();\n listener.eventName = eventName;\n listener.events = new Subject<Event>();\n\n const observerCallback: (event: Event) => void = (event: Event): void => {\n listener.events.next(event);\n };\n nativeElement.addEventListener(eventName, observerCallback, {passive: true, capture: false});\n\n listener.teardownCallback = (): void => {\n nativeElement.removeEventListener(eventName, observerCallback, {passive: true, capture: false});\n };\n\n listener.eventsSubscription = listener.events\n .pipe((!ValueHelper.isNullOrUndefined(throttleInterval))\n ? throttleTime(throttleInterval, undefined, { leading: true, trailing: true})\n : tap(() => {}) // no-op\n )\n .subscribe((event: Event) => {\n callback(event);\n });\n\n return listener;\n }\n\n public detachEventListener(eventListener: EventListener): void {\n if (!ValueHelper.isNullOrUndefined(eventListener.eventsSubscription)) {\n eventListener.eventsSubscription.unsubscribe();\n eventListener.eventsSubscription = null;\n }\n\n if (!ValueHelper.isNullOrUndefined(eventListener.events)) {\n eventListener.events.complete();\n eventListener.events = null;\n }\n\n if (!ValueHelper.isNullOrUndefined(eventListener.teardownCallback)) {\n eventListener.teardownCallback();\n eventListener.teardownCallback = null;\n }\n }\n\n public attachEventListener(nativeElement: any, eventName: string, callback: (event: any) => void,\n throttleInterval?: number): EventListener {\n const listener: EventListener = new EventListener();\n listener.eventName = eventName;\n listener.events = new Subject<Event>();\n\n const observerCallback: (event: Event) => void = (event: Event): void => {\n listener.events.next(event);\n };\n\n listener.teardownCallback = this.renderer.listen(nativeElement, eventName, observerCallback);\n\n listener.eventsSubscription = listener.events\n .pipe((!ValueHelper.isNullOrUndefined(throttleInterval))\n ? throttleTime(throttleInterval, undefined, { leading: true, trailing: true})\n : tap(() => {}) // no-op\n )\n .subscribe((event: Event) => { callback(event); });\n\n return listener;\n }\n}\n","import { Directive, ElementRef, Renderer2, HostBinding, ChangeDetectorRef, inject } from '@angular/core';\nimport { EventListenerHelper } from './event-listener-helper';\nimport { EventListener } from './event-listener';\nimport { ValueHelper } from './value-helper';\n\n@Directive({\n selector: '[ngxSliderElement]',\n standalone: false\n})\nexport class SliderElementDirective {\n protected elemRef = inject(ElementRef);\n protected renderer = inject(Renderer2);\n protected changeDetectionRef = inject(ChangeDetectorRef);\n\n private _position: number = 0;\n get position(): number {\n return this._position;\n }\n\n private _dimension: number = 0;\n get dimension(): number {\n return this._dimension;\n }\n\n private _alwaysHide: boolean = false;\n get alwaysHide(): boolean {\n return this._alwaysHide;\n }\n\n private _vertical: boolean = false;\n get vertical(): boolean {\n return this._vertical;\n }\n\n private _scale: number = 1;\n get scale(): number {\n return this._scale;\n }\n\n private _rotate: number = 0;\n get rotate(): number {\n return this._rotate;\n }\n\n @HostBinding('style.opacity')\n opacity: number = 1;\n\n @HostBinding('style.visibility')\n visibility: string = 'visible';\n\n @HostBinding('style.left')\n left: string = '';\n\n @HostBinding('style.bottom')\n bottom: string = '';\n\n @HostBinding('style.height')\n height: string = '';\n\n @HostBinding('style.width')\n width: string = '';\n\n @HostBinding('style.transform')\n transform: string = '';\n\n private eventListenerHelper: EventListenerHelper;\n private eventListeners: EventListener[] = [];\n\n constructor() {\n this.eventListenerHelper = new EventListenerHelper(this.renderer);\n }\n\n setAlwaysHide(hide: boolean): void {\n this._alwaysHide = hide;\n if (hide) {\n this.visibility = 'hidden';\n } else {\n this.visibility = 'visible';\n }\n }\n\n hide(): void {\n this.opacity = 0;\n }\n\n show(): void {\n if (this.alwaysHide) {\n return;\n }\n\n this.opacity = 1;\n }\n\n isVisible(): boolean {\n if (this.alwaysHide) {\n return false;\n }\n return this.opacity !== 0;\n }\n\n setVertical(vertical: boolean): void {\n this._vertical = vertical;\n if (this._vertical) {\n this.left = '';\n this.width = '';\n } else {\n this.bottom = '';\n this.height = '';\n }\n }\n\n setScale(scale: number): void {\n this._scale = scale;\n }\n\n setRotate(rotate: number): void {\n this._rotate = rotate;\n this.transform = 'rotate(' + rotate + 'deg)';\n }\n\n getRotate(): number {\n return this._rotate;\n }\n\n // Set element left/top position depending on whether slider is horizontal or vertical\n setPosition(pos: number): void {\n if (this._position !== pos && !this.isRefDestroyed()) {\n this.changeDetectionRef.markForCheck();\n }\n\n this._position = pos;\n if (this._vertical) {\n this.bottom = Math.round(pos) + 'px';\n } else {\n this.left = Math.round(pos) + 'px';\n }\n }\n\n // Calculate element's width/height depending on whether slider is horizontal or vertical\n calculateDimension(): void {\n const val: ClientRect = this.getBoundingClientRect();\n if (this.vertical) {\n this._dimension = (val.bottom - val.top) * this.scale;\n } else {\n this._dimension = (val.right - val.left) * this.scale;\n }\n }\n\n // Set element width/height depending on whether slider is horizontal or vertical\n setDimension(dim: number): void {\n if (this._dimension !== dim && !this.isRefDestroyed()) {\n this.changeDetectionRef.markForCheck();\n }\n\n this._dimension = dim;\n if (this._vertical) {\n this.height = Math.round(dim) + 'px';\n } else {\n this.width = Math.round(dim) + 'px';\n }\n }\n\n getBoundingClientRect(): ClientRect {\n return this.elemRef.nativeElement.getBoundingClientRect();\n }\n\n on(eventName: string, callback: (event: any) => void, debounceInterval?: number): void {\n const listener: EventListener = this.eventListenerHelper.attachEventListener(\n this.elemRef.nativeElement, eventName, callback, debounceInterval);\n this.eventListeners.push(listener);\n }\n\n onPassive(eventName: string, callback: (event: any) => void, debounceInterval?: number): void {\n const listener: EventListener = this.eventListenerHelper.attachPassiveEventListener(\n this.elemRef.nativeElement, eventName, callback, debounceInterval);\n this.eventListeners.push(listener);\n }\n\n off(eventName?: string): void {\n let listenersToKeep: EventListener[];\n let listenersToRemove: EventListener[];\n if (!ValueHelper.isNullOrUndefined(eventName)) {\n listenersToKeep = this.eventListeners.filter((event: EventListener) => event.eventName !== eventName);\n listenersToRemove = this.eventListeners.filter((event: EventListener) => event.eventName === eventName);\n } else {\n listenersToKeep = [];\n listenersToRemove = this.eventListeners;\n }\n\n for (const listener of listenersToRemove) {\n this.eventListenerHelper.detachEventListener(listener);\n }\n\n this.eventListeners = listenersToKeep;\n }\n\n private isRefDestroyed(): boolean {\n return ValueHelper.isNullOrUndefined(this.changeDetectionRef) || this.changeDetectionRef['destroyed'];\n }\n}\n","import { Directive, HostBinding } from '@angular/core';\nimport { SliderElementDirective } from './slider-element.directive';\n\n@Directive({\n selector: '[ngxSliderHandle]',\n standalone: false,\n})\nexport class SliderHandleDirective extends SliderElementDirective {\n @HostBinding('class.ngx-slider-active')\n active: boolean = false;\n\n @HostBinding('attr.role')\n role: string = '';\n\n @HostBinding('attr.tabindex')\n tabindex: string = '';\n\n @HostBinding('attr.aria-orientation')\n ariaOrientation: string = '';\n\n @HostBinding('attr.aria-label')\n ariaLabel: string = '';\n\n @HostBinding('attr.aria-labelledby')\n ariaLabelledBy: string = '';\n\n @HostBinding('attr.aria-valuenow')\n ariaValueNow: string = '';\n\n @HostBinding('attr.aria-valuetext')\n ariaValueText: string = '';\n\n @HostBinding('attr.aria-valuemin')\n ariaValueMin: string = '';\n\n @HostBinding('attr.aria-valuemax')\n ariaValueMax: string = '';\n\n focus(): void {\n this.elemRef.nativeElement.focus();\n }\n\n focusIfNeeded(): void {\n if (document.activeElement !== this.elemRef.nativeElement) {\n this.elemRef.nativeElement.focus();\n }\n }\n}\n","import { Directive, inject } from '@angular/core';\nimport { SliderElementDirective } from './slider-element.directive';\nimport { ValueHelper } from './value-helper';\nimport { AllowUnsafeHtmlInSlider } from './options';\n\n@Directive({\n selector: '[ngxSliderLabel]',\n standalone: false,\n})\nexport class SliderLabelDirective extends SliderElementDirective {\n private allowUnsafeHtmlInSlider = inject(AllowUnsafeHtmlInSlider, {\n optional: true,\n });\n\n private _value: string = null;\n get value(): string {\n return this._value;\n }\n\n setValue(value: string): void {\n let recalculateDimension: boolean = false;\n\n if (\n !this.alwaysHide &&\n (ValueHelper.isNullOrUndefined(this.value) ||\n this.value.length !== value.length ||\n (this.value.length > 0 && this.dimension === 0))\n ) {\n recalculateDimension = true;\n }\n\n this._value = value;\n if (this.allowUnsafeHtmlInSlider === false) {\n this.elemRef.nativeElement.innerText = value;\n } else {\n this.elemRef.nativeElement.innerHTML = value;\n }\n\n // Update dimension only when length of the label have changed\n if (recalculateDimension) {\n this.calculateDimension();\n }\n }\n}\n","import { Component, Input, TemplateRef } from '@angular/core';\n\n@Component({\n selector: 'ngx-slider-tooltip-wrapper',\n templateUrl: './tooltip-wrapper.component.html',\n styleUrls: ['./tooltip-wrapper.component.scss'],\n standalone: false\n})\nexport class TooltipWrapperComponent {\n @Input()\n template: TemplateRef<any>;\n\n @Input()\n tooltip: string;\n\n @Input()\n placement: string;\n\n @Input()\n content: string;\n}\n","@if (template) {\n <ng-template *ngTemplateOutlet=\"template; context: {tooltip: tooltip, placement: placement, content: content}\"></ng-template>\n}\n\n@if (!template) {\n <div class=\"ngx-slider-inner-tooltip\" [attr.title]=\"tooltip\" [attr.data-tooltip-placement]=\"placement\">\n {{content}}\n </div>\n}","import { Component, OnInit, ViewChild, AfterViewInit, OnChanges, OnDestroy, HostBinding, HostListener, Input, ElementRef, Renderer2, EventEmitter, Output, ContentChild, TemplateRef, ChangeDetectorRef, SimpleChanges, forwardRef, NgZone, ChangeDetectionStrategy, inject } from '@angular/core';\n\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nimport { Subject, Subscription } from 'rxjs';\nimport { distinctUntilChanged, filter } from 'rxjs/operators';\n\nimport { supportsPassiveEvents } from 'detect-passive-events';\n\nimport {\n Options,\n LabelType,\n ValueToPositionFunction,\n PositionToValueFunction,\n CustomStepDefinition,\n AllowUnsafeHtmlInSlider,\n} from './options';\nimport { PointerType } from './pointer-type';\nimport { ChangeContext } from './change-context';\nimport { ValueHelper } from './value-helper';\nimport { CompatibilityHelper } from './compatibility-helper';\nimport { MathHelper } from './math-helper';\nimport { EventListener } from './event-listener';\nimport { EventListenerHelper } from './event-listener-helper';\nimport { SliderElementDirective } from './slider-element.directive';\nimport { SliderHandleDirective } from './slider-handle.directive';\nimport { SliderLabelDirective } from './slider-label.directive';\n\n// Declaration for ResizeObserver a new API available in some of newest browsers:\n// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\ndeclare class ResizeObserver {\n constructor(callback: () => void);\n observe(target: any): void;\n unobserve(target: any): void;\n disconnect(): void;\n}\n\nexport class Tick {\n selected: boolean = false;\n style: any = {};\n tooltip: string = null;\n tooltipPlacement: string = null;\n value: string = null;\n valueTooltip: string = null;\n valueTooltipPlacement: string = null;\n legend: string = null;\n}\n\nclass Dragging {\n active: boolean = false;\n value: number = 0;\n difference: number = 0;\n position: number = 0;\n lowLimit: number = 0;\n highLimit: number = 0;\n}\n\nclass ModelValues {\n value: number;\n highValue: number;\n\n public static compare(x?: ModelValues, y?: ModelValues): boolean {\n if (ValueHelper.isNullOrUndefined(x) && ValueHelper.isNullOrUndefined(y)) {\n return false;\n }\n if (ValueHelper.isNullOrUndefined(x) !== ValueHelper.isNullOrUndefined(y)) {\n return false;\n }\n return x.value === y.value && x.highValue === y.highValue;\n }\n}\n\nclass ModelChange extends ModelValues {\n // Flag used to by-pass distinctUntilChanged() filter on input values\n // (sometimes there is a need to pass values through even though the model values have not changed)\n forceChange: boolean;\n\n public static override compare(x?: ModelChange, y?: ModelChange): boolean {\n if (ValueHelper.isNullOrUndefined(x) && ValueHelper.isNullOrUndefined(y)) {\n return false;\n }\n if (ValueHelper.isNullOrUndefined(x) !== ValueHelper.isNullOrUndefined(y)) {\n return false;\n }\n return (\n x.value === y.value &&\n x.highValue === y.highValue &&\n x.forceChange === y.forceChange\n );\n }\n}\n\nclass InputModelChange extends ModelChange {\n controlAccessorChange: boolean;\n internalChange: boolean;\n}\n\nclass OutputModelChange extends ModelChange {\n controlAccessorChange: boolean;\n userEventInitiated: boolean;\n}\n\nconst NGX_SLIDER_CONTROL_VALUE_ACCESSOR: any = {\n provide: NG_VALUE_ACCESSOR,\n /* tslint:disable-next-line: no-use-before-declare */\n useExisting: forwardRef(() => SliderComponent),\n multi: true,\n};\n\n@Component({\n selector: 'ngx-slider',\n templateUrl: './slider.component.html',\n styleUrls: ['./slider.component.scss'],\n providers: [NGX_SLIDER_CONTROL_VALUE_ACCESSOR],\n changeDetection: ChangeDetectionStrategy.Default,\n standalone: false\n})\nexport class SliderComponent\n implements OnInit, AfterViewInit, OnChanges, OnDestroy, ControlValueAccessor\n{\n private renderer = inject(Renderer2);\n private elementRef = inject(ElementRef);\n private changeDetectionRef = inject(ChangeDetectorRef);\n private zone = inject(NgZone);\n allowUnsafeHtmlInSlider = inject(AllowUnsafeHtmlInSlider, { optional: true });\n\n // Add ngx-slider class to the host element - this is static, should never change\n @HostBinding('class.ngx-slider')\n private sliderElementNgxSliderClass: boolean = true;\n\n // Model for low value of slider. For simple slider, this is the only input. For range slider, this is the low value.\n @Input()\n public value: number = null;\n // Output for low value slider to support two-way bindings\n @Output()\n public valueChange: EventEmitter<number> = new EventEmitter();\n\n // Model for high value of slider. Not used in simple slider. For range slider, this is the high value.\n @Input()\n public highValue: number = null;\n // Output for high value slider to support two-way bindings\n @Output()\n public highValueChange: EventEmitter<number> = new EventEmitter();\n\n // An object with all the other options of the slider.\n // Each option can be updated at runtime and the slider will automatically be re-rendered.\n @Input()\n public options: Options = new Options();\n\n // Event emitted when user starts interaction with the slider\n @Output()\n public userChangeStart: EventEmitter<ChangeContext> = new EventEmitter();\n\n // Event emitted on each change coming from user interaction\n @Output()\n public userChange: EventEmitter<ChangeContext> = new EventEmitter();\n\n // Event emitted when user finishes interaction with the slider\n @Output()\n public userChangeEnd: EventEmitter<ChangeContext> = new EventEmitter();\n\n private manualRefreshSubscription: any;\n // Input event that triggers slider refresh (re-positioning of slider elements)\n @Input() set manualRefresh(manualRefresh: EventEmitter<void>) {\n this.unsubscribeManualRefresh();\n\n this.manualRefreshSubscription = manualRefresh.subscribe(() => {\n setTimeout(() => this.calculateViewDimensionsAndDetectChanges());\n });\n }\n\n private triggerFocusSubscription: any;\n // Input event that triggers setting focus on given slider handle\n @Input() set triggerFocus(triggerFocus: EventEmitter<void>) {\n this.unsubscribeTriggerFocus();\n\n this.triggerFocusSubscription = triggerFocus.subscribe(\n (pointerType: PointerType) => {\n this.focusPointer(pointerType);\n }\n );\n }\n\n private cancelUserChangeSubscription: any;\n @Input() set cancelUserChange(cancelUserChange: EventEmitter<void>) {\n this.unsubscribeCancelUserChange();\n\n this.cancelUserChangeSubscription = cancelUserChange.subscribe(() => {\n if (this.moving) {\n this.positionTrackingHandle(this.preStartHandleValue);\n this.forceEnd(true);\n }\n });\n }\n\n // Slider type, true means range slider\n public get range(): boolean {\n return (\n !ValueHelper.isNullOrUndefined(this.value) &&\n !ValueHelper.isNullOrUndefined(this.highValue)\n );\n }\n\n // Set to true if init method already executed\n private initHasRun: boolean = false;\n\n // Changes in model inputs are passed through this subject\n // These are all changes coming in from outside the component through input bindings or reactive form inputs\n private inputModelChangeSubject: Subject<InputModelChange> =\n new Subject<InputModelChange>();\n private inputModelChangeSubscription: Subscription = null;\n\n // Changes to model outputs are passed through this subject\n // These are all changes that need to be communicated to output emitters and registered callbacks\n private outputModelChangeSubject: Subject<OutputModelChange> =\n new Subject<OutputModelChange>();\n private outputModelChangeSubscription: Subscription = null;\n\n // Low value synced to model low value\n private viewLowValue: number = null;\n // High value synced to model high value\n private viewHighValue: number = null;\n // Options synced to model options, based on defaults\n private viewOptions: Options = new Options();\n\n // Half of the width or height of the slider handles\n private handleHalfDimension: number = 0;\n // Maximum position the slider handle can have\n private maxHandlePosition: number = 0;\n\n // Which handle is currently tracked for move events\n private currentTrackingPointer: PointerType = null;\n // Internal variable to keep track of the focus element\n private currentFocusPointer: PointerType = null;\n // Used to call onStart on the first keydown event\n private firstKeyDown: boolean = false;\n // Current touch id of touch event being handled\n private touchId: number = null;\n // Values recorded when first dragging the bar\n private dragging: Dragging = new Dragging();\n // Value of hanlde at the beginning of onStart()\n private preStartHandleValue: number = null;\n\n /* Slider DOM elements */\n\n // Left selection bar outside two handles\n @ViewChild('leftOuterSelectionBar', {\n read: SliderElementDirective,\n static: false,\n })\n private leftOuterSelectionBarElement: SliderElementDirective;\n\n // Right selection bar outside two handles\n @ViewChild('rightOuterSelectionBar', {\n read: SliderElementDirective,\n static: false,\n })\n private rightOuterSelectionBarElement: SliderElementDirective;\n\n // The whole slider bar\n @ViewChild('fullBar', { read: SliderElementDirective, static: false })\n private fullBarElement: SliderElementDirective;\n\n // Highlight between two handles\n @ViewChild('selectionBar', { read: SliderElementDirective, static: false })\n private selectionBarElement: SliderElementDirective;\n\n // Left slider handle\n @ViewChild('minHandle', { read: SliderHandleDirective, static: false })\n private minHandleElement: SliderHandleDirective;\n\n // Right slider handle\n @ViewChild('maxHandle', { read: SliderHandleDirective, static: false })\n private maxHandleElement: SliderHandleDirective;\n\n // Floor label\n @ViewChild('floorLabel', { read: SliderLabelDirective, static: false })\n private floorLabelElement: SliderLabelDirective;\n\n // Ceiling label\n @ViewChild('ceilLabel', { read: SliderLabelDirective, static: false })\n private ceilLabelElement: SliderLabelDirective;\n\n // Label above the low value\n @ViewChild('minHandleLabel', { read: SliderLabelDirective, static: false })\n private minHandleLabelElement: SliderLabelDirective;\n\n // Label above the high value\n @ViewChild('maxHandleLabel', { read: SliderLabelDirective, static: false })\n private maxHandleLabelElement: SliderLabelDirective;\n\n // Combined label\n @ViewChild('combinedLabel', { read: SliderLabelDirective, static: false })\n private combinedLabelElement: SliderLabelDirective;\n\n // The ticks\n @ViewChild('ticksElement', { read: SliderElementDirective, static: false })\n private ticksElement: SliderElementDirective;\n\n // Optional custom template for displaying tooltips\n @ContentChild('tooltipTemplate', { static: false })\n public tooltipTemplate: TemplateRef<any>;\n\n // Host element class bindings\n @HostBinding('class.vertical')\n public sliderElementVerticalClass: boolean = false;\n @HostBinding('class.animate')\n public sliderElementAnimateClass: boolean = false;\n @HostBinding('class.with-legend')\n public sliderElementWithLegendClass: boolean = false;\n @HostBinding('attr.disabled')\n public sliderElementDisabledAttr: string = null;\n @HostBinding('attr.aria-label')\n public sliderElementAriaLabel: string = 'ngx-slider';\n\n // CSS styles and class flags\n public barStyle: any = {};\n public minPointerStyle: any = {};\n public maxPointerStyle: any = {};\n public fullBarTransparentClass: boolean = false;\n public selectionBarDraggableClass: boolean = false;\n public ticksUnderValuesClass: boolean = false;\n\n // Whether to show/hide ticks\n public get showTicks(): boolean {\n return this.viewOptions.showTicks;\n }\n\n /* If tickStep is set or ticksArray is specified.\n In this case, ticks values should be displayed below the slider. */\n private intermediateTicks: boolean = false;\n // Ticks array as displayed in view\n public ticks: Tick[] = [];\n\n // Event listeners\n private eventListenerHelper: EventListenerHelper = null;\n private onMoveEventListener: EventListener = null;\n private onEndEventListener: EventListener = null;\n // Whether currently moving the slider (between onStart() and onEnd())\n private moving: boolean = false;\n\n // Observer for slider element resize events\n private resizeObserver: ResizeObserver = null;\n\n // Callbacks for reactive forms support\n private onTouchedCallback: (value: any) => void = null;\n private onChangeCallback: (value: any) => void = null;\n\n public constructor() {\n this.eventListenerHelper = new EventListenerHelper(this.renderer);\n }\n\n // OnInit interface\n public ngOnInit(): void {\n this.viewOptions = new Options();\n Object.assign(this.viewOptions, this.options);\n\n // We need to run these two things first, before the rest of the init in ngAfterViewInit(),\n // because these two settings are set through @HostBinding and Angular change detection\n // mechanism doesn't like them changing in ngAfterViewInit()\n\n this.updateDisabledState();\n this.updateVerticalState();\n this.updateAriaLabel();\n }\n\n // AfterViewInit interface\n public ngAfterViewInit(): void {\n this.applyOptions();\n\n this.subscribeInputModelChangeSubject();\n this.subscribeOutputModelChangeSubject();\n\n // Once we apply options, we need to normalise model values for the first time\n this.renormaliseModelValues();\n\n this.viewLowValue = this.modelValueToViewValue(this.value);\n if (this.range) {\n this.viewHighValue = this.modelValueToViewValue(this.highValue);\n } else {\n this.viewHighValue = null;\n }\n\n this.updateVerticalState(); // need to run this again to cover changes to slider elements\n this.manageElementsStyle();\n this.updateDisabledState();\n this.calculateViewDimensions();\n this.addAccessibility();\n this.updateCeilLabel();\n this.updateFloorLabel();\n this.initHandles();\n this.manageEventsBindings();\n this.updateAriaLabel();\n\n this.subscribeResizeObserver();\n\n this.initHasRun = true;\n\n // Run change detection manually to resolve some issues when init procedure changes values used in the view\n if (!this.isRefDestroyed()) {\n this.changeDetectionRef.detectChanges();\n }\n }\n\n // OnChanges interface\n public ngOnChanges(changes: SimpleChanges): void {\n // Always apply options first\n if (\n !ValueHelper.isNullOrUndefined(changes.options) &&\n JSON.stringify(changes.options.previousValue) !==\n JSON.stringify(changes.options.currentValue)\n ) {\n this.onChangeOptions();\n }\n\n // Then value changes\n if (\n !ValueHelper.isNullOrUndefined(changes.value) ||\n !ValueHelper.isNullOrUndefined(changes.highValue)\n ) {\n this.inputModelChangeSubject.next({\n value: this.value,\n highValue: this.highValue,\n controlAccessorChange: false,\n forceChange: false,\n internalChange: false,\n });\n }\n }\n\n // OnDestroy interface\n public ngOnDestroy(): void {\n this.unbindEvents();\n\n this.unsubscribeResizeObserver();\n this.unsubscribeInputModelChangeSubject();\n this.unsubscribeOutputModelChangeSubject();\n this.unsubscribeManualRefresh();\n this.unsubscribeTriggerFocus();\n }\n\n // ControlValueAccessor interface\n public writeValue(obj: any): void {\n if (obj instanceof Array) {\n this.value = obj[0];\n this.highValue = obj[1];\n } else {\n this.value = obj;\n }\n\n // ngOnChanges() is not called in this instance, so we need to communicate the change manually\n this.inputModelChangeSubject.next({\n value: this.value,\n highValue: this.highValue,\n forceChange: false,\n internalChange: false,\n controlAccessorChange: true,\n });\n }\n\n // ControlValueAccessor interface\n public registerOnChange(onChangeCallback: any): void {\n this.onChangeCallback = onChangeCallback;\n }\n\n // ControlValueAccessor interface\n public registerOnTouched(onTouchedCallback: any): void {\n this.onTouchedCallback = onTouchedCallback;\n }\n\n // ControlValueAccessor interface\n public setDisabledState(isDisabled: boolean): void {\n this.viewOptions.disabled = isDisabled;\n this.updateDisabledState();\n\n if (this.initHasRun) {\n this.manageEventsBindings();\n }\n }\n\n public setAriaLabel(ariaLabel: string): void {\n this.viewOptions.ariaLabel = ariaLabel;\n this.updateAriaLabel();\n }\n\n @HostListener('window:resize', ['$event'])\n public onResize(event: any): void {\n this.calculateViewDimensionsAndDetectChanges();\n }\n\n private subscribeInputModelChangeSubject(): void {\n this.inputModelChangeSubscription = this.inputModelChangeSubject\n .pipe(\n distinctUntilChanged(ModelChange.compare),\n // Hack to reset the status of the distinctUntilChanged() - if a \"fake\" event comes through with forceChange=true,\n // we forcefully by-pass distinctUntilChanged(), but otherwise drop the event\n filter(\n (modelChange: InputModelChange) =>\n !modelChange.forceChange && !modelChange.internalChange\n )\n )\n .subscribe((modelChange: InputModelChange) =>\n this.applyInputModelChange(modelChange)\n );\n }\n\n private subscribeOutputModelChangeSubject(): void {\n this.outputModelChangeSubscription = this.outputModelChangeSubject\n .pipe(distinctUntilChanged(ModelChange.compare))\n .subscribe((modelChange: OutputModelChange) =>\n this.publishOutputModelChange(modelChange)\n );\n }\n\n private subscribeResizeObserver(): void {\n if (CompatibilityHelper.isResizeObserverAvailable()) {\n this.resizeObserver = new ResizeObserver((): void =>\n this.calculateViewDimensionsAndDetectChanges()\n );\n this.resizeObserver.observe(this.elementRef.nativeElement);\n }\n }\n\n private unsubscribeResizeObserver(): void {\n if (\n CompatibilityHelper.isResizeObserverAvailable() &&\n this.resizeObserver !== null\n ) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n }\n\n private unsubscribeOnMove(): void {\n if (!ValueHelper.isNullOrUndefined(this.onMoveEventListener)) {\n this.eventListenerHelper.detachEventListener(this.onMoveEventListener);\n this.onMoveEventListener = null;\n }\n }\n\n private unsubscribeOnEnd(): void {\n if (!ValueHelper.isNullOrUndefined(this.onEndEventListener)) {\n this.eventListenerHelper.detachEventListener(this.onEndEventListener);\n this.onEndEventListener = null;\n }\n }\n\n private unsubscribeInputModelChangeSubject(): void {\n if (!ValueHelper.isNullOrUndefined(this.inputModelChangeSubscription)) {\n this.inputModelChangeSubscription.unsubscribe();\n this.inputModelChangeSubscription = null;\n }\n }\n\n private unsubscribeOutputModelChangeSubject(): void