@truenas/ui-components 0.3.7 → 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.
|
@@ -1760,6 +1760,23 @@ class TnIconComponent {
|
|
|
1760
1760
|
}
|
|
1761
1761
|
return null;
|
|
1762
1762
|
}, ...(ngDevMode ? [{ debugName: "hostDimension" }] : []));
|
|
1763
|
+
/**
|
|
1764
|
+
* Inline min-width/min-height override for the custom/full-size paths.
|
|
1765
|
+
* - customSize: pin the minimum to the custom value so flex layouts can't
|
|
1766
|
+
* squish it (mirrors the preset min-* rules in the stylesheet).
|
|
1767
|
+
* - fullSize: relax the minimum to 0 so the icon can shrink with its
|
|
1768
|
+
* container instead of being clamped by the `size` attribute's preset min.
|
|
1769
|
+
*/
|
|
1770
|
+
hostMinDimension = computed(() => {
|
|
1771
|
+
const custom = this.customSize();
|
|
1772
|
+
if (custom) {
|
|
1773
|
+
return custom;
|
|
1774
|
+
}
|
|
1775
|
+
if (this.fullSize()) {
|
|
1776
|
+
return '0';
|
|
1777
|
+
}
|
|
1778
|
+
return null;
|
|
1779
|
+
}, ...(ngDevMode ? [{ debugName: "hostMinDimension" }] : []));
|
|
1763
1780
|
hostFontSize = computed(() => {
|
|
1764
1781
|
const custom = this.customSize();
|
|
1765
1782
|
if (custom) {
|
|
@@ -1951,7 +1968,7 @@ class TnIconComponent {
|
|
|
1951
1968
|
`To include it, add ${markerExample} to your source and run 'npx truenas-icons generate'.`);
|
|
1952
1969
|
}
|
|
1953
1970
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1954
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnIconComponent, isStandalone: true, selector: "tn-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, fullSize: { classPropertyName: "fullSize", publicName: "fullSize", isSignal: true, isRequired: false, transformFunction: null }, customSize: { classPropertyName: "customSize", publicName: "customSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()", "attr.library": "library()", "attr.size": "size()", "attr.color": "color()", "attr.full-size": "fullSize() || null", "attr.custom-size": "customSize() || null", "style.width": "hostDimension()", "style.height": "hostDimension()", "style.font-size": "hostFontSize()", "style.color": "color() || null" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n tnTestIdType=\"icon\"\n [tnTestId]=\"testId()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult().source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult().spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult().content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n }\n</div>", styles: ["tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;color:var(--tn-icon-color, currentColor);width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));font-size:var(--tn-icon-size, var(--tn-icon-md))}tn-icon[size=xs]{width:var(--tn-icon-xs);height:var(--tn-icon-xs);font-size:var(--tn-icon-xs)}tn-icon[size=sm]{width:var(--tn-icon-sm);height:var(--tn-icon-sm);font-size:var(--tn-icon-sm)}tn-icon[size=md]{width:var(--tn-icon-md);height:var(--tn-icon-md);font-size:var(--tn-icon-md)}tn-icon[size=lg]{width:var(--tn-icon-lg);height:var(--tn-icon-lg);font-size:var(--tn-icon-lg)}tn-icon[size=xl]{width:var(--tn-icon-xl);height:var(--tn-icon-xl);font-size:var(--tn-icon-xl)}.tn-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1971
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnIconComponent, isStandalone: true, selector: "tn-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, fullSize: { classPropertyName: "fullSize", publicName: "fullSize", isSignal: true, isRequired: false, transformFunction: null }, customSize: { classPropertyName: "customSize", publicName: "customSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()", "attr.library": "library()", "attr.size": "size()", "attr.color": "color()", "attr.full-size": "fullSize() || null", "attr.custom-size": "customSize() || null", "style.width": "hostDimension()", "style.height": "hostDimension()", "style.min-width": "hostMinDimension()", "style.min-height": "hostMinDimension()", "style.font-size": "hostFontSize()", "style.color": "color() || null" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n tnTestIdType=\"icon\"\n [tnTestId]=\"testId()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult().source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult().spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult().content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n }\n</div>", styles: ["tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;color:var(--tn-icon-color, currentColor);width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));min-width:var(--tn-icon-size, var(--tn-icon-md));min-height:var(--tn-icon-size, var(--tn-icon-md));font-size:var(--tn-icon-size, var(--tn-icon-md))}tn-icon[size=xs]{width:var(--tn-icon-xs);height:var(--tn-icon-xs);min-width:var(--tn-icon-xs);min-height:var(--tn-icon-xs);font-size:var(--tn-icon-xs)}tn-icon[size=sm]{width:var(--tn-icon-sm);height:var(--tn-icon-sm);min-width:var(--tn-icon-sm);min-height:var(--tn-icon-sm);font-size:var(--tn-icon-sm)}tn-icon[size=md]{width:var(--tn-icon-md);height:var(--tn-icon-md);min-width:var(--tn-icon-md);min-height:var(--tn-icon-md);font-size:var(--tn-icon-md)}tn-icon[size=lg]{width:var(--tn-icon-lg);height:var(--tn-icon-lg);min-width:var(--tn-icon-lg);min-height:var(--tn-icon-lg);font-size:var(--tn-icon-lg)}tn-icon[size=xl]{width:var(--tn-icon-xl);height:var(--tn-icon-xl);min-width:var(--tn-icon-xl);min-height:var(--tn-icon-xl);font-size:var(--tn-icon-xl)}.tn-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1955
1972
|
}
|
|
1956
1973
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconComponent, decorators: [{
|
|
1957
1974
|
type: Component,
|
|
@@ -1964,9 +1981,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1964
1981
|
'[attr.custom-size]': 'customSize() || null',
|
|
1965
1982
|
'[style.width]': 'hostDimension()',
|
|
1966
1983
|
'[style.height]': 'hostDimension()',
|
|
1984
|
+
'[style.min-width]': 'hostMinDimension()',
|
|
1985
|
+
'[style.min-height]': 'hostMinDimension()',
|
|
1967
1986
|
'[style.font-size]': 'hostFontSize()',
|
|
1968
1987
|
'[style.color]': 'color() || null',
|
|
1969
|
-
}, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n tnTestIdType=\"icon\"\n [tnTestId]=\"testId()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult().source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult().spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult().content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n }\n</div>", styles: ["tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;color:var(--tn-icon-color, currentColor);width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));font-size:var(--tn-icon-size, var(--tn-icon-md))}tn-icon[size=xs]{width:var(--tn-icon-xs);height:var(--tn-icon-xs);font-size:var(--tn-icon-xs)}tn-icon[size=sm]{width:var(--tn-icon-sm);height:var(--tn-icon-sm);font-size:var(--tn-icon-sm)}tn-icon[size=md]{width:var(--tn-icon-md);height:var(--tn-icon-md);font-size:var(--tn-icon-md)}tn-icon[size=lg]{width:var(--tn-icon-lg);height:var(--tn-icon-lg);font-size:var(--tn-icon-lg)}tn-icon[size=xl]{width:var(--tn-icon-xl);height:var(--tn-icon-xl);font-size:var(--tn-icon-xl)}.tn-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}\n"] }]
|
|
1988
|
+
}, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n tnTestIdType=\"icon\"\n [tnTestId]=\"testId()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult().source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult().spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult().content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n }\n</div>", styles: ["tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;color:var(--tn-icon-color, currentColor);width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));min-width:var(--tn-icon-size, var(--tn-icon-md));min-height:var(--tn-icon-size, var(--tn-icon-md));font-size:var(--tn-icon-size, var(--tn-icon-md))}tn-icon[size=xs]{width:var(--tn-icon-xs);height:var(--tn-icon-xs);min-width:var(--tn-icon-xs);min-height:var(--tn-icon-xs);font-size:var(--tn-icon-xs)}tn-icon[size=sm]{width:var(--tn-icon-sm);height:var(--tn-icon-sm);min-width:var(--tn-icon-sm);min-height:var(--tn-icon-sm);font-size:var(--tn-icon-sm)}tn-icon[size=md]{width:var(--tn-icon-md);height:var(--tn-icon-md);min-width:var(--tn-icon-md);min-height:var(--tn-icon-md);font-size:var(--tn-icon-md)}tn-icon[size=lg]{width:var(--tn-icon-lg);height:var(--tn-icon-lg);min-width:var(--tn-icon-lg);min-height:var(--tn-icon-lg);font-size:var(--tn-icon-lg)}tn-icon[size=xl]{width:var(--tn-icon-xl);height:var(--tn-icon-xl);min-width:var(--tn-icon-xl);min-height:var(--tn-icon-xl);font-size:var(--tn-icon-xl)}.tn-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}\n"] }]
|
|
1970
1989
|
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], library: [{ type: i0.Input, args: [{ isSignal: true, alias: "library", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], fullSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullSize", required: false }] }], customSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "customSize", required: false }] }], svgContainer: [{ type: i0.ViewChild, args: ['svgContainer', { isSignal: true }] }] } });
|
|
1971
1990
|
|
|
1972
1991
|
/**
|
|
@@ -13544,11 +13563,33 @@ class TnSliderThumbDirective {
|
|
|
13544
13563
|
slider; // Will be set by parent slider component
|
|
13545
13564
|
onTouched = () => { };
|
|
13546
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.
|
|
13547
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;
|
|
13548
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;
|
|
13549
13588
|
ngOnInit() {
|
|
13550
13589
|
// Make the native input visually hidden but still accessible
|
|
13551
13590
|
const input = this.elementRef.nativeElement;
|
|
13591
|
+
this.fallbackAriaLabel = input.getAttribute('aria-label');
|
|
13592
|
+
this.fallbackAriaLabelledby = input.getAttribute('aria-labelledby');
|
|
13552
13593
|
input.style.opacity = '0';
|
|
13553
13594
|
input.style.position = 'absolute';
|
|
13554
13595
|
input.style.width = '100%';
|
|
@@ -13561,10 +13602,46 @@ class TnSliderThumbDirective {
|
|
|
13561
13602
|
ngOnDestroy() {
|
|
13562
13603
|
this.cleanup();
|
|
13563
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
|
+
}
|
|
13564
13617
|
// ControlValueAccessor implementation
|
|
13565
13618
|
writeValue(value) {
|
|
13566
|
-
|
|
13567
|
-
|
|
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
|
+
}
|
|
13568
13645
|
}
|
|
13569
13646
|
}
|
|
13570
13647
|
registerOnChange(fn) {
|
|
@@ -13580,21 +13657,45 @@ class TnSliderThumbDirective {
|
|
|
13580
13657
|
}
|
|
13581
13658
|
}
|
|
13582
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
|
+
}
|
|
13583
13668
|
const input = event.target;
|
|
13584
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
|
+
}
|
|
13585
13676
|
if (this.slider) {
|
|
13586
13677
|
this.slider.updateValue(value);
|
|
13587
13678
|
}
|
|
13588
13679
|
this.onChangeCallback(value);
|
|
13589
13680
|
}
|
|
13590
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() {
|
|
13591
13691
|
this.onTouched();
|
|
13692
|
+
this.slider?.markTouched();
|
|
13592
13693
|
}
|
|
13593
13694
|
onMouseDown(event) {
|
|
13594
13695
|
if (this.disabled()) {
|
|
13595
13696
|
return;
|
|
13596
13697
|
}
|
|
13597
|
-
this.
|
|
13698
|
+
this.isPointerDown = true;
|
|
13598
13699
|
this.addGlobalListeners();
|
|
13599
13700
|
event.stopPropagation(); // Prevent track click
|
|
13600
13701
|
}
|
|
@@ -13602,7 +13703,7 @@ class TnSliderThumbDirective {
|
|
|
13602
13703
|
if (this.disabled()) {
|
|
13603
13704
|
return;
|
|
13604
13705
|
}
|
|
13605
|
-
this.
|
|
13706
|
+
this.isPointerDown = true;
|
|
13606
13707
|
this.addGlobalListeners();
|
|
13607
13708
|
event.stopPropagation(); // Prevent track click
|
|
13608
13709
|
}
|
|
@@ -13619,31 +13720,35 @@ class TnSliderThumbDirective {
|
|
|
13619
13720
|
document.removeEventListener('touchend', this.onGlobalTouchEnd);
|
|
13620
13721
|
}
|
|
13621
13722
|
onGlobalMouseMove = (event) => {
|
|
13622
|
-
if (!this.
|
|
13723
|
+
if (!this.isPointerDown || this.disabled()) {
|
|
13623
13724
|
return;
|
|
13624
13725
|
}
|
|
13726
|
+
this.isDragging = true;
|
|
13625
13727
|
event.preventDefault();
|
|
13626
13728
|
this.updateValueFromPosition(event.clientX);
|
|
13627
13729
|
};
|
|
13628
13730
|
onGlobalMouseUp = () => {
|
|
13629
|
-
if (this.
|
|
13731
|
+
if (this.isPointerDown) {
|
|
13732
|
+
this.isPointerDown = false;
|
|
13630
13733
|
this.isDragging = false;
|
|
13631
|
-
this.
|
|
13734
|
+
this.notifyTouched();
|
|
13632
13735
|
this.removeGlobalListeners();
|
|
13633
13736
|
}
|
|
13634
13737
|
};
|
|
13635
13738
|
onGlobalTouchMove = (event) => {
|
|
13636
|
-
if (!this.
|
|
13739
|
+
if (!this.isPointerDown || this.disabled()) {
|
|
13637
13740
|
return;
|
|
13638
13741
|
}
|
|
13742
|
+
this.isDragging = true;
|
|
13639
13743
|
event.preventDefault();
|
|
13640
13744
|
const touch = event.touches[0];
|
|
13641
13745
|
this.updateValueFromPosition(touch.clientX);
|
|
13642
13746
|
};
|
|
13643
13747
|
onGlobalTouchEnd = () => {
|
|
13644
|
-
if (this.
|
|
13748
|
+
if (this.isPointerDown) {
|
|
13749
|
+
this.isPointerDown = false;
|
|
13645
13750
|
this.isDragging = false;
|
|
13646
|
-
this.
|
|
13751
|
+
this.notifyTouched();
|
|
13647
13752
|
this.removeGlobalListeners();
|
|
13648
13753
|
}
|
|
13649
13754
|
};
|
|
@@ -13657,12 +13762,58 @@ class TnSliderThumbDirective {
|
|
|
13657
13762
|
const maxVal = this.slider.max();
|
|
13658
13763
|
const newValue = minVal + (percentage * (maxVal - minVal));
|
|
13659
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}`;
|
|
13660
13811
|
}
|
|
13661
13812
|
cleanup() {
|
|
13662
13813
|
this.removeGlobalListeners();
|
|
13663
13814
|
}
|
|
13664
13815
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderThumbDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
13665
|
-
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: [
|
|
13666
13817
|
{
|
|
13667
13818
|
provide: NG_VALUE_ACCESSOR,
|
|
13668
13819
|
useExisting: forwardRef(() => TnSliderThumbDirective),
|
|
@@ -13690,15 +13841,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
13690
13841
|
'[attr.max]': 'slider?.max()',
|
|
13691
13842
|
'[attr.step]': 'slider?.step()',
|
|
13692
13843
|
'[value]': 'slider?.value()',
|
|
13844
|
+
'[attr.aria-valuetext]': 'ariaValueText()',
|
|
13845
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
13846
|
+
'[attr.aria-labelledby]': 'ariaLabelledby()',
|
|
13693
13847
|
'(input)': 'onInput($event)',
|
|
13694
13848
|
'(change)': 'onChange($event)',
|
|
13695
|
-
'(blur)': '
|
|
13849
|
+
'(blur)': 'notifyTouched()',
|
|
13696
13850
|
'(mousedown)': 'onMouseDown($event)',
|
|
13697
13851
|
'(touchstart)': 'onTouchStart($event)'
|
|
13698
13852
|
}
|
|
13699
13853
|
}]
|
|
13700
13854
|
}] });
|
|
13701
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
|
+
*/
|
|
13702
13866
|
class TnSliderComponent {
|
|
13703
13867
|
min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : []));
|
|
13704
13868
|
max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : []));
|
|
@@ -13712,9 +13876,18 @@ class TnSliderComponent {
|
|
|
13712
13876
|
* is configured via `TN_TEST_ATTR` (default `data-testid`).
|
|
13713
13877
|
*/
|
|
13714
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' });
|
|
13715
13889
|
thumbDirective = contentChild.required(TnSliderThumbDirective);
|
|
13716
13890
|
sliderContainer = viewChild.required('sliderContainer');
|
|
13717
|
-
thumbVisual = viewChild.required('thumbVisual');
|
|
13718
13891
|
onChange = (_value) => { };
|
|
13719
13892
|
onTouched = () => { };
|
|
13720
13893
|
value = signal(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
@@ -13735,13 +13908,6 @@ class TnSliderComponent {
|
|
|
13735
13908
|
fillScale = computed(() => {
|
|
13736
13909
|
return this.fillPercentage() / 100;
|
|
13737
13910
|
}, ...(ngDevMode ? [{ debugName: "fillScale" }] : []));
|
|
13738
|
-
// Computed position for thumb (in pixels from left)
|
|
13739
|
-
thumbPosition = computed(() => {
|
|
13740
|
-
const containerWidth = this.sliderContainer()?.nativeElement?.offsetWidth || 0;
|
|
13741
|
-
const percentage = this.fillPercentage();
|
|
13742
|
-
// Center the thumb (20px width, so -10px offset)
|
|
13743
|
-
return (containerWidth * percentage / 100) - 10;
|
|
13744
|
-
}, ...(ngDevMode ? [{ debugName: "thumbPosition" }] : []));
|
|
13745
13911
|
// Public signals for label management
|
|
13746
13912
|
showLabel = this._showLabel.asReadonly();
|
|
13747
13913
|
labelVisible = this._labelVisible.asReadonly();
|
|
@@ -13763,13 +13929,24 @@ class TnSliderComponent {
|
|
|
13763
13929
|
}
|
|
13764
13930
|
});
|
|
13765
13931
|
}
|
|
13766
|
-
|
|
13767
|
-
//
|
|
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.
|
|
13768
13936
|
const thumbDirective = this.thumbDirective();
|
|
13769
13937
|
if (thumbDirective) {
|
|
13770
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
|
+
}
|
|
13771
13947
|
}
|
|
13772
|
-
|
|
13948
|
+
}
|
|
13949
|
+
ngAfterViewInit() {
|
|
13773
13950
|
// Set up handle interaction listeners if labelType is handle or both
|
|
13774
13951
|
const currentLabelType = this.labelType();
|
|
13775
13952
|
if ((currentLabelType === 'handle' || currentLabelType === 'both') && this._showLabel()) {
|
|
@@ -13791,6 +13968,15 @@ class TnSliderComponent {
|
|
|
13791
13968
|
registerOnTouched(fn) {
|
|
13792
13969
|
this.onTouched = fn;
|
|
13793
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
|
+
}
|
|
13794
13980
|
setDisabledState(isDisabled) {
|
|
13795
13981
|
this.formDisabled.set(isDisabled);
|
|
13796
13982
|
}
|
|
@@ -13799,7 +13985,6 @@ class TnSliderComponent {
|
|
|
13799
13985
|
const clampedValue = this.clampValue(newValue);
|
|
13800
13986
|
this.value.set(clampedValue);
|
|
13801
13987
|
this.onChange(clampedValue);
|
|
13802
|
-
this.updateThumbPosition();
|
|
13803
13988
|
}
|
|
13804
13989
|
enableLabel() {
|
|
13805
13990
|
this._showLabel.set(true);
|
|
@@ -13813,24 +13998,6 @@ class TnSliderComponent {
|
|
|
13813
13998
|
getSliderRect() {
|
|
13814
13999
|
return this.sliderContainer().nativeElement.getBoundingClientRect();
|
|
13815
14000
|
}
|
|
13816
|
-
onTrackClick(event) {
|
|
13817
|
-
if (this.isDisabled()) {
|
|
13818
|
-
return;
|
|
13819
|
-
}
|
|
13820
|
-
event.preventDefault();
|
|
13821
|
-
const rect = this.getSliderRect();
|
|
13822
|
-
const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
|
13823
|
-
const percentage = (clientX - rect.left) / rect.width;
|
|
13824
|
-
const minVal = this.min();
|
|
13825
|
-
const maxVal = this.max();
|
|
13826
|
-
const newValue = minVal + (percentage * (maxVal - minVal));
|
|
13827
|
-
this.updateValue(newValue);
|
|
13828
|
-
this.onTouched();
|
|
13829
|
-
}
|
|
13830
|
-
updateThumbPosition() {
|
|
13831
|
-
// Thumb position is now handled by computed signal and template binding
|
|
13832
|
-
// No manual DOM manipulation needed
|
|
13833
|
-
}
|
|
13834
14001
|
clampValue(value) {
|
|
13835
14002
|
const minVal = this.min();
|
|
13836
14003
|
const maxVal = this.max();
|
|
@@ -13888,13 +14055,13 @@ class TnSliderComponent {
|
|
|
13888
14055
|
}
|
|
13889
14056
|
};
|
|
13890
14057
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13891
|
-
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: [
|
|
13892
14059
|
{
|
|
13893
14060
|
provide: NG_VALUE_ACCESSOR,
|
|
13894
14061
|
useExisting: forwardRef(() => TnSliderComponent),
|
|
13895
14062
|
multi: true
|
|
13896
14063
|
}
|
|
13897
|
-
], 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"] }] });
|
|
13898
14065
|
}
|
|
13899
14066
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSliderComponent, decorators: [{
|
|
13900
14067
|
type: Component,
|
|
@@ -13907,8 +14074,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
13907
14074
|
], host: {
|
|
13908
14075
|
'class': 'tn-slider',
|
|
13909
14076
|
'[attr.aria-disabled]': 'isDisabled()'
|
|
13910
|
-
}, 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()\"
|
|
13911
|
-
}], 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 }] }] } });
|
|
13912
14079
|
|
|
13913
14080
|
class TnSliderWithLabelDirective {
|
|
13914
14081
|
enabled = input(true, { ...(ngDevMode ? { debugName: "enabled" } : {}), alias: 'tnSliderWithLabel' });
|