@truenas/ui-components 0.1.60 → 0.1.61

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Renderer2, ElementRef, input, effect, Directive, output, viewChild, signal, computed, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, isDevMode, ViewEncapsulation, contentChildren, ViewContainerRef, contentChild, ChangeDetectorRef, HostListener, TemplateRef, DestroyRef, isSignal, untracked, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
2
+ import { InjectionToken, inject, Renderer2, ElementRef, input, effect, Directive, output, viewChild, signal, computed, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, isDevMode, ViewEncapsulation, contentChildren, ViewContainerRef, HostListener, contentChild, ChangeDetectorRef, TemplateRef, DestroyRef, isSignal, untracked, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
3
3
  import * as i2 from '@angular/forms';
4
4
  import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
5
5
  import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
@@ -13,8 +13,8 @@ import { DomSanitizer } from '@angular/platform-browser';
13
13
  import { HttpClient } from '@angular/common/http';
14
14
  import { firstValueFrom, BehaviorSubject, merge, Subject } from 'rxjs';
15
15
  import { RouterLink } from '@angular/router';
16
- import { Overlay, OverlayModule, OverlayPositionBuilder } from '@angular/cdk/overlay';
17
- import { TemplatePortal, PortalModule, ComponentPortal } from '@angular/cdk/portal';
16
+ import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
17
+ import { ComponentPortal, TemplatePortal, PortalModule } from '@angular/cdk/portal';
18
18
  import { CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
19
19
  import { trigger, state, transition, style, animate } from '@angular/animations';
20
20
  import { SPACE, ENTER, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
@@ -1785,10 +1785,244 @@ class TnButtonHarness extends ComponentHarness {
1785
1785
  }
1786
1786
  }
1787
1787
 
1788
+ class TnTooltipComponent {
1789
+ message = input('', ...(ngDevMode ? [{ debugName: "message" }] : []));
1790
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
1791
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1792
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnTooltipComponent, isStandalone: true, selector: "tn-tooltip", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-tooltip-component" }, ngImport: i0, template: "<div\n class=\"tn-tooltip\"\n role=\"tooltip\"\n [id]=\"id()\"\n [attr.aria-hidden]=\"false\">\n {{ message() }}\n</div>\n", styles: [":host{display:block;pointer-events:none;z-index:1200}.tn-tooltip{background:#373737e6;color:#fff;padding:6px 8px;border-radius:4px;font-size:12px;font-weight:500;line-height:1.4;max-width:200px;word-wrap:break-word;white-space:pre-line;box-shadow:0 2px 8px #0003;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:tn-tooltip-show .15s cubic-bezier(0,0,.2,1) forwards;transform-origin:center bottom}@keyframes tn-tooltip-show{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.tn-tooltip{position:relative}.tn-tooltip:after{content:\"\";position:absolute;width:0;height:0;border-style:solid;z-index:1}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{top:100%;left:50%;transform:translate(-50%);border-width:6px 6px 0 6px;border-color:rgba(55,55,55,.9) transparent transparent transparent}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{bottom:100%;left:50%;transform:translate(-50%);border-width:0 6px 6px 6px;border-color:transparent transparent rgba(55,55,55,.9) transparent}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{top:50%;left:100%;transform:translateY(-50%);border-width:6px 0 6px 6px;border-color:transparent transparent transparent rgba(55,55,55,.9)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{top:50%;right:100%;transform:translateY(-50%);border-width:6px 6px 6px 0;border-color:transparent rgba(55,55,55,.9) transparent transparent}@media(prefers-contrast:high){.tn-tooltip{background:var(--tn-fg1, #000);color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #999)}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{border-top-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{border-bottom-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{border-left-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{border-right-color:var(--tn-fg1, #000)}}:host-context(.tn-slider-thumb-label) .tn-tooltip{font-size:11px;padding:4px 6px;font-weight:600;min-width:24px;text-align:center;background:#373737f2}@media(prefers-reduced-motion:reduce){.tn-tooltip{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1793
+ }
1794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, decorators: [{
1795
+ type: Component,
1796
+ args: [{ selector: 'tn-tooltip', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1797
+ 'class': 'tn-tooltip-component'
1798
+ }, template: "<div\n class=\"tn-tooltip\"\n role=\"tooltip\"\n [id]=\"id()\"\n [attr.aria-hidden]=\"false\">\n {{ message() }}\n</div>\n", styles: [":host{display:block;pointer-events:none;z-index:1200}.tn-tooltip{background:#373737e6;color:#fff;padding:6px 8px;border-radius:4px;font-size:12px;font-weight:500;line-height:1.4;max-width:200px;word-wrap:break-word;white-space:pre-line;box-shadow:0 2px 8px #0003;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:tn-tooltip-show .15s cubic-bezier(0,0,.2,1) forwards;transform-origin:center bottom}@keyframes tn-tooltip-show{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.tn-tooltip{position:relative}.tn-tooltip:after{content:\"\";position:absolute;width:0;height:0;border-style:solid;z-index:1}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{top:100%;left:50%;transform:translate(-50%);border-width:6px 6px 0 6px;border-color:rgba(55,55,55,.9) transparent transparent transparent}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{bottom:100%;left:50%;transform:translate(-50%);border-width:0 6px 6px 6px;border-color:transparent transparent rgba(55,55,55,.9) transparent}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{top:50%;left:100%;transform:translateY(-50%);border-width:6px 0 6px 6px;border-color:transparent transparent transparent rgba(55,55,55,.9)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{top:50%;right:100%;transform:translateY(-50%);border-width:6px 6px 6px 0;border-color:transparent rgba(55,55,55,.9) transparent transparent}@media(prefers-contrast:high){.tn-tooltip{background:var(--tn-fg1, #000);color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #999)}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{border-top-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{border-bottom-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{border-left-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{border-right-color:var(--tn-fg1, #000)}}:host-context(.tn-slider-thumb-label) .tn-tooltip{font-size:11px;padding:4px 6px;font-weight:600;min-width:24px;text-align:center;background:#373737f2}@media(prefers-reduced-motion:reduce){.tn-tooltip{animation:none}}\n"] }]
1799
+ }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
1800
+
1801
+ /* eslint-disable @angular-eslint/no-input-rename */
1802
+ // Input aliasing is intentional for directive API consistency (e.g., ixTooltip, ixTooltipPosition)
1803
+ // This follows the standard Angular pattern used by Material and other directive-based components
1804
+ class TnTooltipDirective {
1805
+ message = input('', { ...(ngDevMode ? { debugName: "message" } : {}), alias: 'tnTooltip' });
1806
+ position = input('above', { ...(ngDevMode ? { debugName: "position" } : {}), alias: 'tnTooltipPosition' });
1807
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'tnTooltipDisabled' });
1808
+ showDelay = input(0, { ...(ngDevMode ? { debugName: "showDelay" } : {}), alias: 'tnTooltipShowDelay' });
1809
+ hideDelay = input(0, { ...(ngDevMode ? { debugName: "hideDelay" } : {}), alias: 'tnTooltipHideDelay' });
1810
+ tooltipClass = input('', { ...(ngDevMode ? { debugName: "tooltipClass" } : {}), alias: 'tnTooltipClass' });
1811
+ _overlayRef = null;
1812
+ _tooltipInstance = null;
1813
+ _showTimeout = null;
1814
+ _hideTimeout = null;
1815
+ _isTooltipVisible = false;
1816
+ _positionSub = null;
1817
+ _tooltipId = '';
1818
+ /**
1819
+ * Only point `aria-describedby` at the tooltip when there is actually a message to
1820
+ * describe. Otherwise every host (e.g. an icon button with no tooltip) would carry a
1821
+ * dangling reference to a tooltip element that is never rendered.
1822
+ */
1823
+ get _ariaDescribedBy() {
1824
+ return !this.disabled() && this.message() ? this._tooltipId : null;
1825
+ }
1826
+ _overlay = inject(Overlay);
1827
+ _elementRef = inject((ElementRef));
1828
+ _viewContainerRef = inject(ViewContainerRef);
1829
+ _overlayPositionBuilder = inject(OverlayPositionBuilder);
1830
+ ngOnInit() {
1831
+ // Generate unique ID for aria-describedby
1832
+ this._tooltipId = `tn-tooltip-${Math.random().toString(36).substr(2, 9)}`;
1833
+ }
1834
+ ngOnDestroy() {
1835
+ this._clearTimeouts();
1836
+ this.hide(0);
1837
+ this._positionSub?.unsubscribe();
1838
+ if (this._overlayRef) {
1839
+ this._overlayRef.dispose();
1840
+ this._overlayRef = null;
1841
+ }
1842
+ }
1843
+ _onMouseEnter() {
1844
+ if (!this.disabled() && this.message()) {
1845
+ this.show(this.showDelay());
1846
+ }
1847
+ }
1848
+ _onMouseLeave() {
1849
+ this.hide(this.hideDelay());
1850
+ }
1851
+ _onFocus() {
1852
+ if (!this.disabled() && this.message()) {
1853
+ this.show(this.showDelay());
1854
+ }
1855
+ }
1856
+ _onBlur() {
1857
+ this.hide(this.hideDelay());
1858
+ }
1859
+ _onKeydown(event) {
1860
+ if (event.key === 'Escape' && this._isTooltipVisible) {
1861
+ this.hide(0);
1862
+ }
1863
+ }
1864
+ /** Shows the tooltip */
1865
+ show(delay = 0) {
1866
+ if (this.disabled() || !this.message() || this._isTooltipVisible) {
1867
+ return;
1868
+ }
1869
+ this._clearTimeouts();
1870
+ this._showTimeout = setTimeout(() => {
1871
+ if (!this._overlayRef) {
1872
+ this._createOverlay();
1873
+ }
1874
+ this._attachTooltip();
1875
+ }, delay);
1876
+ }
1877
+ /** Hides the tooltip */
1878
+ hide(delay = 0) {
1879
+ this._clearTimeouts();
1880
+ this._hideTimeout = setTimeout(() => {
1881
+ if (this._tooltipInstance) {
1882
+ this._tooltipInstance.destroy();
1883
+ this._tooltipInstance = null;
1884
+ this._isTooltipVisible = false;
1885
+ }
1886
+ }, delay);
1887
+ }
1888
+ /** Toggle the tooltip visibility */
1889
+ toggle() {
1890
+ this._isTooltipVisible ? this.hide() : this.show();
1891
+ }
1892
+ _createOverlay() {
1893
+ const positions = this._getPositions();
1894
+ const positionStrategy = this._overlayPositionBuilder
1895
+ .flexibleConnectedTo(this._elementRef)
1896
+ .withPositions(positions)
1897
+ .withFlexibleDimensions(false)
1898
+ .withViewportMargin(8)
1899
+ .withScrollableContainers([]);
1900
+ this._overlayRef = this._overlay.create({
1901
+ positionStrategy,
1902
+ scrollStrategy: this._overlay.scrollStrategies.reposition({ scrollThrottle: 20 }),
1903
+ panelClass: ['tn-tooltip-panel', `tn-tooltip-panel-${this.position()}`, this.tooltipClass()].filter(Boolean),
1904
+ });
1905
+ this._positionSub = positionStrategy.positionChanges
1906
+ .subscribe((change) => {
1907
+ // The position panelClass is applied to the overlay pane (overlayElement), so the
1908
+ // resolved-position class must be toggled on that same element. Updating the parent
1909
+ // instead left the stale initial class on the pane, so after a flip both the original
1910
+ // and resolved classes matched :host-context and the arrow rendered incorrectly.
1911
+ const panel = this._overlayRef?.overlayElement;
1912
+ if (!panel) {
1913
+ return;
1914
+ }
1915
+ const actual = this._resolvePosition(change.connectionPair);
1916
+ const allPositionClasses = [
1917
+ 'tn-tooltip-panel-above', 'tn-tooltip-panel-below',
1918
+ 'tn-tooltip-panel-left', 'tn-tooltip-panel-right',
1919
+ 'tn-tooltip-panel-before', 'tn-tooltip-panel-after',
1920
+ ];
1921
+ panel.classList.remove(...allPositionClasses);
1922
+ panel.classList.add(`tn-tooltip-panel-${actual}`);
1923
+ });
1924
+ }
1925
+ _resolvePosition(pair) {
1926
+ if (pair.overlayY === 'bottom') {
1927
+ return 'above';
1928
+ }
1929
+ if (pair.overlayY === 'top') {
1930
+ return 'below';
1931
+ }
1932
+ if (pair.overlayX === 'end') {
1933
+ return 'left';
1934
+ }
1935
+ return 'right';
1936
+ }
1937
+ _attachTooltip() {
1938
+ if (!this._overlayRef) {
1939
+ return;
1940
+ }
1941
+ if (!this._tooltipInstance) {
1942
+ const portal = new ComponentPortal(TnTooltipComponent, this._viewContainerRef);
1943
+ this._tooltipInstance = this._overlayRef.attach(portal);
1944
+ this._tooltipInstance.setInput('message', this.message());
1945
+ this._tooltipInstance.setInput('id', this._tooltipId);
1946
+ this._isTooltipVisible = true;
1947
+ }
1948
+ }
1949
+ _getPositions() {
1950
+ switch (this.position()) {
1951
+ case 'above':
1952
+ return [
1953
+ { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
1954
+ { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
1955
+ ];
1956
+ case 'below':
1957
+ return [
1958
+ { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
1959
+ { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
1960
+ ];
1961
+ case 'left':
1962
+ case 'before':
1963
+ return [
1964
+ { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
1965
+ { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
1966
+ ];
1967
+ case 'right':
1968
+ case 'after':
1969
+ return [
1970
+ { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
1971
+ { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
1972
+ ];
1973
+ default:
1974
+ return [
1975
+ { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
1976
+ ];
1977
+ }
1978
+ }
1979
+ _clearTimeouts() {
1980
+ if (this._showTimeout) {
1981
+ clearTimeout(this._showTimeout);
1982
+ this._showTimeout = null;
1983
+ }
1984
+ if (this._hideTimeout) {
1985
+ clearTimeout(this._hideTimeout);
1986
+ this._hideTimeout = null;
1987
+ }
1988
+ }
1989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1990
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: TnTooltipDirective, isStandalone: true, selector: "[tnTooltip]", inputs: { message: { classPropertyName: "message", publicName: "tnTooltip", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "tnTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "tnTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, showDelay: { classPropertyName: "showDelay", publicName: "tnTooltipShowDelay", isSignal: true, isRequired: false, transformFunction: null }, hideDelay: { classPropertyName: "hideDelay", publicName: "tnTooltipHideDelay", isSignal: true, isRequired: false, transformFunction: null }, tooltipClass: { classPropertyName: "tooltipClass", publicName: "tnTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "_onMouseEnter()", "mouseleave": "_onMouseLeave()", "focus": "_onFocus()", "blur": "_onBlur()", "keydown": "_onKeydown($event)" }, properties: { "attr.aria-describedby": "_ariaDescribedBy" } }, ngImport: i0 });
1991
+ }
1992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, decorators: [{
1993
+ type: Directive,
1994
+ args: [{
1995
+ selector: '[tnTooltip]',
1996
+ standalone: true,
1997
+ host: {
1998
+ '[attr.aria-describedby]': '_ariaDescribedBy',
1999
+ }
2000
+ }]
2001
+ }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltip", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipPosition", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipDisabled", required: false }] }], showDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipShowDelay", required: false }] }], hideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipHideDelay", required: false }] }], tooltipClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipClass", required: false }] }], _onMouseEnter: [{
2002
+ type: HostListener,
2003
+ args: ['mouseenter']
2004
+ }], _onMouseLeave: [{
2005
+ type: HostListener,
2006
+ args: ['mouseleave']
2007
+ }], _onFocus: [{
2008
+ type: HostListener,
2009
+ args: ['focus']
2010
+ }], _onBlur: [{
2011
+ type: HostListener,
2012
+ args: ['blur']
2013
+ }], _onKeydown: [{
2014
+ type: HostListener,
2015
+ args: ['keydown', ['$event']]
2016
+ }] } });
2017
+
1788
2018
  class TnIconButtonComponent {
1789
2019
  // Button-related inputs
1790
2020
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2021
+ /** Compact variant with reduced padding, for dense contexts like toolbars. */
2022
+ dense = input(false, ...(ngDevMode ? [{ debugName: "dense" }] : []));
1791
2023
  ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
2024
+ /** Reflects an expanded/collapsed state (e.g. toggling a panel) onto the inner button. */
2025
+ ariaExpanded = input(undefined, ...(ngDevMode ? [{ debugName: "ariaExpanded" }] : []));
1792
2026
  /**
1793
2027
  * Test-id applied to the rendered `<button>` element. Rendered under whichever attribute
1794
2028
  * name is configured via `TN_TEST_ATTR` (default `data-testid`).
@@ -1799,7 +2033,11 @@ class TnIconButtonComponent {
1799
2033
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1800
2034
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : []));
1801
2035
  tooltip = input(undefined, ...(ngDevMode ? [{ debugName: "tooltip" }] : []));
2036
+ /** Position of the styled tooltip relative to the button. */
2037
+ tooltipPosition = input('above', ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : []));
1802
2038
  library = input(undefined, ...(ngDevMode ? [{ debugName: "library" }] : []));
2039
+ /** Extra class(es) applied to the inner icon, e.g. for animations or state colors. */
2040
+ iconClass = input('', ...(ngDevMode ? [{ debugName: "iconClass" }] : []));
1803
2041
  onClick = output();
1804
2042
  hostRef = inject((ElementRef));
1805
2043
  buttonRef = viewChild.required('button');
@@ -1808,6 +2046,9 @@ class TnIconButtonComponent {
1808
2046
  if (this.color()) {
1809
2047
  result.push('tn-icon-button--custom-color');
1810
2048
  }
2049
+ if (this.dense()) {
2050
+ result.push('tn-icon-button--dense');
2051
+ }
1811
2052
  return result;
1812
2053
  }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
1813
2054
  effectiveAriaLabel = computed(() => {
@@ -1842,12 +2083,12 @@ class TnIconButtonComponent {
1842
2083
  host.focus = (options) => inner.focus(options);
1843
2084
  }
1844
2085
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1845
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnIconButtonComponent, isStandalone: true, selector: "tn-icon-button", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, 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 }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, viewQueries: [{ propertyName: "buttonRef", first: true, predicate: ["button"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
2086
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnIconButtonComponent, isStandalone: true, selector: "tn-icon-button", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaExpanded: { classPropertyName: "ariaExpanded", publicName: "ariaExpanded", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, 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 }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null }, iconClass: { classPropertyName: "iconClass", publicName: "iconClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, viewQueries: [{ propertyName: "buttonRef", first: true, predicate: ["button"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-expanded]=\"ariaExpanded()\"\n [tnTestId]=\"testId()\"\n [tnTooltip]=\"tooltip() || ''\"\n [tnTooltipPosition]=\"tooltipPosition()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ngClass]=\"iconClass()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n <ng-content />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{position:relative;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button--dense{padding:3px}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }, { kind: "directive", type: TnTooltipDirective, selector: "[tnTooltip]", inputs: ["tnTooltip", "tnTooltipPosition", "tnTooltipDisabled", "tnTooltipShowDelay", "tnTooltipHideDelay", "tnTooltipClass"] }] });
1846
2087
  }
1847
2088
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconButtonComponent, decorators: [{
1848
2089
  type: Component,
1849
- args: [{ selector: 'tn-icon-button', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective], template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"] }]
1850
- }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], 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 }] }], library: [{ type: i0.Input, args: [{ isSignal: true, alias: "library", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }], buttonRef: [{ type: i0.ViewChild, args: ['button', { isSignal: true }] }] } });
2090
+ args: [{ selector: 'tn-icon-button', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective, TnTooltipDirective], template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-expanded]=\"ariaExpanded()\"\n [tnTestId]=\"testId()\"\n [tnTooltip]=\"tooltip() || ''\"\n [tnTooltipPosition]=\"tooltipPosition()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ngClass]=\"iconClass()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n <ng-content />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{position:relative;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button--dense{padding:3px}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"] }]
2091
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dense: [{ type: i0.Input, args: [{ isSignal: true, alias: "dense", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaExpanded", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], 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 }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], library: [{ type: i0.Input, args: [{ isSignal: true, alias: "library", required: false }] }], iconClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconClass", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }], buttonRef: [{ type: i0.ViewChild, args: ['button', { isSignal: true }] }] } });
1851
2092
 
