@truenas/ui-components 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -13563,11 +13563,33 @@ class TnSliderThumbDirective {
|
|
|
13563
13563
|
slider; // Will be set by parent slider component
|
|
13564
13564
|
onTouched = () => { };
|
|
13565
13565
|
onChangeCallback = (_value) => { };
|
|
13566
|
+
// Pointer is held down on the thumb (set on mousedown/touchstart). Drives the
|
|
13567
|
+
// global move/up listeners.
|
|
13568
|
+
isPointerDown = false;
|
|
13569
|
+
// The pointer has actually moved since going down — i.e. a genuine drag. Only
|
|
13570
|
+
// set once movement occurs (onGlobalMouseMove/onGlobalTouchMove), never on the
|
|
13571
|
+
// initial press, so a plain click still commits through onInput. See onInput.
|
|
13566
13572
|
isDragging = false;
|
|
13573
|
+
// Last value written by the form. Retained so the parent slider can pick it up
|
|
13574
|
+
// once it links the thumb (ngAfterContentInit) — writeValue can run before the
|
|
13575
|
+
// slider sets `this.slider`.
|
|
13576
|
+
currentValue = 0;
|
|
13577
|
+
// Whether the form has actually written a value to this thumb. Lets the parent
|
|
13578
|
+
// slider distinguish "form wrote 0" from "thumb never bound" so it doesn't
|
|
13579
|
+
// clobber its own form value with this default. See getValue()/hasFormValue().
|
|
13580
|
+
hasWrittenValue = false;
|
|
13567
13581
|
elementRef = inject((ElementRef));
|
|
13582
|
+
// An accessible name set directly on the <input tnSliderThumb> (e.g.
|
|
13583
|
+
// aria-label="Volume"). Captured up front so the host's [attr.aria-label]
|
|
13584
|
+
// binding can fall back to it instead of clobbering it when the parent slider
|
|
13585
|
+
// doesn't supply one. See ariaLabel()/ariaLabelledby().
|
|
13586
|
+
fallbackAriaLabel = null;
|
|
13587
|
+
fallbackAriaLabelledby = null;
|
|
13568
13588
|
ngOnInit() {
|
|
13569
13589
|
// Make the native input visually hidden but still accessible
|
|
13570
13590
|
const input = this.elementRef.nativeElement;
|
|
13591
|
+
this.fallbackAriaLabel = input.getAttribute('aria-label');
|
|
13592
|
+
this.fallbackAriaLabelledby = input.getAttribute('aria-labelledby');
|
|
13571
13593
|
input.style.opacity = '0';
|
|
13572
13594
|
input.style.position = 'absolute';
|
|
13573
13595
|
input.style.width = '100%';
|
|
@@ -13580,10 +13602,46 @@ class TnSliderThumbDirective {
|
|
|
13580
13602
|
ngOnDestroy() {
|
|
13581
13603
|
this.cleanup();
|
|
13582
13604
|
}
|
|
13605
|
+
/** Value last written by the form, for the slider to read once linked. */
|
|
13606
|
+
getValue() {
|
|
13607
|
+
return this.currentValue;
|
|
13608
|
+
}
|
|
13609
|
+
/**
|
|
13610
|
+
* Whether the form has written a value to this thumb. The slider checks this
|
|
13611
|
+
* before adopting getValue() so an unbound thumb's default 0 never overwrites
|
|
13612
|
+
* a value bound directly on the slider.
|
|
13613
|
+
*/
|
|
13614
|
+
hasFormValue() {
|
|
13615
|
+
return this.hasWrittenValue;
|
|
13616
|
+
}
|
|
13583
13617
|
// ControlValueAccessor implementation
|
|
13584
13618
|
writeValue(value) {
|
|
13585
|
-
|
|
13586
|
-
|
|
13619
|
+
const nextValue = value ?? 0;
|
|
13620
|
+
this.hasWrittenValue = true;
|
|
13621
|
+
// Propagate to the parent slider so its value signal (and thus the thumb
|
|
13622
|
+
// position / track fill) reflects the form value, not just the native input.
|
|
13623
|
+
// When linked, mirror the slider's clamped/stepped result so currentValue and
|
|
13624
|
+
// the native input agree with it; otherwise retain the raw value for the
|
|
13625
|
+
// slider to adopt (and clamp) once it links the thumb in ngAfterContentInit.
|
|
13626
|
+
if (this.slider) {
|
|
13627
|
+
this.slider.updateValue(nextValue);
|
|
13628
|
+
// Note: we don't emit the clamped result back to the form control, so an
|
|
13629
|
+
// out-of-range write (e.g. setValue(143) on a 0–100 slider) leaves the model
|
|
13630
|
+
// holding 143 while the slider renders 100. This mirrors native
|
|
13631
|
+
// <input type="range">, which also doesn't reconcile the bound value.
|
|
13632
|
+
this.currentValue = this.slider.value();
|
|
13633
|
+
// When linked, the host `[value]="slider?.value()"` binding writes the
|
|
13634
|
+
// native input on the next change-detection pass, so a manual write here
|
|
13635
|
+
// would only be re-overwritten — leave it to the binding to keep one source
|
|
13636
|
+
// of truth.
|
|
13637
|
+
}
|
|
13638
|
+
else {
|
|
13639
|
+
this.currentValue = nextValue;
|
|
13640
|
+
// Unlinked: no host binding value yet (slider?.value() is undefined), so set
|
|
13641
|
+
// the native input directly until the slider links and takes over.
|
|
13642
|
+
if (this.elementRef.nativeElement) {
|
|
13643
|
+
this.elementRef.nativeElement.value = this.currentValue.toString();
|
|
13644
|
+
}
|
|
13587
13645
|
}
|
|
13588
13646
|
}
|
|
13589
13647
|
registerOnChange(fn) {
|
|
@@ -13599,21 +13657,45 @@ class TnSliderThumbDirective {
|
|
|
13599
13657
|
}
|
|
13600
13658
|
}
|
|
13601
13659
|
onInput(event) {
|
|
13660
|
+
// Once a drag is underway, the global pointer handlers
|
|
13661
|
+
// (updateValueFromPosition) own value commits; the native range also fires
|
|
13662
|
+
// input here, so skip it to avoid a redundant double emit. A plain click sets
|
|
13663
|
+
// isPointerDown but not isDragging (no movement), so its native input commits
|
|
13664
|
+
// here; keyboard changes set neither and also flow through.
|
|
13665
|
+
if (this.isDragging) {
|
|
13666
|
+
return;
|
|
13667
|
+
}
|
|
13602
13668
|
const input = event.target;
|
|
13603
13669
|
const value = parseFloat(input.value);
|
|
13670
|
+
// An empty/garbage value parses to NaN, which clampValue can't sanitize
|
|
13671
|
+
// (Math.max/min with NaN stay NaN). Native range inputs shouldn't produce
|
|
13672
|
+
// this, but guard so a NaN never reaches the form or slider value.
|
|
13673
|
+
if (!Number.isFinite(value)) {
|
|
13674
|
+
return;
|
|
13675
|
+
}
|
|
13604
13676
|
if (this.slider) {
|
|
13605
13677
|
this.slider.updateValue(value);
|
|
13606
13678
|
}
|
|
13607
13679
|
this.onChangeCallback(value);
|
|
13608
13680
|
}
|
|
13609
13681
|
onChange(_event) {
|
|
13682
|
+
this.notifyTouched();
|
|
13683
|
+
}
|
|
13684
|
+
/**
|
|
13685
|
+
* Marks the bound control touched. Calls the thumb's own `onTouched` (for a
|
|
13686
|
+
* thumb-bound control) and forwards to the linked slider (for a
|
|
13687
|
+
* slider-host-bound control) — the thumb is the only interactive element, so
|
|
13688
|
+
* the slider relies on it to ever become touched.
|
|
13689
|
+
*/
|
|
13690
|
+
notifyTouched() {
|
|
13610
13691
|
this.onTouched();
|
|
13692
|
+
this.slider?.markTouched();
|
|
13611
13693
|
}
|
|
13612
13694
|
onMouseDown(event) {
|
|
13613
13695
|
if (this.disabled()) {
|
|
13614
13696
|
return;
|
|
13615
13697
|
}
|
|
13616
|
-
this.
|
|
13698
|
+
this.isPointerDown = true;
|
|
13617
13699
|
this.addGlobalListeners();
|
|
13618
13700
|
event.stopPropagation(); // Prevent track click
|
|
13619
13701
|
}
|
|
@@ -13621,7 +13703,7 @@ class TnSliderThumbDirective {
|
|
|
13621
13703
|
if (this.disabled()) {
|
|
13622
13704
|
return;
|
|
13623
13705
|
}
|
|
13624
|
-
this.
|
|
13706
|
+
this.isPointerDown = true;
|
|
13625
13707
|
this.addGlobalListeners();
|
|
13626
13708
|
event.stopPropagation(); // Prevent track click
|
|
13627
13709
|
}
|
|
@@ -13638,31 +13720,35 @@ class TnSliderThumbDirective {
|
|
|
13638
13720
|
document.removeEventListener('touchend', this.onGlobalTouchEnd);
|
|
13639
13721
|
}
|
|
13640
13722
|
onGlobalMouseMove = (event) => {
|
|
13641
|
-
if (!this.
|
|
13723
|
+
if (!this.isPointerDown || this.disabled()) {
|
|
13642
13724
|
return;
|
|
13643
13725
|
}
|
|
13726
|
+
this.isDragging = true;
|
|
13644
13727
|
event.preventDefault();
|
|
13645
13728
|
this.updateValueFromPosition(event.clientX);
|
|
13646
13729
|
};
|
|
13647
13730
|
onGlobalMouseUp = () => {
|
|
13648
|
-
if (this.
|
|
13731
|
+
if (this.isPointerDown) {
|
|
13732
|
+
this.isPointerDown = false;
|
|
13649
13733
|
this.isDragging = false;
|
|
13650
|
-
this.
|
|
13734
|
+
this.notifyTouched();
|
|
13651
13735
|
this.removeGlobalListeners();
|
|
13652
13736
|
}
|
|
13653
13737
|
};
|
|
13654
13738
|
onGlobalTouchMove = (event) => {
|
|
13655
|
-
if (!this.
|
|
13739
|
+
if (!this.isPointerDown || this.disabled()) {
|
|
13656
13740
|
return;
|
|
13657
13741
|
}
|
|
13742
|
+
this.isDragging = true;
|
|
13658
13743
|
event.preventDefault();
|
|
13659
13744
|
const touch = event.touches[0];
|
|
13660
13745
|
this.updateValueFromPosition(touch.clientX);
|
|
13661
13746
|
};
|
|
13662
13747
|
onGlobalTouchEnd = () => {
|
|
13663
|
-
if (this.
|
|
13748
|
+
if (this.isPointerDown) {
|
|
13749
|
+
this.isPointerDown = false;
|
|
13664
13750
|
this.isDragging = false;
|
|
13665
|
-
this.
|
|
13751
|
+
this.notifyTouched();
|
|
13666
13752
|
this.removeGlobalListeners();
|
|
13667
13753
|
}
|
|
13668
13754
|
};
|
|
@@ -13676,12 +13762,58 @@ class TnSliderThumbDirective {
|
|
|
13676
13762
|
const maxVal = this.slider.max();
|
|
13677
13763
|
const newValue = minVal + (percentage * (maxVal - minVal));
|
|
13678
13764
|
this.slider.updateValue(newValue);
|
|
13765
|
+
// updateValue only moves the visual thumb; commit the (clamped/stepped) value
|
|
13766
|
+
// to the form and native input so dragging actually emits changes — otherwise
|
|
13767
|
+
// only click (native input event) updates the model.
|
|
13768
|
+
this.commit(this.slider.value());
|
|
13769
|
+
}
|
|
13770
|
+
/**
|
|
13771
|
+
* Resolve the accessible name for the range input: the parent slider's
|
|
13772
|
+
* `aria-label`/`aria-labelledby` input when set, otherwise a value placed
|
|
13773
|
+
* directly on the `<input tnSliderThumb>`. Returning the fallback keeps the
|
|
13774
|
+
* host binding from wiping a directly-set label. Null removes the attribute.
|
|
13775
|
+
*/
|
|
13776
|
+
ariaLabel() {
|
|
13777
|
+
return this.slider?.ariaLabel() ?? this.fallbackAriaLabel;
|
|
13778
|
+
}
|
|
13779
|
+
ariaLabelledby() {
|
|
13780
|
+
return this.slider?.ariaLabelledby() ?? this.fallbackAriaLabelledby;
|
|
13781
|
+
}
|
|
13782
|
+
/**
|
|
13783
|
+
* Commit a value that originated outside the native input (a thumb drag).
|
|
13784
|
+
* Syncs `currentValue`, the native input, and emits to the form. The slider's
|
|
13785
|
+
* own `onChange` only reaches a slider-bound control, so a thumb-bound control
|
|
13786
|
+
* relies on this to stay in sync. Expects an already clamped/stepped value
|
|
13787
|
+
* (slider.value()).
|
|
13788
|
+
*/
|
|
13789
|
+
commit(value) {
|
|
13790
|
+
this.currentValue = value;
|
|
13791
|
+
if (this.elementRef.nativeElement) {
|
|
13792
|
+
this.elementRef.nativeElement.value = value.toString();
|
|
13793
|
+
}
|
|
13794
|
+
this.onChangeCallback(value);
|
|
13795
|
+
}
|
|
13796
|
+
/**
|
|
13797
|
+
* Builds an aria-valuetext when the slider has a label prefix/suffix so screen
|
|
13798
|
+
* readers announce "50 km/h" rather than the bare number. Returns null when
|
|
13799
|
+
* neither is set, letting the native range's valuenow announcement stand.
|
|
13800
|
+
*/
|
|
13801
|
+
ariaValueText() {
|
|
13802
|
+
if (!this.slider) {
|
|
13803
|
+
return null;
|
|
13804
|
+
}
|
|
13805
|
+
const prefix = this.slider.labelPrefix();
|
|
13806
|
+
const suffix = this.slider.labelSuffix();
|
|
13807
|
+
if (!prefix && !suffix) {
|
|
13808
|
+
return null;
|
|
13809
|
+
}
|
|
13810
|
+
return `${prefix}${this.slider.value()}${suffix}`;
|
|
13679
13811
|
}
|
|
13680
13812
|
cleanup() {
|
|
13681
13813
|
this.removeGlobalListeners();
|
|
13682
13814
|
}
|
|
13683
13815
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderThumbDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
13684
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnSliderThumbDirective, isStandalone: true, selector: "input[tnSliderThumb]", host: { attributes: { "type": "range" }, listeners: { "input": "onInput($event)", "change": "onChange($event)", "blur": "
|
|
13816
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnSliderThumbDirective, isStandalone: true, selector: "input[tnSliderThumb]", host: { attributes: { "type": "range" }, listeners: { "input": "onInput($event)", "change": "onChange($event)", "blur": "notifyTouched()", "mousedown": "onMouseDown($event)", "touchstart": "onTouchStart($event)" }, properties: { "disabled": "slider?.isDisabled()", "attr.min": "slider?.min()", "attr.max": "slider?.max()", "attr.step": "slider?.step()", "value": "slider?.value()", "attr.aria-valuetext": "ariaValueText()", "attr.aria-label": "ariaLabel()", "attr.aria-labelledby": "ariaLabelledby()" }, classAttribute: "tn-slider-thumb" }, providers: [
|
|
13685
13817
|
{
|
|
13686
13818
|
provide: NG_VALUE_ACCESSOR,
|
|
13687
13819
|
useExisting: forwardRef(() => TnSliderThumbDirective),
|
|
@@ -13709,15 +13841,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
13709
13841
|
'[attr.max]': 'slider?.max()',
|
|
13710
13842
|
'[attr.step]': 'slider?.step()',
|
|
13711
13843
|
'[value]': 'slider?.value()',
|
|
13844
|
+
'[attr.aria-valuetext]': 'ariaValueText()',
|
|
13845
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
13846
|
+
'[attr.aria-labelledby]': 'ariaLabelledby()',
|
|
13712
13847
|
'(input)': 'onInput($event)',
|
|
13713
13848
|
'(change)': 'onChange($event)',
|
|
13714
|
-
'(blur)': '
|
|
13849
|
+
'(blur)': 'notifyTouched()',
|
|
13715
13850
|
'(mousedown)': 'onMouseDown($event)',
|
|
13716
13851
|
'(touchstart)': 'onTouchStart($event)'
|
|
13717
13852
|
}
|
|
13718
13853
|
}]
|
|
13719
13854
|
}] });
|
|
13720
13855
|
|
|
13856
|
+
/**
|
|
13857
|
+
* Range slider with an optional value label.
|
|
13858
|
+
*
|
|
13859
|
+
* Form binding: both this component and the inner `input[tnSliderThumb]`
|
|
13860
|
+
* directive are `NG_VALUE_ACCESSOR` providers, so a `formControl`/`ngModel` can
|
|
13861
|
+
* be attached to either element. Bind to the `tn-slider` host for the simplest
|
|
13862
|
+
* usage; binding to the inner thumb input also works and the slider adopts that
|
|
13863
|
+
* value on init (see {@link ngAfterContentInit}). Avoid binding to both at once —
|
|
13864
|
+
* pick one element per control to keep a single source of truth.
|
|
13865
|
+
*/
|
|
13721
13866
|
class TnSliderComponent {
|
|
13722
13867
|
min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : []));
|
|
13723
13868
|
max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : []));
|
|
@@ -13731,9 +13876,18 @@ class TnSliderComponent {
|
|
|
13731
13876
|
* is configured via `TN_TEST_ATTR` (default `data-testid`).
|
|
13732
13877
|
*/
|
|
13733
13878
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
13879
|
+
/**
|
|
13880
|
+
* Accessible name forwarded to the inner range input — the focusable element
|
|
13881
|
+
* screen readers actually announce. Set this (or `aria-labelledby`) when the
|
|
13882
|
+
* slider isn't already labelled by a `tn-form-field`/`<label>`, otherwise a
|
|
13883
|
+
* standalone `<tn-slider><input tnSliderThumb></tn-slider>` announces only
|
|
13884
|
+
* "slider". A label set directly on the `input[tnSliderThumb]` is used as a
|
|
13885
|
+
* fallback when neither is provided here.
|
|
13886
|
+
*/
|
|
13887
|
+
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : {}), alias: 'aria-label' });
|
|
13888
|
+
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : {}), alias: 'aria-labelledby' });
|
|
13734
13889
|
thumbDirective = contentChild.required(TnSliderThumbDirective);
|
|
13735
13890
|
sliderContainer = viewChild.required('sliderContainer');
|
|
13736
|
-
thumbVisual = viewChild.required('thumbVisual');
|
|
13737
13891
|
onChange = (_value) => { };
|
|
13738
13892
|
onTouched = () => { };
|
|
13739
13893
|
value = signal(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
@@ -13754,13 +13908,6 @@ class TnSliderComponent {
|
|
|
13754
13908
|
fillScale = computed(() => {
|
|
13755
13909
|
return this.fillPercentage() / 100;
|
|
13756
13910
|
}, ...(ngDevMode ? [{ debugName: "fillScale" }] : []));
|
|
13757
|
-
// Computed position for thumb (in pixels from left)
|
|
13758
|
-
thumbPosition = computed(() => {
|
|
13759
|
-
const containerWidth = this.sliderContainer()?.nativeElement?.offsetWidth || 0;
|
|
13760
|
-
const percentage = this.fillPercentage();
|
|
13761
|
-
// Center the thumb (20px width, so -10px offset)
|
|
13762
|
-
return (containerWidth * percentage / 100) - 10;
|
|
13763
|
-
}, ...(ngDevMode ? [{ debugName: "thumbPosition" }] : []));
|
|
13764
13911
|
// Public signals for label management
|
|
13765
13912
|
showLabel = this._showLabel.asReadonly();
|
|
13766
13913
|
labelVisible = this._labelVisible.asReadonly();
|
|
@@ -13782,13 +13929,24 @@ class TnSliderComponent {
|
|
|
13782
13929
|
}
|
|
13783
13930
|
});
|
|
13784
13931
|
}
|
|
13785
|
-
|
|
13786
|
-
//
|
|
13932
|
+
ngAfterContentInit() {
|
|
13933
|
+
// Link the projected thumb directive. Done in AfterContentInit (not
|
|
13934
|
+
// AfterViewInit) so the link exists before the thumb's host bindings settle,
|
|
13935
|
+
// avoiding a null→value flip on its [disabled]/[min]/[value] bindings.
|
|
13787
13936
|
const thumbDirective = this.thumbDirective();
|
|
13788
13937
|
if (thumbDirective) {
|
|
13789
13938
|
thumbDirective.slider = this;
|
|
13939
|
+
// When the form is bound to the inner thumb input, its initial writeValue()
|
|
13940
|
+
// may run before this link exists, so adopt the value the thumb received —
|
|
13941
|
+
// otherwise the slider keeps its default 0 and the thumb/fill render at the
|
|
13942
|
+
// wrong position. Only adopt when the thumb was actually written to, so a
|
|
13943
|
+
// value bound directly on the slider isn't clobbered by the thumb's default.
|
|
13944
|
+
if (thumbDirective.hasFormValue()) {
|
|
13945
|
+
this.value.set(this.clampValue(thumbDirective.getValue()));
|
|
13946
|
+
}
|
|
13790
13947
|
}
|
|
13791
|
-
|
|
13948
|
+
}
|
|
13949
|
+
ngAfterViewInit() {
|
|
13792
13950
|
// Set up handle interaction listeners if labelType is handle or both
|
|
13793
13951
|
const currentLabelType = this.labelType();
|
|
13794
13952
|
if ((currentLabelType === 'handle' || currentLabelType === 'both') && this._showLabel()) {
|
|
@@ -13810,6 +13968,15 @@ class TnSliderComponent {
|
|
|
13810
13968
|
registerOnTouched(fn) {
|
|
13811
13969
|
this.onTouched = fn;
|
|
13812
13970
|
}
|
|
13971
|
+
/**
|
|
13972
|
+
* Marks a slider-host-bound control as touched. The inner thumb is the only
|
|
13973
|
+
* interactive element, so it forwards its touch events here (on blur / pointer
|
|
13974
|
+
* release) — otherwise a control bound to the `tn-slider` host would never
|
|
13975
|
+
* transition to touched and touched-gated validation would never show.
|
|
13976
|
+
*/
|
|
13977
|
+
markTouched() {
|
|
13978
|
+
this.onTouched();
|
|
13979
|
+
}
|
|
13813
13980
|
setDisabledState(isDisabled) {
|
|
13814
13981
|
this.formDisabled.set(isDisabled);
|
|
13815
13982
|
}
|
|
@@ -13818,7 +13985,6 @@ class TnSliderComponent {
|
|
|
13818
13985
|
const clampedValue = this.clampValue(newValue);
|
|
13819
13986
|
this.value.set(clampedValue);
|
|
13820
13987
|
this.onChange(clampedValue);
|
|
13821
|
-
this.updateThumbPosition();
|
|
13822
13988
|
}
|
|
13823
13989
|
enableLabel() {
|
|
13824
13990
|
this._showLabel.set(true);
|
|
@@ -13832,24 +13998,6 @@ class TnSliderComponent {
|
|
|
13832
13998
|
getSliderRect() {
|
|
13833
13999
|
return this.sliderContainer().nativeElement.getBoundingClientRect();
|
|
13834
14000
|
}
|
|
13835
|
-
onTrackClick(event) {
|
|
13836
|
-
if (this.isDisabled()) {
|
|
13837
|
-
return;
|
|
13838
|
-
}
|
|
13839
|
-
event.preventDefault();
|
|
13840
|
-
const rect = this.getSliderRect();
|
|
13841
|
-
const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
|
13842
|
-
const percentage = (clientX - rect.left) / rect.width;
|
|
13843
|
-
const minVal = this.min();
|
|
13844
|
-
const maxVal = this.max();
|
|
13845
|
-
const newValue = minVal + (percentage * (maxVal - minVal));
|
|
13846
|
-
this.updateValue(newValue);
|
|
13847
|
-
this.onTouched();
|
|
13848
|
-
}
|
|
13849
|
-
updateThumbPosition() {
|
|
13850
|
-
// Thumb position is now handled by computed signal and template binding
|
|
13851
|
-
// No manual DOM manipulation needed
|
|
13852
|
-
}
|
|
13853
14001
|
clampValue(value) {
|
|
13854
14002
|
const minVal = this.min();
|
|
13855
14003
|
const maxVal = this.max();
|
|
@@ -13907,13 +14055,13 @@ class TnSliderComponent {
|
|
|
13907
14055
|
}
|
|
13908
14056
|
};
|
|
13909
14057
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13910
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSliderComponent, isStandalone: true, selector: "tn-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, labelPrefix: { classPropertyName: "labelPrefix", publicName: "labelPrefix", isSignal: true, isRequired: false, transformFunction: null }, labelSuffix: { classPropertyName: "labelSuffix", publicName: "labelSuffix", isSignal: true, isRequired: false, transformFunction: null }, labelType: { classPropertyName: "labelType", publicName: "labelType", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-disabled": "isDisabled()" }, classAttribute: "tn-slider" }, providers: [
|
|
14058
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSliderComponent, isStandalone: true, selector: "tn-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, labelPrefix: { classPropertyName: "labelPrefix", publicName: "labelPrefix", isSignal: true, isRequired: false, transformFunction: null }, labelSuffix: { classPropertyName: "labelSuffix", publicName: "labelSuffix", isSignal: true, isRequired: false, transformFunction: null }, labelType: { classPropertyName: "labelType", publicName: "labelType", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-disabled": "isDisabled()" }, classAttribute: "tn-slider" }, providers: [
|
|
13911
14059
|
{
|
|
13912
14060
|
provide: NG_VALUE_ACCESSOR,
|
|
13913
14061
|
useExisting: forwardRef(() => TnSliderComponent),
|
|
13914
14062
|
multi: true
|
|
13915
14063
|
}
|
|
13916
|
-
], queries: [{ propertyName: "thumbDirective", first: true, predicate: TnSliderThumbDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "sliderContainer", first: true, predicate: ["sliderContainer"], descendants: true, isSignal: true },
|
|
14064
|
+
], queries: [{ propertyName: "thumbDirective", first: true, predicate: TnSliderThumbDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "sliderContainer", first: true, predicate: ["sliderContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Pointer interaction (click-to-position and press-drag, anywhere on the track)\n is handled by the projected native range input, which overlays the whole\n container (see TnSliderThumbDirective): a click jumps the thumb and fires\n `input` \u2192 onInput; a press-and-move runs the directive's global drag handlers.\n No container-level click handler is needed \u2014 and one wouldn't fire anyway, as\n the overlay calls stopPropagation() on its own pointer events.\n-->\n<div\n #sliderContainer\n class=\"tn-slider-container\"\n tnTestIdType=\"slider\"\n [tnTestId]=\"testId()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.data-disabled]=\"isDisabled()\">\n\n <div class=\"tn-slider-track\">\n <div class=\"tn-slider-track-inactive\"></div>\n <div class=\"tn-slider-track-active\">\n <div\n class=\"tn-slider-track-active-fill\"\n [style.transform]=\"'scaleX(' + fillScale() + ')'\">\n </div>\n </div>\n @if ((labelType() === 'track' || labelType() === 'both') && showLabel()) {\n <div class=\"tn-slider-track-label\">\n {{ labelPrefix() }}{{ value() }}{{ labelSuffix() }}\n </div>\n }\n </div>\n\n <div\n class=\"tn-slider-thumb-visual\"\n [style.left.%]=\"fillPercentage()\">\n <div class=\"tn-slider-thumb-knob\"></div>\n @if ((labelType() === 'handle' || labelType() === 'both') && showLabel()) {\n <div\n class=\"tn-slider-thumb-label\"\n [class.visible]=\"labelVisible()\">\n {{ labelPrefix() }}{{ value() }}{{ labelSuffix() }}\n </div>\n }\n </div>\n\n <ng-content />\n</div>\n", styles: [":host{display:block;width:100%;height:48px;position:relative;touch-action:pan-x}.tn-slider-container{position:relative;width:100%;height:100%;display:flex;align-items:center;cursor:pointer}.tn-slider-container[data-disabled=true]{cursor:not-allowed;opacity:.6}.tn-slider-track{position:relative;width:100%;height:4px}.tn-slider-track-inactive{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--tn-lines, #e0e0e0);border-radius:2px}.tn-slider-track-active{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;border-radius:2px}.tn-slider-track-active-fill{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--tn-primary, #007bff);border-radius:2px;transform-origin:left center;transition:transform .1s ease-out}:host ::ng-deep .tn-slider-thumb{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;margin:0;padding:0;cursor:pointer;z-index:2;-webkit-appearance:none;appearance:none;background:none;border:none;outline:none}:host ::ng-deep .tn-slider-thumb:disabled{cursor:not-allowed}:host ::ng-deep .tn-slider-thumb:focus{outline:none}:host ::ng-deep .tn-slider-thumb:focus-visible{outline:none}:host ::ng-deep .tn-slider-container:has(.tn-slider-thumb:focus-visible) .tn-slider-thumb-knob{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-slider-thumb-visual{position:absolute;top:50%;left:0;margin-left:-10px;pointer-events:none;z-index:3;transition:left .1s ease-out}.tn-slider-thumb-knob{width:20px;height:24px;background:var(--tn-primary, #007bff);border:2px solid var(--tn-bg1, white);border-radius:4px;box-shadow:0 2px 4px #0003;transition:box-shadow .15s ease;transform:translateY(-50%)}.tn-slider-container:hover .tn-slider-thumb-knob{box-shadow:0 4px 8px #0000004d}.tn-slider-container[data-disabled=true] .tn-slider-thumb-knob{background:var(--tn-fg2, #999);box-shadow:0 1px 2px #0000001a}.tn-slider-thumb-label{position:absolute;bottom:calc(100% + 16px);left:50%;transform:translate(-50%) translateY(-50%);padding:8px 12px;background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, white);border-radius:6px;font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;opacity:0;pointer-events:none;-webkit-user-select:none;user-select:none;transition:opacity .15s ease;z-index:4;box-shadow:0 2px 8px #00000026}.tn-slider-thumb-label:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid var(--tn-primary, #007bff)}.tn-slider-thumb-label.visible{opacity:1}.tn-slider-track-label{position:absolute;top:-28px;right:0;padding:4px 0;background:transparent;color:var(--tn-fg1, #000);font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none;z-index:2}@media(prefers-reduced-motion:reduce){.tn-slider-track-active-fill,.tn-slider-thumb-visual,.tn-slider-thumb-knob,.tn-slider-thumb-label{transition:none}}@media(prefers-contrast:high){.tn-slider-track-inactive{border:1px solid var(--tn-fg1, #000)}.tn-slider-thumb-knob{border-width:3px;border-color:var(--tn-fg1, #000)}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
13917
14065
|
}
|
|
13918
14066
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderComponent, decorators: [{
|
|
13919
14067
|
type: Component,
|
|
@@ -13926,8 +14074,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
13926
14074
|
], host: {
|
|
13927
14075
|
'class': 'tn-slider',
|
|
13928
14076
|
'[attr.aria-disabled]': 'isDisabled()'
|
|
13929
|
-
}, template: "<div\n #sliderContainer\n class=\"tn-slider-container\"\n tnTestIdType=\"slider\"\n [tnTestId]=\"testId()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.data-disabled]=\"isDisabled()\"
|
|
13930
|
-
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], labelPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPrefix", required: false }] }], labelSuffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelSuffix", required: false }] }], labelType: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelType", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }],
|
|
14077
|
+
}, template: "<!--\n Pointer interaction (click-to-position and press-drag, anywhere on the track)\n is handled by the projected native range input, which overlays the whole\n container (see TnSliderThumbDirective): a click jumps the thumb and fires\n `input` \u2192 onInput; a press-and-move runs the directive's global drag handlers.\n No container-level click handler is needed \u2014 and one wouldn't fire anyway, as\n the overlay calls stopPropagation() on its own pointer events.\n-->\n<div\n #sliderContainer\n class=\"tn-slider-container\"\n tnTestIdType=\"slider\"\n [tnTestId]=\"testId()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.data-disabled]=\"isDisabled()\">\n\n <div class=\"tn-slider-track\">\n <div class=\"tn-slider-track-inactive\"></div>\n <div class=\"tn-slider-track-active\">\n <div\n class=\"tn-slider-track-active-fill\"\n [style.transform]=\"'scaleX(' + fillScale() + ')'\">\n </div>\n </div>\n @if ((labelType() === 'track' || labelType() === 'both') && showLabel()) {\n <div class=\"tn-slider-track-label\">\n {{ labelPrefix() }}{{ value() }}{{ labelSuffix() }}\n </div>\n }\n </div>\n\n <div\n class=\"tn-slider-thumb-visual\"\n [style.left.%]=\"fillPercentage()\">\n <div class=\"tn-slider-thumb-knob\"></div>\n @if ((labelType() === 'handle' || labelType() === 'both') && showLabel()) {\n <div\n class=\"tn-slider-thumb-label\"\n [class.visible]=\"labelVisible()\">\n {{ labelPrefix() }}{{ value() }}{{ labelSuffix() }}\n </div>\n }\n </div>\n\n <ng-content />\n</div>\n", styles: [":host{display:block;width:100%;height:48px;position:relative;touch-action:pan-x}.tn-slider-container{position:relative;width:100%;height:100%;display:flex;align-items:center;cursor:pointer}.tn-slider-container[data-disabled=true]{cursor:not-allowed;opacity:.6}.tn-slider-track{position:relative;width:100%;height:4px}.tn-slider-track-inactive{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--tn-lines, #e0e0e0);border-radius:2px}.tn-slider-track-active{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;border-radius:2px}.tn-slider-track-active-fill{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--tn-primary, #007bff);border-radius:2px;transform-origin:left center;transition:transform .1s ease-out}:host ::ng-deep .tn-slider-thumb{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;margin:0;padding:0;cursor:pointer;z-index:2;-webkit-appearance:none;appearance:none;background:none;border:none;outline:none}:host ::ng-deep .tn-slider-thumb:disabled{cursor:not-allowed}:host ::ng-deep .tn-slider-thumb:focus{outline:none}:host ::ng-deep .tn-slider-thumb:focus-visible{outline:none}:host ::ng-deep .tn-slider-container:has(.tn-slider-thumb:focus-visible) .tn-slider-thumb-knob{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-slider-thumb-visual{position:absolute;top:50%;left:0;margin-left:-10px;pointer-events:none;z-index:3;transition:left .1s ease-out}.tn-slider-thumb-knob{width:20px;height:24px;background:var(--tn-primary, #007bff);border:2px solid var(--tn-bg1, white);border-radius:4px;box-shadow:0 2px 4px #0003;transition:box-shadow .15s ease;transform:translateY(-50%)}.tn-slider-container:hover .tn-slider-thumb-knob{box-shadow:0 4px 8px #0000004d}.tn-slider-container[data-disabled=true] .tn-slider-thumb-knob{background:var(--tn-fg2, #999);box-shadow:0 1px 2px #0000001a}.tn-slider-thumb-label{position:absolute;bottom:calc(100% + 16px);left:50%;transform:translate(-50%) translateY(-50%);padding:8px 12px;background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, white);border-radius:6px;font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;opacity:0;pointer-events:none;-webkit-user-select:none;user-select:none;transition:opacity .15s ease;z-index:4;box-shadow:0 2px 8px #00000026}.tn-slider-thumb-label:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid var(--tn-primary, #007bff)}.tn-slider-thumb-label.visible{opacity:1}.tn-slider-track-label{position:absolute;top:-28px;right:0;padding:4px 0;background:transparent;color:var(--tn-fg1, #000);font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none;z-index:2}@media(prefers-reduced-motion:reduce){.tn-slider-track-active-fill,.tn-slider-thumb-visual,.tn-slider-thumb-knob,.tn-slider-thumb-label{transition:none}}@media(prefers-contrast:high){.tn-slider-track-inactive{border:1px solid var(--tn-fg1, #000)}.tn-slider-thumb-knob{border-width:3px;border-color:var(--tn-fg1, #000)}}\n"] }]
|
|
14078
|
+
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], labelPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPrefix", required: false }] }], labelSuffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelSuffix", required: false }] }], labelType: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelType", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], thumbDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnSliderThumbDirective), { isSignal: true }] }], sliderContainer: [{ type: i0.ViewChild, args: ['sliderContainer', { isSignal: true }] }] } });
|
|
13931
14079
|
|
|
13932
14080
|
class TnSliderWithLabelDirective {
|
|
13933
14081
|
enabled = input(true, { ...(ngDevMode ? { debugName: "enabled" } : {}), alias: 'tnSliderWithLabel' });
|