1852
2093
  /**
1853
2094
  * Harness for interacting with tn-icon-button in tests.
@@ -3245,7 +3486,7 @@ class TnCardComponent {
3245
3486
  return type ? `tn-card__status--${type}` : 'tn-card__status--neutral';
3246
3487
  }
3247
3488
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3248
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCardComponent, isStandalone: true, selector: "tn-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, titleLink: { classPropertyName: "titleLink", publicName: "titleLink", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, padContent: { classPropertyName: "padContent", publicName: "padContent", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, headerStatus: { classPropertyName: "headerStatus", publicName: "headerStatus", isSignal: true, isRequired: false, transformFunction: null }, headerControl: { classPropertyName: "headerControl", publicName: "headerControl", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, headerMenuTriggerTestId: { classPropertyName: "headerMenuTriggerTestId", publicName: "headerMenuTriggerTestId", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null }, footerLink: { classPropertyName: "footerLink", publicName: "footerLink", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedHeader", first: true, predicate: TnCardHeaderDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card:not(.tn-card--bordered) .tn-card__header{border-bottom-color:#0000001a}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card:not(.tn-card--bordered) .tn-card__footer{border-top-color:#0000001a}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "ariaLabel", "testId", "name", "size", "color", "tooltip", "library"], outputs: ["onClick"] }, { kind: "component", type: TnSlideToggleComponent, selector: "tn-slide-toggle", inputs: ["labelPosition", "label", "disabled", "required", "color", "testId", "ariaLabel", "ariaLabelledby", "checked"], outputs: ["change", "toggleChange"] }, { kind: "component", type: TnMenuComponent, selector: "tn-menu", inputs: ["items", "contextMenu"], outputs: ["menuItemClick", "menuOpen", "menuClose"] }, { kind: "directive", type: TnMenuTriggerDirective, selector: "[tnMenuTriggerFor]", inputs: ["tnMenuTriggerFor", "tnMenuPosition"], exportAs: ["tnMenuTrigger"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3489
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCardComponent, isStandalone: true, selector: "tn-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, titleLink: { classPropertyName: "titleLink", publicName: "titleLink", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, padContent: { classPropertyName: "padContent", publicName: "padContent", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, headerStatus: { classPropertyName: "headerStatus", publicName: "headerStatus", isSignal: true, isRequired: false, transformFunction: null }, headerControl: { classPropertyName: "headerControl", publicName: "headerControl", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, headerMenuTriggerTestId: { classPropertyName: "headerMenuTriggerTestId", publicName: "headerMenuTriggerTestId", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null }, footerLink: { classPropertyName: "footerLink", publicName: "footerLink", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedHeader", first: true, predicate: TnCardHeaderDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card:not(.tn-card--bordered) .tn-card__header{border-bottom-color:#0000001a}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card:not(.tn-card--bordered) .tn-card__footer{border-top-color:#0000001a}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSlideToggleComponent, selector: "tn-slide-toggle", inputs: ["labelPosition", "label", "disabled", "required", "color", "testId", "ariaLabel", "ariaLabelledby", "checked"], outputs: ["change", "toggleChange"] }, { kind: "component", type: TnMenuComponent, selector: "tn-menu", inputs: ["items", "contextMenu"], outputs: ["menuItemClick", "menuOpen", "menuClose"] }, { kind: "directive", type: TnMenuTriggerDirective, selector: "[tnMenuTriggerFor]", inputs: ["tnMenuTriggerFor", "tnMenuPosition"], exportAs: ["tnMenuTrigger"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3249
3490
  }
3250
3491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, decorators: [{
3251
3492
  type: Component,
@@ -7926,7 +8167,7 @@ class TnTablePagerComponent {
7926
8167
  provider.setPagination(pagination);
7927
8168
  }
7928
8169
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7929
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTablePagerComponent, isStandalone: true, selector: "tn-table-pager", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, dataProvider: { classPropertyName: "dataProvider", publicName: "dataProvider", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, tablePaginationLabel: { classPropertyName: "tablePaginationLabel", publicName: "tablePaginationLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "resolvedTablePaginationLabel()" }, classAttribute: "tn-table-pager" }, hostDirectives: [{ directive: TnTestIdDirective, inputs: ["tnTestId", "testId"] }], ngImport: i0, template: "<div class=\"tn-table-pager__page-size\">\n <span class=\"tn-table-pager__label\">{{ resolvedItemsPerPageLabel() }}:</span>\n <tn-select\n class=\"tn-table-pager__size-select\"\n [options]=\"pageSizeSelectOptions()\"\n [ariaLabel]=\"resolvedItemsPerPageLabel()\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\" />\n</div>\n\n<span class=\"tn-table-pager__range\">\n @if (effectiveTotalItems() === 0) {\n \u2013 {{ resolvedOfLabel() }} 0\n } @else if (lastItemOnPage() > firstItemOnPage()) {\n {{ firstItemOnPage() }} \u2013 {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n } @else {\n {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n }\n</span>\n\n<div class=\"tn-table-pager__buttons\">\n <tn-icon-button\n name=\"page-first\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedFirstPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"goToPage(1)\" />\n <tn-icon-button\n name=\"chevron-left\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedPreviousPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"previousPage()\" />\n <tn-icon-button\n name=\"chevron-right\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedNextPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"nextPage()\" />\n <tn-icon-button\n name=\"page-last\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedLastPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"goToPage(totalPages())\" />\n</div>\n", styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:16px;padding:10px;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);color:var(--tn-fg2)}.tn-table-pager__page-size{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.tn-table-pager__label{white-space:nowrap}.tn-table-pager__size-select{min-width:84px}.tn-table-pager__range{white-space:nowrap}.tn-table-pager__buttons{display:flex;align-items:center;gap:4px}@media(max-width:600px){:host{gap:8px}.tn-table-pager__page-size{gap:4px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "ariaLabel", "testId", "name", "size", "color", "tooltip", "library"], outputs: ["onClick"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "disabled", "testId", "multiple", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8170
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTablePagerComponent, isStandalone: true, selector: "tn-table-pager", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, dataProvider: { classPropertyName: "dataProvider", publicName: "dataProvider", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, tablePaginationLabel: { classPropertyName: "tablePaginationLabel", publicName: "tablePaginationLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "resolvedTablePaginationLabel()" }, classAttribute: "tn-table-pager" }, hostDirectives: [{ directive: TnTestIdDirective, inputs: ["tnTestId", "testId"] }], ngImport: i0, template: "<div class=\"tn-table-pager__page-size\">\n <span class=\"tn-table-pager__label\">{{ resolvedItemsPerPageLabel() }}:</span>\n <tn-select\n class=\"tn-table-pager__size-select\"\n [options]=\"pageSizeSelectOptions()\"\n [ariaLabel]=\"resolvedItemsPerPageLabel()\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\" />\n</div>\n\n<span class=\"tn-table-pager__range\">\n @if (effectiveTotalItems() === 0) {\n \u2013 {{ resolvedOfLabel() }} 0\n } @else if (lastItemOnPage() > firstItemOnPage()) {\n {{ firstItemOnPage() }} \u2013 {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n } @else {\n {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n }\n</span>\n\n<div class=\"tn-table-pager__buttons\">\n <tn-icon-button\n name=\"page-first\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedFirstPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"goToPage(1)\" />\n <tn-icon-button\n name=\"chevron-left\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedPreviousPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"previousPage()\" />\n <tn-icon-button\n name=\"chevron-right\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedNextPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"nextPage()\" />\n <tn-icon-button\n name=\"page-last\"\n library=\"mdi\"\n [ariaLabel]=\"resolvedLastPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"goToPage(totalPages())\" />\n</div>\n", styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:16px;padding:10px;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);color:var(--tn-fg2)}.tn-table-pager__page-size{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.tn-table-pager__label{white-space:nowrap}.tn-table-pager__size-select{min-width:84px}.tn-table-pager__range{white-space:nowrap}.tn-table-pager__buttons{display:flex;align-items:center;gap:4px}@media(max-width:600px){:host{gap:8px}.tn-table-pager__page-size{gap:4px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "disabled", "testId", "multiple", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7930
8171
  }
7931
8172
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, decorators: [{
7932
8173
  type: Component,
@@ -11625,232 +11866,30 @@ class TnButtonToggleGroupHarness extends ComponentHarness {
11625
11866
  }
11626
11867
  }
11627
11868
 
11628
- class TnTooltipComponent {
11629
- message = input('', ...(ngDevMode ? [{ debugName: "message" }] : []));
11630
- id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
11631
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11632
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnTooltipComponent, isStandalone: true, selector: "tn-tooltip", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-tooltip-component" }, ngImport: i0, template: "<div\n class=\"tn-tooltip\"\n role=\"tooltip\"\n [id]=\"id()\"\n [attr.aria-hidden]=\"false\">\n {{ message() }}\n</div>\n", styles: [":host{display:block;pointer-events:none;z-index:1200}.tn-tooltip{background:#373737e6;color:#fff;padding:6px 8px;border-radius:4px;font-size:12px;font-weight:500;line-height:1.4;max-width:200px;word-wrap:break-word;white-space:pre-line;box-shadow:0 2px 8px #0003;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:tn-tooltip-show .15s cubic-bezier(0,0,.2,1) forwards;transform-origin:center bottom}@keyframes tn-tooltip-show{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.tn-tooltip{position:relative}.tn-tooltip:after{content:\"\";position:absolute;width:0;height:0;border-style:solid;z-index:1}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{top:100%;left:50%;transform:translate(-50%);border-width:6px 6px 0 6px;border-color:rgba(55,55,55,.9) transparent transparent transparent}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{bottom:100%;left:50%;transform:translate(-50%);border-width:0 6px 6px 6px;border-color:transparent transparent rgba(55,55,55,.9) transparent}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{top:50%;left:100%;transform:translateY(-50%);border-width:6px 0 6px 6px;border-color:transparent transparent transparent rgba(55,55,55,.9)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{top:50%;right:100%;transform:translateY(-50%);border-width:6px 6px 6px 0;border-color:transparent rgba(55,55,55,.9) transparent transparent}@media(prefers-contrast:high){.tn-tooltip{background:var(--tn-fg1, #000);color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #999)}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{border-top-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{border-bottom-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{border-left-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{border-right-color:var(--tn-fg1, #000)}}:host-context(.tn-slider-thumb-label) .tn-tooltip{font-size:11px;padding:4px 6px;font-weight:600;min-width:24px;text-align:center;background:#373737f2}@media(prefers-reduced-motion:reduce){.tn-tooltip{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11633
- }
11634
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, decorators: [{
11635
- type: Component,
11636
- args: [{ selector: 'tn-tooltip', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
11637
- 'class': 'tn-tooltip-component'
11638
- }, template: "<div\n class=\"tn-tooltip\"\n role=\"tooltip\"\n [id]=\"id()\"\n [attr.aria-hidden]=\"false\">\n {{ message() }}\n</div>\n", styles: [":host{display:block;pointer-events:none;z-index:1200}.tn-tooltip{background:#373737e6;color:#fff;padding:6px 8px;border-radius:4px;font-size:12px;font-weight:500;line-height:1.4;max-width:200px;word-wrap:break-word;white-space:pre-line;box-shadow:0 2px 8px #0003;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:tn-tooltip-show .15s cubic-bezier(0,0,.2,1) forwards;transform-origin:center bottom}@keyframes tn-tooltip-show{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.tn-tooltip{position:relative}.tn-tooltip:after{content:\"\";position:absolute;width:0;height:0;border-style:solid;z-index:1}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{top:100%;left:50%;transform:translate(-50%);border-width:6px 6px 0 6px;border-color:rgba(55,55,55,.9) transparent transparent transparent}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{bottom:100%;left:50%;transform:translate(-50%);border-width:0 6px 6px 6px;border-color:transparent transparent rgba(55,55,55,.9) transparent}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{top:50%;left:100%;transform:translateY(-50%);border-width:6px 0 6px 6px;border-color:transparent transparent transparent rgba(55,55,55,.9)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{top:50%;right:100%;transform:translateY(-50%);border-width:6px 6px 6px 0;border-color:transparent rgba(55,55,55,.9) transparent transparent}@media(prefers-contrast:high){.tn-tooltip{background:var(--tn-fg1, #000);color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #999)}:host-context(.tn-tooltip-panel-above) .tn-tooltip:after{border-top-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-below) .tn-tooltip:after{border-bottom-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-left) .tn-tooltip:after,:host-context(.tn-tooltip-panel-before) .tn-tooltip:after{border-left-color:var(--tn-fg1, #000)}:host-context(.tn-tooltip-panel-right) .tn-tooltip:after,:host-context(.tn-tooltip-panel-after) .tn-tooltip:after{border-right-color:var(--tn-fg1, #000)}}:host-context(.tn-slider-thumb-label) .tn-tooltip{font-size:11px;padding:4px 6px;font-weight:600;min-width:24px;text-align:center;background:#373737f2}@media(prefers-reduced-motion:reduce){.tn-tooltip{animation:none}}\n"] }]
11639
- }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
11640
-
11641
- /* eslint-disable @angular-eslint/no-input-rename */
11642
- // Input aliasing is intentional for directive API consistency (e.g., ixTooltip, ixTooltipPosition)
11643
- // This follows the standard Angular pattern used by Material and other directive-based components
11644
- class TnTooltipDirective {
11645
- message = input('', { ...(ngDevMode ? { debugName: "message" } : {}), alias: 'tnTooltip' });
11646
- position = input('above', { ...(ngDevMode ? { debugName: "position" } : {}), alias: 'tnTooltipPosition' });
11647
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'tnTooltipDisabled' });
11648
- showDelay = input(0, { ...(ngDevMode ? { debugName: "showDelay" } : {}), alias: 'tnTooltipShowDelay' });
11649
- hideDelay = input(0, { ...(ngDevMode ? { debugName: "hideDelay" } : {}), alias: 'tnTooltipHideDelay' });
11650
- tooltipClass = input('', { ...(ngDevMode ? { debugName: "tooltipClass" } : {}), alias: 'tnTooltipClass' });
11651
- _overlayRef = null;
11652
- _tooltipInstance = null;
11653
- _showTimeout = null;
11654
- _hideTimeout = null;
11655
- _isTooltipVisible = false;
11656
- _positionSub = null;
11657
- _ariaDescribedBy = null;
11658
- _overlay = inject(Overlay);
11659
- _elementRef = inject((ElementRef));
11660
- _viewContainerRef = inject(ViewContainerRef);
11661
- _overlayPositionBuilder = inject(OverlayPositionBuilder);
11662
- ngOnInit() {
11663
- // Generate unique ID for aria-describedby
11664
- this._ariaDescribedBy = `tn-tooltip-${Math.random().toString(36).substr(2, 9)}`;
11665
- }
11666
- ngOnDestroy() {
11667
- this._clearTimeouts();
11668
- this.hide(0);
11669
- this._positionSub?.unsubscribe();
11670
- if (this._overlayRef) {
11671
- this._overlayRef.dispose();
11672
- this._overlayRef = null;
11673
- }
11674
- }
11675
- _onMouseEnter() {
11676
- if (!this.disabled() && this.message()) {
11677
- this.show(this.showDelay());
11678
- }
11679
- }
11680
- _onMouseLeave() {
11681
- this.hide(this.hideDelay());
11682
- }
11683
- _onFocus() {
11684
- if (!this.disabled() && this.message()) {
11685
- this.show(this.showDelay());
11686
- }
11687
- }
11688
- _onBlur() {
11689
- this.hide(this.hideDelay());
11690
- }
11691
- _onKeydown(event) {
11692
- if (event.key === 'Escape' && this._isTooltipVisible) {
11693
- this.hide(0);
11694
- }
11695
- }
11696
- /** Shows the tooltip */
11697
- show(delay = 0) {
11698
- if (this.disabled() || !this.message() || this._isTooltipVisible) {
11699
- return;
11700
- }
11701
- this._clearTimeouts();
11702
- this._showTimeout = setTimeout(() => {
11703
- if (!this._overlayRef) {
11704
- this._createOverlay();
11705
- }
11706
- this._attachTooltip();
11707
- }, delay);
11708
- }
11709
- /** Hides the tooltip */
11710
- hide(delay = 0) {
11711
- this._clearTimeouts();
11712
- this._hideTimeout = setTimeout(() => {
11713
- if (this._tooltipInstance) {
11714
- this._tooltipInstance.destroy();
11715
- this._tooltipInstance = null;
11716
- this._isTooltipVisible = false;
11717
- }
11718
- }, delay);
11719
- }
11720
- /** Toggle the tooltip visibility */
11721
- toggle() {
11722
- this._isTooltipVisible ? this.hide() : this.show();
11723
- }
11724
- _createOverlay() {
11725
- const positions = this._getPositions();
11726
- const positionStrategy = this._overlayPositionBuilder
11727
- .flexibleConnectedTo(this._elementRef)
11728
- .withPositions(positions)
11729
- .withFlexibleDimensions(false)
11730
- .withViewportMargin(8)
11731
- .withScrollableContainers([]);
11732
- this._overlayRef = this._overlay.create({
11733
- positionStrategy,
11734
- scrollStrategy: this._overlay.scrollStrategies.reposition({ scrollThrottle: 20 }),
11735
- panelClass: ['tn-tooltip-panel', `tn-tooltip-panel-${this.position()}`, this.tooltipClass()].filter(Boolean),
11736
- });
11737
- this._positionSub = positionStrategy.positionChanges
11738
- .subscribe((change) => {
11739
- const panel = this._overlayRef?.overlayElement?.parentElement;
11740
- if (!panel) {
11741
- return;
11742
- }
11743
- const actual = this._resolvePosition(change.connectionPair);
11744
- const allPositionClasses = [
11745
- 'tn-tooltip-panel-above', 'tn-tooltip-panel-below',
11746
- 'tn-tooltip-panel-left', 'tn-tooltip-panel-right',
11747
- 'tn-tooltip-panel-before', 'tn-tooltip-panel-after',
11748
- ];
11749
- panel.classList.remove(...allPositionClasses);
11750
- panel.classList.add(`tn-tooltip-panel-${actual}`);
11751
- });
11752
- }
11753
- _resolvePosition(pair) {
11754
- if (pair.overlayY === 'bottom') {
11755
- return 'above';
11756
- }
11757
- if (pair.overlayY === 'top') {
11758
- return 'below';
11759
- }
11760
- if (pair.overlayX === 'end') {
11761
- return 'left';
11762
- }
11763
- return 'right';
11764
- }
11765
- _attachTooltip() {
11766
- if (!this._overlayRef) {
11767
- return;
11768
- }
11769
- if (!this._tooltipInstance) {
11770
- const portal = new ComponentPortal(TnTooltipComponent, this._viewContainerRef);
11771
- this._tooltipInstance = this._overlayRef.attach(portal);
11772
- this._tooltipInstance.setInput('message', this.message());
11773
- this._tooltipInstance.setInput('id', this._ariaDescribedBy);
11774
- this._isTooltipVisible = true;
11775
- }
11776
- }
11777
- _getPositions() {
11778
- switch (this.position()) {
11779
- case 'above':
11780
- return [
11781
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
11782
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11783
- ];
11784
- case 'below':
11785
- return [
11786
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11787
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
11788
- ];
11789
- case 'left':
11790
- case 'before':
11791
- return [
11792
- { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
11793
- { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
11794
- ];
11795
- case 'right':
11796
- case 'after':
11797
- return [
11798
- { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
11799
- { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
11800
- ];
11801
- default:
11802
- return [
11803
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11804
- ];
11805
- }
11806
- }
11807
- _clearTimeouts() {
11808
- if (this._showTimeout) {
11809
- clearTimeout(this._showTimeout);
11810
- this._showTimeout = null;
11811
- }
11812
- if (this._hideTimeout) {
11813
- clearTimeout(this._hideTimeout);
11814
- this._hideTimeout = null;
11815
- }
11816
- }
11817
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
11818
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: TnTooltipDirective, isStandalone: true, selector: "[tnTooltip]", inputs: { message: { classPropertyName: "message", publicName: "tnTooltip", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "tnTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "tnTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, showDelay: { classPropertyName: "showDelay", publicName: "tnTooltipShowDelay", isSignal: true, isRequired: false, transformFunction: null }, hideDelay: { classPropertyName: "hideDelay", publicName: "tnTooltipHideDelay", isSignal: true, isRequired: false, transformFunction: null }, tooltipClass: { classPropertyName: "tooltipClass", publicName: "tnTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "_onMouseEnter()", "mouseleave": "_onMouseLeave()", "focus": "_onFocus()", "blur": "_onBlur()", "keydown": "_onKeydown($event)" }, properties: { "attr.aria-describedby": "_ariaDescribedBy" } }, ngImport: i0 });
11819
- }
11820
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, decorators: [{
11821
- type: Directive,
11822
- args: [{
11823
- selector: '[tnTooltip]',
11824
- standalone: true,
11825
- host: {
11826
- '[attr.aria-describedby]': '_ariaDescribedBy',
11827
- }
11828
- }]
11829
- }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltip", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipPosition", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipDisabled", required: false }] }], showDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipShowDelay", required: false }] }], hideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipHideDelay", required: false }] }], tooltipClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTooltipClass", required: false }] }], _onMouseEnter: [{
11830
- type: HostListener,
11831
- args: ['mouseenter']
11832
- }], _onMouseLeave: [{
11833
- type: HostListener,
11834
- args: ['mouseleave']
11835
- }], _onFocus: [{
11836
- type: HostListener,
11837
- args: ['focus']
11838
- }], _onBlur: [{
11839
- type: HostListener,
11840
- args: ['blur']
11841
- }], _onKeydown: [{
11842
- type: HostListener,
11843
- args: ['keydown', ['$event']]
11844
- }] } });
11845
-
11869
+ let nextUniqueId = 0;
11846
11870
  class TnDialogShellComponent {
11847
11871
  title = input('', ...(ngDevMode ? [{ debugName: "title" }] : []));
11848
11872
  showFullscreenButton = input(false, ...(ngDevMode ? [{ debugName: "showFullscreenButton" }] : []));
11873
+ /** Stable id for the title heading, referenced by the dialog's aria-labelledby. */
11874
+ titleId = `tn-dialog-title-${nextUniqueId++}`;
11849
11875
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
11850
11876
  originalStyles = {};
11851
11877
  ref = inject(DialogRef);
11852
11878
  document = inject(DOCUMENT);
11879
+ host = inject(ElementRef);
11853
11880
  data = inject(DIALOG_DATA, { optional: true });
11881
+ constructor() {
11882
+ // Give the CDK dialog container an accessible name by pointing its
11883
+ // aria-labelledby at the visible title heading. Tracked in an effect so a
11884
+ // title set after init is still reflected.
11885
+ effect(() => {
11886
+ if (!this.title()) {
11887
+ return;
11888
+ }
11889
+ const container = this.host.nativeElement.closest('cdk-dialog-container');
11890
+ container?.setAttribute('aria-labelledby', this.titleId);
11891
+ });
11892
+ }
11854
11893
  ngOnInit() {
11855
11894
  // Check if dialog was opened in fullscreen mode by looking for existing fullscreen class
11856
11895
  setTimeout(() => {
@@ -11908,14 +11947,14 @@ class TnDialogShellComponent {
11908
11947
  }
11909
11948
  }
11910
11949
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11911
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnDialogShellComponent, isStandalone: true, selector: "tn-dialog-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showFullscreenButton: { classPropertyName: "showFullscreenButton", publicName: "showFullscreenButton", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-dialog-shell" }, ngImport: i0, template: "<header class=\"tn-dialog__header\">\n <h2 class=\"tn-dialog__title\">{{ title() }}</h2>\n @if (showFullscreenButton()) {\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tabindex=\"-1\"\n [attr.aria-label]=\"isFullscreen() ? 'Exit fullscreen' : 'Enter fullscreen'\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n <button type=\"button\" class=\"tn-dialog__close\" tabindex=\"-1\" aria-label=\"Close dialog\" (click)=\"close()\">\u2715</button>\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent>\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions>\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" });
11950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnDialogShellComponent, isStandalone: true, selector: "tn-dialog-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showFullscreenButton: { classPropertyName: "showFullscreenButton", publicName: "showFullscreenButton", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-dialog-shell" }, ngImport: i0, template: "<header class=\"tn-dialog__header\">\n <h2 class=\"tn-dialog__title\" [id]=\"titleId\">{{ title() }}</h2>\n @if (showFullscreenButton()) {\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n [attr.aria-label]=\"isFullscreen() ? 'Exit fullscreen' : 'Enter fullscreen'\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n <button type=\"button\" class=\"tn-dialog__close\" aria-label=\"Close dialog\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent>\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions>\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" });
11912
11951
  }
11913
11952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, decorators: [{
11914
11953
  type: Component,
11915
11954
  args: [{ selector: 'tn-dialog-shell', standalone: true, imports: [], host: {
11916
11955
  'class': 'tn-dialog-shell'
11917
- }, template: "<header class=\"tn-dialog__header\">\n <h2 class=\"tn-dialog__title\">{{ title() }}</h2>\n @if (showFullscreenButton()) {\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tabindex=\"-1\"\n [attr.aria-label]=\"isFullscreen() ? 'Exit fullscreen' : 'Enter fullscreen'\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n <button type=\"button\" class=\"tn-dialog__close\" tabindex=\"-1\" aria-label=\"Close dialog\" (click)=\"close()\">\u2715</button>\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent>\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions>\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" }]
11918
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showFullscreenButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFullscreenButton", required: false }] }] } });
11956
+ }, template: "<header class=\"tn-dialog__header\">\n <h2 class=\"tn-dialog__title\" [id]=\"titleId\">{{ title() }}</h2>\n @if (showFullscreenButton()) {\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n [attr.aria-label]=\"isFullscreen() ? 'Exit fullscreen' : 'Enter fullscreen'\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n <button type=\"button\" class=\"tn-dialog__close\" aria-label=\"Close dialog\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent>\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions>\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" }]
11957
+ }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showFullscreenButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFullscreenButton", required: false }] }] } });
11919
11958
 
11920
11959
  class TnConfirmDialogComponent {
11921
11960
  ref = inject((DialogRef));
@@ -12096,6 +12135,26 @@ class TnDialogHarness extends ComponentHarness {
12096
12135
  const closeBtn = await this._closeButton();
12097
12136
  await closeBtn.click();
12098
12137
  }
12138
+ /**
12139
+ * Gets the `tabindex` attribute of the close button, or null if unset.
12140
+ * A null/non-negative value means the button is reachable via the Tab key.
12141
+ *
12142
+ * @returns Promise resolving to the tabindex attribute value.
12143
+ */
12144
+ async getCloseButtonTabIndex() {
12145
+ const closeBtn = await this._closeButton();
12146
+ return closeBtn.getAttribute('tabindex');
12147
+ }
12148
+ /**
12149
+ * Gets the `tabindex` attribute of the fullscreen button, or null if unset
12150
+ * or if the dialog has no fullscreen button.
12151
+ *
12152
+ * @returns Promise resolving to the tabindex attribute value.
12153
+ */
12154
+ async getFullscreenButtonTabIndex() {
12155
+ const btn = await this._fullscreenButton();
12156
+ return btn ? btn.getAttribute('tabindex') : null;
12157
+ }
12099
12158
  /**
12100
12159
  * Clicks an action button in the dialog footer by its label.
12101
12160
  * Only matches buttons inside the `tnDialogAction` footer area, not buttons in content.
@@ -12346,7 +12405,7 @@ class TnSidePanelComponent {
12346
12405
  });
12347
12406
  }
12348
12407
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12349
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSidePanelComponent, isStandalone: true, selector: "tn-side-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonTestId: { classPropertyName: "closeButtonTestId", publicName: "closeButtonTestId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { properties: { "attr.data-tn-panel": "panelId" }, classAttribute: "tn-side-panel" }, queries: [{ propertyName: "actionContent", predicate: TnSidePanelActionDirective, isSignal: true }], viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlay"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Overlay wrapper: portaled to document.body -->\n<div\n #overlay\n class=\"tn-side-panel__overlay\"\n role=\"dialog\"\n [attr.data-tn-panel]=\"panelId\"\n [tnTestId]=\"testId()\"\n [class.tn-side-panel__overlay--initialized]=\"initialized()\"\n [class.tn-side-panel__overlay--open]=\"open()\"\n [attr.aria-modal]=\"open() ? 'true' : null\"\n [attr.aria-labelledby]=\"open() ? titleId : null\"\n [attr.aria-hidden]=\"!open() ? 'true' : null\">\n\n <!-- Backdrop -->\n @if (hasBackdrop()) {\n <div\n class=\"tn-side-panel__backdrop\"\n aria-hidden=\"true\"\n (click)=\"onBackdropClick()\">\n </div>\n }\n\n <!-- Panel -->\n <div\n class=\"tn-side-panel__panel\"\n tabindex=\"-1\"\n cdkTrapFocus\n [style.width]=\"width()\"\n [cdkTrapFocusAutoCapture]=\"open()\"\n (transitionend)=\"onTransitionEnd($event)\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Header -->\n <header class=\"tn-side-panel__header\">\n <h2 class=\"tn-side-panel__title\" [id]=\"titleId\">{{ title() }}</h2>\n <div class=\"tn-side-panel__header-actions\">\n <ng-content select=\"[tnSidePanelHeaderAction]\" />\n <tn-icon-button\n name=\"close\"\n library=\"mdi\"\n size=\"sm\"\n ariaLabel=\"Dismiss\"\n [testId]=\"closeButtonTestId()\"\n (onClick)=\"dismiss()\" />\n </div>\n </header>\n\n <!-- Content -->\n <section class=\"tn-side-panel__content\">\n <ng-content />\n </section>\n\n <!-- Actions -->\n @if (hasActions()) {\n <footer class=\"tn-side-panel__actions\">\n <ng-content select=\"[tnSidePanelAction]\" />\n </footer>\n }\n </div>\n</div>\n", styles: [":host{display:contents}.tn-side-panel__overlay{position:fixed;inset:0;z-index:1000;pointer-events:none}.tn-side-panel__overlay--open{pointer-events:auto}.tn-side-panel__overlay--open .tn-side-panel__backdrop{opacity:1}.tn-side-panel__overlay--open .tn-side-panel__panel{transform:translate(0)}.tn-side-panel__backdrop{position:absolute;inset:0;background:#00000080;opacity:0}.tn-side-panel__overlay--initialized .tn-side-panel__backdrop{transition:opacity .2s ease}.tn-side-panel__panel{position:absolute;top:0;right:0;bottom:0;display:flex;flex-direction:column;max-width:100vw;background:var(--tn-bg2, #282828);color:var(--tn-fg1, #ffffff);box-shadow:-4px 0 24px #0000004d;outline:none;transform:translate(100%)}.tn-side-panel__overlay--initialized .tn-side-panel__panel{transition:transform .3s cubic-bezier(.4,0,.2,1)}.tn-side-panel__header{flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:16px 24px;border-bottom:1px solid var(--tn-lines, #383838)}.tn-side-panel__title{margin:0;font-size:1.25rem;font-weight:600;line-height:1.5;color:var(--tn-fg1, #ffffff);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-side-panel__header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-side-panel__content{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--tn-content-padding, 24px);-webkit-overflow-scrolling:touch}.tn-side-panel__actions{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--tn-lines, #383838)}@media(max-width:640px){.tn-side-panel__panel{width:100vw!important}}@media(prefers-reduced-motion:reduce){.tn-side-panel__panel,.tn-side-panel__backdrop{transition-duration:0ms!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "ariaLabel", "testId", "name", "size", "color", "tooltip", "library"], outputs: ["onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
12408
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSidePanelComponent, isStandalone: true, selector: "tn-side-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonTestId: { classPropertyName: "closeButtonTestId", publicName: "closeButtonTestId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { properties: { "attr.data-tn-panel": "panelId" }, classAttribute: "tn-side-panel" }, queries: [{ propertyName: "actionContent", predicate: TnSidePanelActionDirective, isSignal: true }], viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlay"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Overlay wrapper: portaled to document.body -->\n<div\n #overlay\n class=\"tn-side-panel__overlay\"\n role=\"dialog\"\n [attr.data-tn-panel]=\"panelId\"\n [tnTestId]=\"testId()\"\n [class.tn-side-panel__overlay--initialized]=\"initialized()\"\n [class.tn-side-panel__overlay--open]=\"open()\"\n [attr.aria-modal]=\"open() ? 'true' : null\"\n [attr.aria-labelledby]=\"open() ? titleId : null\"\n [attr.aria-hidden]=\"!open() ? 'true' : null\">\n\n <!-- Backdrop -->\n @if (hasBackdrop()) {\n <div\n class=\"tn-side-panel__backdrop\"\n aria-hidden=\"true\"\n (click)=\"onBackdropClick()\">\n </div>\n }\n\n <!-- Panel -->\n <div\n class=\"tn-side-panel__panel\"\n tabindex=\"-1\"\n cdkTrapFocus\n [style.width]=\"width()\"\n [cdkTrapFocusAutoCapture]=\"open()\"\n (transitionend)=\"onTransitionEnd($event)\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Header -->\n <header class=\"tn-side-panel__header\">\n <h2 class=\"tn-side-panel__title\" [id]=\"titleId\">{{ title() }}</h2>\n <div class=\"tn-side-panel__header-actions\">\n <ng-content select=\"[tnSidePanelHeaderAction]\" />\n <tn-icon-button\n name=\"close\"\n library=\"mdi\"\n size=\"sm\"\n ariaLabel=\"Dismiss\"\n [testId]=\"closeButtonTestId()\"\n (onClick)=\"dismiss()\" />\n </div>\n </header>\n\n <!-- Content -->\n <section class=\"tn-side-panel__content\">\n <ng-content />\n </section>\n\n <!-- Actions -->\n @if (hasActions()) {\n <footer class=\"tn-side-panel__actions\">\n <ng-content select=\"[tnSidePanelAction]\" />\n </footer>\n }\n </div>\n</div>\n", styles: [":host{display:contents}.tn-side-panel__overlay{position:fixed;inset:0;z-index:1000;pointer-events:none}.tn-side-panel__overlay--open{pointer-events:auto}.tn-side-panel__overlay--open .tn-side-panel__backdrop{opacity:1}.tn-side-panel__overlay--open .tn-side-panel__panel{transform:translate(0)}.tn-side-panel__backdrop{position:absolute;inset:0;background:#00000080;opacity:0}.tn-side-panel__overlay--initialized .tn-side-panel__backdrop{transition:opacity .2s ease}.tn-side-panel__panel{position:absolute;top:0;right:0;bottom:0;display:flex;flex-direction:column;max-width:100vw;background:var(--tn-bg2, #282828);color:var(--tn-fg1, #ffffff);box-shadow:-4px 0 24px #0000004d;outline:none;transform:translate(100%)}.tn-side-panel__overlay--initialized .tn-side-panel__panel{transition:transform .3s cubic-bezier(.4,0,.2,1)}.tn-side-panel__header{flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:16px 24px;border-bottom:1px solid var(--tn-lines, #383838)}.tn-side-panel__title{margin:0;font-size:1.25rem;font-weight:600;line-height:1.5;color:var(--tn-fg1, #ffffff);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-side-panel__header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-side-panel__content{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--tn-content-padding, 24px);-webkit-overflow-scrolling:touch}.tn-side-panel__actions{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--tn-lines, #383838)}@media(max-width:640px){.tn-side-panel__panel{width:100vw!important}}@media(prefers-reduced-motion:reduce){.tn-side-panel__panel,.tn-side-panel__backdrop{transition-duration:0ms!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
12350
12409
  }
12351
12410
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, decorators: [{
12352
12411
  type: Component,
@@ -13509,16 +13568,19 @@ var TnToastPosition;
13509
13568
  TnToastPosition["Bottom"] = "bottom";
13510
13569
  })(TnToastPosition || (TnToastPosition = {}));
13511
13570
 
13512
- // Mark icons for sprite inclusion (dynamic names aren't detected by the scanner)
13571
+ // Material icons render via the `material-icons` CSS font (see icon.component.ts),
13572
+ // so they don't need sprite scanning — the literal `mat-` prefix matches what
13573
+ // the runtime icon resolver would produce from a Material library name.
13513
13574
  const TOAST_ICONS = {
13514
- [TnToastType.Info]: tnIconMarker('info', 'material'),
13515
- [TnToastType.Success]: tnIconMarker('check_circle', 'material'),
13516
- [TnToastType.Warning]: tnIconMarker('warning', 'material'),
13517
- [TnToastType.Error]: tnIconMarker('error', 'material'),
13575
+ [TnToastType.Info]: 'mat-info',
13576
+ [TnToastType.Success]: 'mat-check_circle',
13577
+ [TnToastType.Warning]: 'mat-warning',
13578
+ [TnToastType.Error]: 'mat-error',
13518
13579
  };
13519
13580
  class TnToastComponent {
13520
13581
  message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : []));
13521
13582
  action = signal(null, ...(ngDevMode ? [{ debugName: "action" }] : []));
13583
+ actionTestId = signal(undefined, ...(ngDevMode ? [{ debugName: "actionTestId" }] : []));
13522
13584
  type = signal(TnToastType.Info, ...(ngDevMode ? [{ debugName: "type" }] : []));
13523
13585
  position = signal(TnToastPosition.Top, ...(ngDevMode ? [{ debugName: "position" }] : []));
13524
13586
  visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
@@ -13526,14 +13588,14 @@ class TnToastComponent {
13526
13588
  onAction = () => { };
13527
13589
  onDismiss = () => { };
13528
13590
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13529
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnToastComponent, isStandalone: true, selector: "tn-toast", host: { properties: { "class.tn-toast--top": "position() === \"top\"", "class.tn-toast--bottom": "position() === \"bottom\"" } }, ngImport: i0, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
13591
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnToastComponent, isStandalone: true, selector: "tn-toast", host: { properties: { "class.tn-toast--top": "position() === \"top\"", "class.tn-toast--bottom": "position() === \"bottom\"" } }, ngImport: i0, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n [tnTestId]=\"actionTestId()\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
13530
13592
  }
13531
13593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, decorators: [{
13532
13594
  type: Component,
13533
- args: [{ selector: 'tn-toast', standalone: true, imports: [TnIconComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
13595
+ args: [{ selector: 'tn-toast', standalone: true, imports: [TnIconComponent, TnTestIdDirective], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
13534
13596
  '[class.tn-toast--top]': 'position() === "top"',
13535
13597
  '[class.tn-toast--bottom]': 'position() === "bottom"',
13536
- }, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"] }]
13598
+ }, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n [tnTestId]=\"actionTestId()\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"] }]
13537
13599
  }] });
13538
13600
 
13539
13601
  class TnToastRef {