@truenas/ui-components 0.1.59 → 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,8 +1,8 @@
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
- import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
5
+ import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
6
6
  import * as i1 from '@angular/cdk/a11y';
7
7
  import { A11yModule, FocusMonitor } from '@angular/cdk/a11y';
8
8
  import * as i1$2 from '@angular/common';
@@ -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';
@@ -289,7 +289,7 @@ class TnAutocompleteComponent {
289
289
  useExisting: forwardRef(() => TnAutocompleteComponent),
290
290
  multi: true,
291
291
  },
292
- ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-autocomplete\" [tnTestId]=\"testId()\">\n <input\n #inputEl\n type=\"text\"\n class=\"tn-autocomplete__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class.open]=\"isOpen()\"\n [class.disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [value]=\"searchTerm()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"isOpen() ? uid + '-dropdown' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\" />\n\n @if (isOpen()) {\n <div\n class=\"tn-autocomplete__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-dropdown'\">\n\n @if (hasResults()) {\n @for (option of filteredOptions(); track $index) {\n <div\n class=\"tn-autocomplete__option\"\n role=\"option\"\n tabindex=\"-1\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\">\n {{ displayWith()(option) }}\n </div>\n }\n } @else {\n <div class=\"tn-autocomplete__no-results\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-autocomplete{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-autocomplete__input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;color:var(--tn-fg1, #212529);font-size:.875rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-autocomplete__input::placeholder{color:var(--tn-alt-fg1, #999)}.tn-autocomplete__input:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-autocomplete__input:focus-visible,.tn-autocomplete__input.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-autocomplete__input.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-autocomplete__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow-y:auto;padding:.25rem 0}.tn-autocomplete__option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out}.tn-autocomplete__option:hover{background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-autocomplete__option.highlighted{background-color:var(--tn-alt-bg1, #f8f9fa)}.tn-autocomplete__no-results{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}@media(prefers-reduced-motion:reduce){.tn-autocomplete__input,.tn-autocomplete__option{transition:none}}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
292
+ ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-autocomplete\" [tnTestId]=\"testId()\">\n <input\n #inputEl\n type=\"text\"\n class=\"tn-autocomplete__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class.open]=\"isOpen()\"\n [class.disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [value]=\"searchTerm()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"isOpen() ? uid + '-dropdown' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\" />\n\n @if (isOpen()) {\n <div\n class=\"tn-autocomplete__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-dropdown'\">\n\n @if (hasResults()) {\n @for (option of filteredOptions(); track $index) {\n <div\n class=\"tn-autocomplete__option\"\n role=\"option\"\n tabindex=\"-1\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\">\n {{ displayWith()(option) }}\n </div>\n }\n } @else {\n <div class=\"tn-autocomplete__no-results\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-autocomplete{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-autocomplete__input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;color:var(--tn-fg1, #212529);font-size:1rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-autocomplete__input::placeholder{color:var(--tn-alt-fg1, #999)}.tn-autocomplete__input:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-autocomplete__input:focus-visible,.tn-autocomplete__input.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-autocomplete__input.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-autocomplete__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow-y:auto;padding:.25rem 0}.tn-autocomplete__option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out}.tn-autocomplete__option:hover{background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-autocomplete__option.highlighted{background-color:var(--tn-alt-bg1, #f8f9fa)}.tn-autocomplete__no-results{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}@media(prefers-reduced-motion:reduce){.tn-autocomplete__input,.tn-autocomplete__option{transition:none}}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
293
293
  }
294
294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnAutocompleteComponent, decorators: [{
295
295
  type: Component,
@@ -299,7 +299,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
299
299
  useExisting: forwardRef(() => TnAutocompleteComponent),
300
300
  multi: true,
301
301
  },
302
- ], template: "<div class=\"tn-autocomplete\" [tnTestId]=\"testId()\">\n <input\n #inputEl\n type=\"text\"\n class=\"tn-autocomplete__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class.open]=\"isOpen()\"\n [class.disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [value]=\"searchTerm()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"isOpen() ? uid + '-dropdown' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\" />\n\n @if (isOpen()) {\n <div\n class=\"tn-autocomplete__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-dropdown'\">\n\n @if (hasResults()) {\n @for (option of filteredOptions(); track $index) {\n <div\n class=\"tn-autocomplete__option\"\n role=\"option\"\n tabindex=\"-1\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\">\n {{ displayWith()(option) }}\n </div>\n }\n } @else {\n <div class=\"tn-autocomplete__no-results\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-autocomplete{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-autocomplete__input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;color:var(--tn-fg1, #212529);font-size:.875rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-autocomplete__input::placeholder{color:var(--tn-alt-fg1, #999)}.tn-autocomplete__input:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-autocomplete__input:focus-visible,.tn-autocomplete__input.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-autocomplete__input.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-autocomplete__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow-y:auto;padding:.25rem 0}.tn-autocomplete__option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out}.tn-autocomplete__option:hover{background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-autocomplete__option.highlighted{background-color:var(--tn-alt-bg1, #f8f9fa)}.tn-autocomplete__no-results{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}@media(prefers-reduced-motion:reduce){.tn-autocomplete__input,.tn-autocomplete__option{transition:none}}\n"] }]
302
+ ], template: "<div class=\"tn-autocomplete\" [tnTestId]=\"testId()\">\n <input\n #inputEl\n type=\"text\"\n class=\"tn-autocomplete__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class.open]=\"isOpen()\"\n [class.disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [value]=\"searchTerm()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"isOpen() ? uid + '-dropdown' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\" />\n\n @if (isOpen()) {\n <div\n class=\"tn-autocomplete__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-dropdown'\">\n\n @if (hasResults()) {\n @for (option of filteredOptions(); track $index) {\n <div\n class=\"tn-autocomplete__option\"\n role=\"option\"\n tabindex=\"-1\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\">\n {{ displayWith()(option) }}\n </div>\n }\n } @else {\n <div class=\"tn-autocomplete__no-results\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-autocomplete{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-autocomplete__input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;color:var(--tn-fg1, #212529);font-size:1rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-autocomplete__input::placeholder{color:var(--tn-alt-fg1, #999)}.tn-autocomplete__input:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-autocomplete__input:focus-visible,.tn-autocomplete__input.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-autocomplete__input.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-autocomplete__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow-y:auto;padding:.25rem 0}.tn-autocomplete__option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out}.tn-autocomplete__option:hover{background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-autocomplete__option.highlighted{background-color:var(--tn-alt-bg1, #f8f9fa)}.tn-autocomplete__no-results{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}@media(prefers-reduced-motion:reduce){.tn-autocomplete__input,.tn-autocomplete__option{transition:none}}\n"] }]
303
303
  }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], requireSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "requireSelection", required: false }] }], filterFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFn", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], maxResults: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxResults", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], optionSelected: [{ type: i0.Output, args: ["optionSelected"] }], inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
304
304
 
305
305
  /**
@@ -1486,11 +1486,11 @@ class TnBannerComponent {
1486
1486
  return result;
1487
1487
  }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
1488
1488
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1489
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\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"] }] });
1489
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\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"] }] });
1490
1490
  }
1491
1491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, decorators: [{
1492
1492
  type: Component,
1493
- args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"] }]
1493
+ args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"] }]
1494
1494
  }], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }] } });
1495
1495
 
1496
1496
  /**
@@ -1632,13 +1632,43 @@ class TnButtonComponent {
1632
1632
  }
1633
1633
  this.onClick.emit(event);
1634
1634
  }
1635
+ hostRef = inject((ElementRef));
1636
+ // The template renders exactly one of `<a [routerLink]>`, `<a [href]>`, or
1637
+ // `<button>` via `@if/@else`, and each carries the `#button` ref — so this
1638
+ // resolves to whichever variant is active. Using a viewChild instead of a
1639
+ // `:scope > button, :scope > a` querySelector keeps the wiring resilient if
1640
+ // the template ever wraps the inner element in an extra container.
1641
+ innerRef = viewChild.required('button');
1642
+ ngAfterViewInit() {
1643
+ // The wrapped <button>/<a> is natively focusable. If a consumer also places
1644
+ // the <tn-button> host into the tab order (commonly `tabindex="0"` for
1645
+ // card-style focus management), both elements become tab stops — the user
1646
+ // perceives a "double focus" on the same logical button.
1647
+ //
1648
+ // Forward any tabindex set on the host to the inner element and clear it
1649
+ // from the host, so the button is a single tab stop with the focus ring
1650
+ // landing on the styled inner element (the host has no visual styling).
1651
+ // Also delegate `host.focus()` to the inner element so callers holding a
1652
+ // ref to the host (FocusMonitor, MatMenuTrigger restore, etc.) focus
1653
+ // something visible — same pattern used in TnIconButtonComponent.
1654
+ const host = this.hostRef.nativeElement;
1655
+ const inner = this.innerRef().nativeElement;
1656
+ if (host.hasAttribute('tabindex')) {
1657
+ const ti = host.getAttribute('tabindex');
1658
+ if (ti !== null) {
1659
+ inner.setAttribute('tabindex', ti);
1660
+ }
1661
+ host.removeAttribute('tabindex');
1662
+ }
1663
+ host.focus = (options) => inner.focus(options);
1664
+ }
1635
1665
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1636
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnButtonComponent, isStandalone: true, selector: "tn-button", inputs: { primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, queryParams: { classPropertyName: "queryParams", publicName: "queryParams", isSignal: true, isRequired: false, transformFunction: null }, fragment: { classPropertyName: "fragment", publicName: "fragment", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, ngImport: i0, template: "@if (isRouterLink()) {\n <a\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [routerLink]=\"disabled() ? null : routerLink()\"\n [queryParams]=\"queryParams()\"\n [fragment]=\"fragment()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else if (isAnchor()) {\n <a\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [attr.href]=\"disabled() ? null : href()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else {\n <button\n type=\"button\"\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n >\n {{ label() }}\n </button>\n}\n", styles: [":host{display:inline-block;width:fit-content;justify-self:center}.storybook-button{display:inline-block;cursor:pointer;border:0;font-weight:500;line-height:1;font-family:IBM Plex Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.storybook-button:disabled,.storybook-button.is-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}a.storybook-button{text-decoration:none;text-align:center}.button-primary{background-color:var(--tn-primary);color:var(--tn-primary-txt)}.button-default{box-shadow:#00000026 0 0 0 1px inset;background-color:var(--tn-btn-default-bg);color:var(--tn-btn-default-txt)}.button-outline-primary{background-color:transparent;border:1px solid var(--tn-primary);color:var(--tn-primary);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-primary:hover{background-color:var(--tn-primary);border:1px solid var(--tn-primary);color:var(--tn-primary-txt)}.button-outline-default{background-color:transparent;border:1px solid var(--tn-lines, #e5e7eb);color:var(--tn-fg1, #000000);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-default:hover{background-color:var(--tn-btn-default-bg);border:1px solid var(--tn-btn-default-bg);color:var(--tn-btn-default-txt);box-shadow:#00000026 0 0 0 1px inset}.button-warn{background-color:var(--tn-red);color:#fff}.button-outline-warn{background-color:transparent;border:1px solid var(--tn-red);color:var(--tn-red);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-warn:hover{background-color:var(--tn-red);border:1px solid var(--tn-red);color:#fff}.storybook-button--small{padding:10px 16px;font-size:12px}.storybook-button--medium{padding:11px 20px;font-size:14px}.storybook-button--large{padding:12px 24px;font-size:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
1666
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnButtonComponent, isStandalone: true, selector: "tn-button", inputs: { primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, queryParams: { classPropertyName: "queryParams", publicName: "queryParams", isSignal: true, isRequired: false, transformFunction: null }, fragment: { classPropertyName: "fragment", publicName: "fragment", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, viewQueries: [{ propertyName: "innerRef", first: true, predicate: ["button"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isRouterLink()) {\n <a\n #button\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [routerLink]=\"disabled() ? null : routerLink()\"\n [queryParams]=\"queryParams()\"\n [fragment]=\"fragment()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else if (isAnchor()) {\n <a\n #button\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [attr.href]=\"disabled() ? null : href()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else {\n <button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n >\n {{ label() }}\n </button>\n}\n", styles: [":host{display:inline-block;width:fit-content;justify-self:center}.storybook-button{display:inline-block;cursor:pointer;border:0;font-weight:500;line-height:1;font-family:IBM Plex Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.storybook-button:disabled,.storybook-button.is-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}a.storybook-button{text-decoration:none;text-align:center}.button-primary{background-color:var(--tn-primary);color:var(--tn-primary-txt)}.button-default{box-shadow:#00000026 0 0 0 1px inset;background-color:var(--tn-btn-default-bg);color:var(--tn-btn-default-txt)}.button-outline-primary{background-color:transparent;border:1px solid var(--tn-primary);color:var(--tn-primary);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-primary:hover{background-color:var(--tn-primary);border:1px solid var(--tn-primary);color:var(--tn-primary-txt)}.button-outline-default{background-color:transparent;border:1px solid var(--tn-lines, #e5e7eb);color:var(--tn-fg1, #000000);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-default:hover{background-color:var(--tn-btn-default-bg);border:1px solid var(--tn-btn-default-bg);color:var(--tn-btn-default-txt);box-shadow:#00000026 0 0 0 1px inset}.button-warn{background-color:var(--tn-red);color:#fff}.button-outline-warn{background-color:transparent;border:1px solid var(--tn-red);color:var(--tn-red);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-warn:hover{background-color:var(--tn-red);border:1px solid var(--tn-red);color:#fff}.storybook-button--small{padding:10px 16px;font-size:12px}.storybook-button--medium{padding:11px 20px;font-size:14px}.storybook-button--large{padding:12px 24px;font-size:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
1637
1667
  }
1638
1668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnButtonComponent, decorators: [{
1639
1669
  type: Component,
1640
- args: [{ selector: 'tn-button', standalone: true, imports: [CommonModule, RouterLink, TnTestIdDirective], template: "@if (isRouterLink()) {\n <a\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [routerLink]=\"disabled() ? null : routerLink()\"\n [queryParams]=\"queryParams()\"\n [fragment]=\"fragment()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else if (isAnchor()) {\n <a\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [attr.href]=\"disabled() ? null : href()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else {\n <button\n type=\"button\"\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n >\n {{ label() }}\n </button>\n}\n", styles: [":host{display:inline-block;width:fit-content;justify-self:center}.storybook-button{display:inline-block;cursor:pointer;border:0;font-weight:500;line-height:1;font-family:IBM Plex Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.storybook-button:disabled,.storybook-button.is-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}a.storybook-button{text-decoration:none;text-align:center}.button-primary{background-color:var(--tn-primary);color:var(--tn-primary-txt)}.button-default{box-shadow:#00000026 0 0 0 1px inset;background-color:var(--tn-btn-default-bg);color:var(--tn-btn-default-txt)}.button-outline-primary{background-color:transparent;border:1px solid var(--tn-primary);color:var(--tn-primary);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-primary:hover{background-color:var(--tn-primary);border:1px solid var(--tn-primary);color:var(--tn-primary-txt)}.button-outline-default{background-color:transparent;border:1px solid var(--tn-lines, #e5e7eb);color:var(--tn-fg1, #000000);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-default:hover{background-color:var(--tn-btn-default-bg);border:1px solid var(--tn-btn-default-bg);color:var(--tn-btn-default-txt);box-shadow:#00000026 0 0 0 1px inset}.button-warn{background-color:var(--tn-red);color:#fff}.button-outline-warn{background-color:transparent;border:1px solid var(--tn-red);color:var(--tn-red);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-warn:hover{background-color:var(--tn-red);border:1px solid var(--tn-red);color:#fff}.storybook-button--small{padding:10px 16px;font-size:12px}.storybook-button--medium{padding:11px 20px;font-size:14px}.storybook-button--large{padding:12px 24px;font-size:16px}\n"] }]
1641
- }], propDecorators: { primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], queryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParams", required: false }] }], fragment: [{ type: i0.Input, args: [{ isSignal: true, alias: "fragment", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], rel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }] } });
1670
+ args: [{ selector: 'tn-button', standalone: true, imports: [CommonModule, RouterLink, TnTestIdDirective], template: "@if (isRouterLink()) {\n <a\n #button\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [routerLink]=\"disabled() ? null : routerLink()\"\n [queryParams]=\"queryParams()\"\n [fragment]=\"fragment()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else if (isAnchor()) {\n <a\n #button\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [attr.href]=\"disabled() ? null : href()\"\n [target]=\"target()\"\n [rel]=\"rel()\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n [class.is-disabled]=\"disabled()\"\n [tnTestId]=\"testId()\"\n (click)=\"handleAnchorClick($event)\"\n >\n {{ label() }}\n </a>\n} @else {\n <button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n >\n {{ label() }}\n </button>\n}\n", styles: [":host{display:inline-block;width:fit-content;justify-self:center}.storybook-button{display:inline-block;cursor:pointer;border:0;font-weight:500;line-height:1;font-family:IBM Plex Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.storybook-button:disabled,.storybook-button.is-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}a.storybook-button{text-decoration:none;text-align:center}.button-primary{background-color:var(--tn-primary);color:var(--tn-primary-txt)}.button-default{box-shadow:#00000026 0 0 0 1px inset;background-color:var(--tn-btn-default-bg);color:var(--tn-btn-default-txt)}.button-outline-primary{background-color:transparent;border:1px solid var(--tn-primary);color:var(--tn-primary);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-primary:hover{background-color:var(--tn-primary);border:1px solid var(--tn-primary);color:var(--tn-primary-txt)}.button-outline-default{background-color:transparent;border:1px solid var(--tn-lines, #e5e7eb);color:var(--tn-fg1, #000000);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-default:hover{background-color:var(--tn-btn-default-bg);border:1px solid var(--tn-btn-default-bg);color:var(--tn-btn-default-txt);box-shadow:#00000026 0 0 0 1px inset}.button-warn{background-color:var(--tn-red);color:#fff}.button-outline-warn{background-color:transparent;border:1px solid var(--tn-red);color:var(--tn-red);transition:background-color .2s ease-in-out,border-color .2s ease-in-out,color .2s ease-in-out}.button-outline-warn:hover{background-color:var(--tn-red);border:1px solid var(--tn-red);color:#fff}.storybook-button--small{padding:10px 16px;font-size:12px}.storybook-button--medium{padding:11px 20px;font-size:14px}.storybook-button--large{padding:12px 24px;font-size:1rem}\n"] }]
1671
+ }], propDecorators: { primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], queryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParams", required: false }] }], fragment: [{ type: i0.Input, args: [{ isSignal: true, alias: "fragment", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], rel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }], innerRef: [{ type: i0.ViewChild, args: ['button', { isSignal: true }] }] } });
1642
1672
 
1643
1673
  /**
1644
1674
  * Harness for interacting with tn-button in tests.
@@ -1755,10 +1785,244 @@ class TnButtonHarness extends ComponentHarness {
1755
1785
  }
1756
1786
  }
1757
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
+
1758
2018
  class TnIconButtonComponent {
1759
2019
  // Button-related inputs
1760
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" }] : []));
1761
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" }] : []));
1762
2026
  /**
1763
2027
  * Test-id applied to the rendered `<button>` element. Rendered under whichever attribute
1764
2028
  * name is configured via `TN_TEST_ATTR` (default `data-testid`).
@@ -1769,25 +2033,62 @@ class TnIconButtonComponent {
1769
2033
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1770
2034
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : []));
1771
2035
  tooltip = input(undefined, ...(ngDevMode ? [{ debugName: "tooltip" }] : []));
2036
+ /** Position of the styled tooltip relative to the button. */
2037
+ tooltipPosition = input('above', ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : []));
1772
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" }] : []));
1773
2041
  onClick = output();
2042
+ hostRef = inject((ElementRef));
2043
+ buttonRef = viewChild.required('button');
1774
2044
  classes = computed(() => {
1775
2045
  const result = ['tn-icon-button'];
1776
2046
  if (this.color()) {
1777
2047
  result.push('tn-icon-button--custom-color');
1778
2048
  }
2049
+ if (this.dense()) {
2050
+ result.push('tn-icon-button--dense');
2051
+ }
1779
2052
  return result;
1780
2053
  }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
1781
2054
  effectiveAriaLabel = computed(() => {
1782
2055
  return this.ariaLabel() || this.name() || 'Icon button';
1783
2056
  }, ...(ngDevMode ? [{ debugName: "effectiveAriaLabel" }] : []));
2057
+ /**
2058
+ * Focuses the inner native `<button>`. Exposed as a public method so callers
2059
+ * with a `TnIconButtonComponent` reference (e.g. `@ViewChild`) can focus it
2060
+ * without reaching into the DOM themselves.
2061
+ */
2062
+ focus(options) {
2063
+ this.buttonRef().nativeElement.focus(options);
2064
+ }
2065
+ ngAfterViewInit() {
2066
+ // Make `host.focus()` delegate to the inner `<button>`. Triggers that take
2067
+ // an element ref and call `.focus()` on it (MatMenuTrigger restores focus
2068
+ // this way; CDK A11y FocusMonitor also uses raw `.focus()`) target the
2069
+ // host custom element, which isn't focusable on its own — so without this
2070
+ // override the focus call silently no-ops and users see the focus ring
2071
+ // disappear after a menu closes. Forwarding to the inner button keeps the
2072
+ // public DOM API behaving like a native button.
2073
+ //
2074
+ // Intentionally per-instance, not on the prototype: each host owns its own
2075
+ // inner button ref. We don't restore the original `focus` on destroy
2076
+ // because the host element is destroyed at the same time.
2077
+ //
2078
+ // If this component is ever moved to `ViewEncapsulation.ShadowDom`, drop
2079
+ // this override and rely on the shadow root's `delegatesFocus: true`
2080
+ // option, which is the platform-native equivalent.
2081
+ const host = this.hostRef.nativeElement;
2082
+ const inner = this.buttonRef().nativeElement;
2083
+ host.focus = (options) => inner.focus(options);
2084
+ }
1784
2085
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1785
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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" }, ngImport: i0, template: "<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"] }] });
1786
2087
  }
1787
2088
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconButtonComponent, decorators: [{
1788
2089
  type: Component,
1789
- args: [{ selector: 'tn-icon-button', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective], template: "<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"] }]
1790
- }], 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"] }] } });
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 }] }] } });
1791
2092
 
1792
2093
  /**
1793
2094
  * Harness for interacting with tn-icon-button in tests.
@@ -2533,6 +2834,13 @@ class TnMenuTriggerDirective {
2533
2834
  overlayRef;
2534
2835
  isMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMenuOpen" }] : []));
2535
2836
  itemClickSub;
2837
+ /**
2838
+ * RxJS subscriptions for the current overlay's `backdropClick()` and
2839
+ * `keydownEvents()`. Each `openMenu()` creates a fresh overlay (and fresh
2840
+ * subscriptions), so we collect them here and tear them down in `closeMenu`
2841
+ * / `ngOnDestroy` to keep open→close cycles leak-free.
2842
+ */
2843
+ overlaySubs = [];
2536
2844
  elementRef = inject(ElementRef);
2537
2845
  overlay = inject(Overlay);
2538
2846
  viewContainerRef = inject(ViewContainerRef);
@@ -2544,6 +2852,12 @@ class TnMenuTriggerDirective {
2544
2852
  this.openMenu();
2545
2853
  }
2546
2854
  }
2855
+ onArrowDown(event) {
2856
+ if (!this.isMenuOpen()) {
2857
+ event.preventDefault();
2858
+ this.openMenu();
2859
+ }
2860
+ }
2547
2861
  openMenu() {
2548
2862
  const menuComponent = this.menu();
2549
2863
  if (!menuComponent || this.isMenuOpen()) {
@@ -2572,26 +2886,91 @@ class TnMenuTriggerDirective {
2572
2886
  const portal = new TemplatePortal(menuTemplate, this.viewContainerRef);
2573
2887
  this.overlayRef.attach(portal);
2574
2888
  this.isMenuOpen.set(true);
2575
- // Handle backdrop click
2576
- this.overlayRef.backdropClick().subscribe(() => {
2889
+ // Handle backdrop click. Track the subscription so a future close/open
2890
+ // cycle (or component destroy) tears it down explicitly instead of relying
2891
+ // on the overlay's GC.
2892
+ this.overlaySubs.push(this.overlayRef.backdropClick().subscribe(() => {
2577
2893
  this.closeMenu();
2578
- });
2894
+ }));
2895
+ // Escape and Tab both close the menu. CdkMenu has handlers for both, but
2896
+ // they only tell the menu stack to close — our overlay lifecycle isn't
2897
+ // wired to that stack, so we listen on the overlay's keydown events
2898
+ // directly.
2899
+ //
2900
+ // - Escape: preventDefault + close + restore focus (so the user lands
2901
+ // somewhere sensible after dismissing).
2902
+ // - Tab / Shift+Tab: close + restore focus to the trigger, but do NOT
2903
+ // preventDefault — the browser's default Tab action then advances focus
2904
+ // from the trigger to the next/previous focusable element on the page,
2905
+ // which is what the user wanted by pressing Tab.
2906
+ this.overlaySubs.push(this.overlayRef.keydownEvents().subscribe((event) => {
2907
+ const hasMod = event.altKey || event.ctrlKey || event.metaKey;
2908
+ if (event.key === 'Escape' && !hasMod) {
2909
+ event.preventDefault();
2910
+ this.closeMenu();
2911
+ }
2912
+ else if (event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey) {
2913
+ this.closeMenu();
2914
+ }
2915
+ }));
2579
2916
  // Close menu when a leaf item is selected
2580
2917
  this.itemClickSub = menuComponent.menuItemClick.subscribe(() => {
2581
2918
  this.closeMenu();
2582
2919
  });
2583
- // Notify menu component
2920
+ // Notify menu component. TnMenuActivateHoverDirective (applied inside the
2921
+ // overlay template) will focus the first enabled item via
2922
+ // CdkMenu.focusFirstItem() once the embedded view is fully attached, so
2923
+ // there is no need to reach into the overlay from here.
2584
2924
  menuComponent.onMenuOpen();
2585
2925
  }
2586
2926
  closeMenu() {
2587
2927
  this.itemClickSub?.unsubscribe();
2588
2928
  this.itemClickSub = undefined;
2929
+ this.disposeOverlaySubs();
2589
2930
  if (this.overlayRef) {
2590
2931
  this.overlayRef.dispose();
2591
2932
  this.overlayRef = undefined;
2592
2933
  this.isMenuOpen.set(false);
2593
2934
  this.menu().onMenuClose();
2935
+ this.restoreFocusToTrigger();
2936
+ }
2937
+ }
2938
+ ngOnDestroy() {
2939
+ // If the host is destroyed while the menu is open, closeMenu() never runs.
2940
+ // Dispose directly without trying to restore focus or notify the menu — the
2941
+ // surrounding view is being torn down anyway.
2942
+ this.itemClickSub?.unsubscribe();
2943
+ this.itemClickSub = undefined;
2944
+ this.disposeOverlaySubs();
2945
+ this.overlayRef?.dispose();
2946
+ this.overlayRef = undefined;
2947
+ }
2948
+ disposeOverlaySubs() {
2949
+ for (const sub of this.overlaySubs) {
2950
+ sub.unsubscribe();
2594
2951
  }
2952
+ this.overlaySubs.length = 0;
2953
+ }
2954
+ /**
2955
+ * Return focus to the trigger element so keyboard users land somewhere
2956
+ * sensible after the menu closes (Escape, item click, or backdrop click).
2957
+ *
2958
+ * The host might be a custom-element wrapper like `<tn-button>` /
2959
+ * `<tn-icon-button>` whose host element isn't itself focusable — focus
2960
+ * lives on the inner `<button>` or `<a>`. We focus the host if it's
2961
+ * focusable; otherwise we drill into the first focusable descendant.
2962
+ *
2963
+ * Passes `focusVisible: true` (Chrome/Firefox) so the `:focus-visible`
2964
+ * outline reappears on the restored trigger — without it, browsers treat a
2965
+ * programmatic `.focus()` as non-keyboard and skip the focus ring, which
2966
+ * looks to users like focus has been lost. Safari ignores the option and
2967
+ * falls back to its heuristic; that's acceptable.
2968
+ */
2969
+ restoreFocusToTrigger() {
2970
+ const host = this.elementRef.nativeElement;
2971
+ const FOCUSABLE = 'button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
2972
+ const target = host.matches(FOCUSABLE) ? host : host.querySelector(FOCUSABLE);
2973
+ target?.focus({ preventScroll: true, focusVisible: true });
2595
2974
  }
2596
2975
  getPositions() {
2597
2976
  switch (this.tnMenuPosition()) {
@@ -2618,7 +2997,7 @@ class TnMenuTriggerDirective {
2618
2997
  }
2619
2998
  }
2620
2999
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2621
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: TnMenuTriggerDirective, isStandalone: true, selector: "[tnMenuTriggerFor]", inputs: { menu: { classPropertyName: "menu", publicName: "tnMenuTriggerFor", isSignal: true, isRequired: true, transformFunction: null }, tnMenuPosition: { classPropertyName: "tnMenuPosition", publicName: "tnMenuPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()" } }, exportAs: ["tnMenuTrigger"], ngImport: i0 });
3000
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: TnMenuTriggerDirective, isStandalone: true, selector: "[tnMenuTriggerFor]", inputs: { menu: { classPropertyName: "menu", publicName: "tnMenuTriggerFor", isSignal: true, isRequired: true, transformFunction: null }, tnMenuPosition: { classPropertyName: "tnMenuPosition", publicName: "tnMenuPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown.arrowDown": "onArrowDown($event)" } }, exportAs: ["tnMenuTrigger"], ngImport: i0 });
2622
3001
  }
2623
3002
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuTriggerDirective, decorators: [{
2624
3003
  type: Directive,
@@ -2627,11 +3006,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2627
3006
  standalone: true,
2628
3007
  exportAs: 'tnMenuTrigger',
2629
3008
  host: {
2630
- '(click)': 'onClick()'
2631
- }
3009
+ '(click)': 'onClick()',
3010
+ '(keydown.arrowDown)': 'onArrowDown($event)',
3011
+ },
2632
3012
  }]
2633
3013
  }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuTriggerFor", required: true }] }], tnMenuPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuPosition", required: false }] }] } });
2634
3014
 
3015
+ /**
3016
+ * Renders a single `TnMenuItem` (separator, leaf button, or submenu trigger).
3017
+ *
3018
+ * Used recursively by `<tn-menu>` so the same authoring covers root items and
3019
+ * arbitrarily-nested submenus — adding a new menu-item property (badge,
3020
+ * tooltip, etc.) only needs to be wired up here, not duplicated across every
3021
+ * nesting level.
3022
+ *
3023
+ * **Why a component, not an ng-template:** earlier prototypes used a recursive
3024
+ * `<ng-template #menuItem>` outletted via `[ngTemplateOutlet]`. That fails
3025
+ * because the embedded view's DI chain resolves through the template's
3026
+ * declaration site, not the outlet anchor — `CdkMenuItem` then can't find
3027
+ * `MENU_STACK`, which is provided per-`CdkMenu`. A real component's DI walks
3028
+ * up through its host element instead, so each renderer instance sees the
3029
+ * `CdkMenu` it's actually rendered under (root or any nested submenu).
3030
+ *
3031
+ * @internal Implementation detail of `<tn-menu>`. Not re-exported from the
3032
+ * menu barrel or `public-api.ts` because it depends on injecting
3033
+ * `TnMenuComponent` from the host DI scope and cannot be used standalone.
3034
+ */
3035
+ class TnMenuItemRendererComponent {
3036
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
3037
+ // The renderer is rendered inside a CDK overlay portal, but the portal is
3038
+ // created from a template defined inside `<tn-menu>` — so the view-DI chain
3039
+ // still walks back to the host `<tn-menu>` component, which means inject()
3040
+ // here resolves it correctly.
3041
+ menu = inject(TnMenuComponent);
3042
+ onClick() {
3043
+ this.menu.onMenuItemClick(this.item());
3044
+ }
3045
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3046
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuItemRendererComponent, isStandalone: true, selector: "tn-menu-item-renderer", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (item().separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n} @else if (!item().children || item().children!.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n (click)=\"onClick()\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n </button>\n} @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (child of item().children!; track child.id) {\n <tn-menu-item-renderer [item]=\"child\" />\n }\n </div>\n </ng-template>\n}\n", dependencies: [{ kind: "component", type: i0.forwardRef(() => TnMenuItemRendererComponent), selector: "tn-menu-item-renderer", inputs: ["item"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => CdkMenu), selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuItem), selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuTrigger), selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: i0.forwardRef(() => TnIconComponent), selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: i0.forwardRef(() => TnTestIdDirective), selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3047
+ }
3048
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, decorators: [{
3049
+ type: Component,
3050
+ args: [{ selector: 'tn-menu-item-renderer', standalone: true, imports: [
3051
+ CommonModule,
3052
+ CdkMenu,
3053
+ CdkMenuItem,
3054
+ CdkMenuTrigger,
3055
+ TnIconComponent,
3056
+ TnTestIdDirective,
3057
+ forwardRef(() => TnMenuItemRendererComponent),
3058
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (item().separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n} @else if (!item().children || item().children!.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n (click)=\"onClick()\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n </button>\n} @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (child of item().children!; track child.id) {\n <tn-menu-item-renderer [item]=\"child\" />\n }\n </div>\n </ng-template>\n}\n" }]
3059
+ }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
3060
+
2635
3061
  /**
2636
3062
  * Projection-based menu item for use inside `<tn-menu>`.
2637
3063
  *
@@ -2643,17 +3069,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2643
3069
  *
2644
3070
  * Existing `<tn-menu [items]="...">` consumers don't need this component;
2645
3071
  * the items-array API continues to work unchanged. Items-array entries and
2646
- * projected `<tn-menu-item>` children render together inside one `<tn-menu>`.
2647
- *
2648
- * Subscribe to either the projected item's own `itemClick` output (preferred,
2649
- * per-item handlers) or the parent menu's `menuItemClick` (uniform handler).
2650
- * Trigger-driven menus close automatically on projected-item click.
2651
- *
2652
- * **Note on keyboard navigation:** projected items render as `role="menuitem"`
2653
- * buttons but do not participate in `CdkMenu` arrow-key navigation (CdkMenuItem
2654
- * requires its parent `CdkMenu` in the same injector tree, which projection
2655
- * breaks). For menus that depend on arrow-key navigation between options, use
2656
- * the `items` input form. Tab/Shift+Tab and Enter/Space activation still work.
3072
+ * projected `<tn-menu-item>` children render together inside one `<tn-menu>`,
3073
+ * share keyboard navigation, and look identical.
3074
+ *
3075
+ * **How it works:** the component itself renders nothing visible — it acts as
3076
+ * a configuration declaration. The parent `<tn-menu>` collects projected items
3077
+ * via `contentChildren`, re-renders them inside the CDK overlay alongside
3078
+ * items-array entries (with `cdkMenuItem` for full arrow-key navigation), and
3079
+ * routes clicks back to each item's `itemClick` output.
3080
+ *
3081
+ * **Accessibility:** because items are re-rendered inside the parent's
3082
+ * `CdkMenu`, all keyboard semantics from `@angular/cdk/menu` apply uniformly:
3083
+ * Arrow Up/Down/Home/End to move focus, Enter/Space to activate, Esc to
3084
+ * close, type-ahead search. Disabled items are skipped.
3085
+ *
3086
+ * **Limitation — custom content & keyboard nav:** the projected
3087
+ * `<ng-content>` (custom-content mode) is rendered inside a `<button
3088
+ * cdkMenuItem>` wrapper that owns Enter/Space/Arrow handling. Interactive
3089
+ * elements inside the projection (a nested `<button>`, link, toggle, etc.)
3090
+ * receive clicks but **do not** participate in CDK arrow-key navigation —
3091
+ * the wrapper button is the only keyboard-reachable target. Prefer
3092
+ * display-only content (icons, badges, two-line text); for cases that need
3093
+ * an extra interactive control, build a custom menu host with `cdkMenu`
3094
+ * directly instead of `<tn-menu>`.
2657
3095
  *
2658
3096
  * @example
2659
3097
  * ```html
@@ -2675,20 +3113,16 @@ class TnMenuItemComponent {
2675
3113
  selected = input(false, ...(ngDevMode ? [{ debugName: "selected" }] : []));
2676
3114
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
2677
3115
  itemClick = output();
3116
+ /** Template capturing whatever the consumer projected as item content. */
3117
+ content = viewChild.required('content');
2678
3118
  resolvedTestId = computed(() => this.testId() ?? (this.id() ? `menu-item-${this.id()}` : undefined), ...(ngDevMode ? [{ debugName: "resolvedTestId" }] : []));
2679
- handleClick(event) {
2680
- if (this.disabled()) {
2681
- return;
2682
- }
2683
- this.itemClick.emit(event);
2684
- }
2685
3119
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2686
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuItemComponent, isStandalone: true, selector: "tn-menu-item", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconLibrary: { classPropertyName: "iconLibrary", publicName: "iconLibrary", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, ngImport: i0, template: "<button\n class=\"tn-menu-item\"\n type=\"button\"\n role=\"menuitem\"\n [tnTestId]=\"resolvedTestId()\"\n [disabled]=\"disabled()\"\n [class.disabled]=\"disabled()\"\n [class.tn-menu-item--selected]=\"selected()\"\n [attr.aria-current]=\"selected() ? 'true' : null\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"handleClick($event)\"\n>\n @if (label() !== undefined) {\n @if (icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"icon()!\" [library]=\"iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ label() }}</span>\n @if (shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ shortcut() }}</span>\n }\n } @else {\n <ng-content />\n }\n</button>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3120
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnMenuItemComponent, isStandalone: true, selector: "tn-menu-item", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconLibrary: { classPropertyName: "iconLibrary", publicName: "iconLibrary", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { styleAttribute: "display: none;" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #content><ng-content /></ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2687
3121
  }
2688
3122
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemComponent, decorators: [{
2689
3123
  type: Component,
2690
- args: [{ selector: 'tn-menu-item', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective], template: "<button\n class=\"tn-menu-item\"\n type=\"button\"\n role=\"menuitem\"\n [tnTestId]=\"resolvedTestId()\"\n [disabled]=\"disabled()\"\n [class.disabled]=\"disabled()\"\n [class.tn-menu-item--selected]=\"selected()\"\n [attr.aria-current]=\"selected() ? 'true' : null\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"handleClick($event)\"\n>\n @if (label() !== undefined) {\n @if (icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"icon()!\" [library]=\"iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ label() }}</span>\n @if (shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ shortcut() }}</span>\n }\n } @else {\n <ng-content />\n }\n</button>\n" }]
2691
- }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconLibrary", required: false }] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
3124
+ args: [{ selector: 'tn-menu-item', standalone: true, imports: [CommonModule], host: { style: 'display: none;' }, template: "<ng-template #content><ng-content /></ng-template>\n" }]
3125
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconLibrary", required: false }] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
2692
3126
 
2693
3127
  /**
2694
3128
  * Activates CDK menu hover-to-open behavior for menus opened via custom overlays.
@@ -2704,12 +3138,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2704
3138
  */
2705
3139
  class TnMenuActivateHoverDirective {
2706
3140
  cdkMenu = inject(CdkMenu);
2707
- elementRef = inject(ElementRef);
3141
+ elementRef = inject((ElementRef));
2708
3142
  ngAfterContentInit() {
2709
3143
  const stack = this.cdkMenu.menuStack;
2710
3144
  if (stack.isEmpty()) {
2711
3145
  stack.push(this.cdkMenu);
2712
- this.elementRef.nativeElement.focus({ preventScroll: true });
3146
+ }
3147
+ const km = this.cdkMenu.keyManager;
3148
+ km?.skipPredicate?.((item) => item.disabled);
3149
+ // Prefer the marked "selected" item when one exists — matches user
3150
+ // expectation for option pickers (export format, sort key, etc.) where
3151
+ // reopening the menu should land focus on the current choice, not always
3152
+ // the first entry. Falls back to the first enabled item.
3153
+ const host = this.elementRef.nativeElement;
3154
+ const items = Array.from(host.querySelectorAll('[cdkMenuItem]'));
3155
+ const selectedIndex = items.findIndex((el) => el.classList.contains('tn-menu-item--selected') && !el.hasAttribute('disabled'));
3156
+ const selectedItem = selectedIndex >= 0 ? items[selectedIndex] : undefined;
3157
+ if (selectedItem && km?.setActiveItem) {
3158
+ km.setActiveItem(selectedIndex);
3159
+ selectedItem.focus();
3160
+ }
3161
+ else {
3162
+ // Set the active index to the first enabled item so the next ArrowDown
3163
+ // advances correctly (instead of being a no-op while the manager "catches
3164
+ // up" to where DOM focus already is).
3165
+ this.cdkMenu.focusFirstItem('keyboard');
2713
3166
  }
2714
3167
  }
2715
3168
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuActivateHoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -2731,6 +3184,7 @@ class TnMenuComponent {
2731
3184
  menuTemplate = viewChild.required('menuTemplate');
2732
3185
  contextMenuTemplate = viewChild.required('contextMenuTemplate');
2733
3186
  contextOverlayRef;
3187
+ contextBackdropSub;
2734
3188
  overlay = inject(Overlay);
2735
3189
  viewContainerRef = inject(ViewContainerRef);
2736
3190
  onMenuItemClick(item) {
@@ -2746,21 +3200,20 @@ class TnMenuComponent {
2746
3200
  }
2747
3201
  }
2748
3202
  contentItems = contentChildren(TnMenuItemComponent, ...(ngDevMode ? [{ debugName: "contentItems" }] : []));
2749
- constructor() {
2750
- // Forward projected `<tn-menu-item>` clicks to `menuItemClick` so trigger-
2751
- // driven menus close uniformly. The synthetic emission mirrors the input
2752
- // shape; consumers wanting per-item behavior should bind to the projected
2753
- // item's own `itemClick` output.
2754
- effect((onCleanup) => {
2755
- const items = this.contentItems();
2756
- const subs = items.map((item) => item.itemClick.subscribe(() => {
2757
- this.menuItemClick.emit({ id: item.id() ?? '', label: item.label() ?? '' });
2758
- if (this.contextOverlayRef) {
2759
- this.closeContextMenu();
2760
- }
2761
- }));
2762
- onCleanup(() => subs.forEach((s) => s.unsubscribe()));
2763
- });
3203
+ /**
3204
+ * Click handler for projected `<tn-menu-item>` entries. Emits the item's own
3205
+ * `itemClick` output, re-emits a synthetic entry on `menuItemClick` so
3206
+ * trigger-driven menus close uniformly, and closes any open context menu.
3207
+ */
3208
+ onProjectedItemClick(item, event) {
3209
+ if (item.disabled()) {
3210
+ return;
3211
+ }
3212
+ item.itemClick.emit(event);
3213
+ this.menuItemClick.emit({ id: item.id() ?? '', label: item.label() ?? '' });
3214
+ if (this.contextOverlayRef) {
3215
+ this.closeContextMenu();
3216
+ }
2764
3217
  }
2765
3218
  hasChildren = computed(() => (item) => {
2766
3219
  return !!(item.children && item.children.length > 0);
@@ -2803,20 +3256,30 @@ class TnMenuComponent {
2803
3256
  // Create portal and attach to overlay
2804
3257
  const portal = new TemplatePortal(contextMenuTemplate, this.viewContainerRef);
2805
3258
  this.contextOverlayRef.attach(portal);
2806
- // Handle backdrop click to close menu
2807
- this.contextOverlayRef.backdropClick().subscribe(() => {
3259
+ // Handle backdrop click to close menu — keep the subscription so we can
3260
+ // unsubscribe explicitly on close/destroy rather than leaving it dangling.
3261
+ this.contextBackdropSub = this.contextOverlayRef.backdropClick().subscribe(() => {
2808
3262
  this.closeContextMenu();
2809
3263
  });
2810
3264
  this.onMenuOpen();
2811
3265
  }
2812
3266
  }
2813
3267
  closeContextMenu() {
3268
+ this.contextBackdropSub?.unsubscribe();
3269
+ this.contextBackdropSub = undefined;
2814
3270
  if (this.contextOverlayRef) {
2815
3271
  this.contextOverlayRef.dispose();
2816
3272
  this.contextOverlayRef = undefined;
2817
3273
  this.onMenuClose();
2818
3274
  }
2819
3275
  }
3276
+ ngOnDestroy() {
3277
+ // Component destroyed while context menu open → clean up without notifying.
3278
+ this.contextBackdropSub?.unsubscribe();
3279
+ this.contextBackdropSub = undefined;
3280
+ this.contextOverlayRef?.dispose();
3281
+ this.contextOverlayRef = undefined;
3282
+ }
2820
3283
  onContextMenu(event) {
2821
3284
  if (this.contextMenu()) {
2822
3285
  event.preventDefault();
@@ -2829,12 +3292,12 @@ class TnMenuComponent {
2829
3292
  return item.id;
2830
3293
  }
2831
3294
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2832
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuComponent, isStandalone: true, selector: "tn-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemClick: "menuItemClick", menuOpen: "menuOpen", menuClose: "menuClose" }, queries: [{ propertyName: "contentItems", predicate: TnMenuItemComponent, isSignal: true }], viewQueries: [{ propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true, isSignal: true }, { propertyName: "contextMenuTemplate", first: true, predicate: ["contextMenuTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n <!-- Context menu template for overlay -->\n <ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"nestedMenu\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #nestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (nestedItem of item.children; track trackByItemId($index, nestedItem)) {\n @if (nestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!nestedItem.children || nestedItem.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(nestedItem)\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [cdkMenuTriggerFor]=\"deepNestedMenu\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #deepNestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (deepNestedItem of nestedItem.children; track trackByItemId($index, deepNestedItem)) {\n @if (deepNestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"deepNestedItem.testId ?? 'menu-item-' + deepNestedItem.id\"\n [disabled]=\"deepNestedItem.disabled\"\n [class.disabled]=\"deepNestedItem.disabled\"\n [class.tn-menu-item--selected]=\"deepNestedItem.selected\"\n [attr.aria-current]=\"deepNestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(deepNestedItem)\"\n >\n @if (deepNestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"deepNestedItem.icon\" [library]=\"deepNestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ deepNestedItem.label }}</span>\n @if (deepNestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ deepNestedItem.shortcut }}</span>\n }\n </button>\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n\n <!-- Regular menu template -->\n <ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!-- Projected <tn-menu-item> children render here, mixed with the items() array below. -->\n <ng-content select=\"tn-menu-item, .tn-menu-separator\" />\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"nestedMenu\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #nestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (nestedItem of item.children; track trackByItemId($index, nestedItem)) {\n @if (nestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!nestedItem.children || nestedItem.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(nestedItem)\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [cdkMenuTriggerFor]=\"deepNestedMenu\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #deepNestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (deepNestedItem of nestedItem.children; track trackByItemId($index, deepNestedItem)) {\n @if (deepNestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"deepNestedItem.testId ?? 'menu-item-' + deepNestedItem.id\"\n [disabled]=\"deepNestedItem.disabled\"\n [class.disabled]=\"deepNestedItem.disabled\"\n [class.tn-menu-item--selected]=\"deepNestedItem.selected\"\n [attr.aria-current]=\"deepNestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(deepNestedItem)\"\n >\n @if (deepNestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"deepNestedItem.icon\" [library]=\"deepNestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ deepNestedItem.label }}</span>\n @if (deepNestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ deepNestedItem.shortcut }}</span>\n }\n </button>\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:16px;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnMenuActivateHoverDirective, selector: "[tnMenuActivateHover]" }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3295
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuComponent, isStandalone: true, selector: "tn-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemClick: "menuItemClick", menuOpen: "menuOpen", menuClose: "menuClose" }, queries: [{ propertyName: "contentItems", predicate: TnMenuItemComponent, isSignal: true }], viewQueries: [{ propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true, isSignal: true }, { propertyName: "contextMenuTemplate", first: true, predicate: ["contextMenuTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!--\n Projected <tn-menu-item> entries are re-rendered here inside the\n overlay so they participate in CdkMenu's keyboard navigation (arrow\n keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of contentItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedItemClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnMenuActivateHoverDirective, selector: "[tnMenuActivateHover]" }, { kind: "component", type: TnMenuItemRendererComponent, selector: "tn-menu-item-renderer", inputs: ["item"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
2833
3296
  }
2834
3297
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuComponent, decorators: [{
2835
3298
  type: Component,
2836
- args: [{ selector: 'tn-menu', standalone: true, imports: [CommonModule, CdkMenu, CdkMenuItem, CdkMenuTrigger, TnIconComponent, TnMenuActivateHoverDirective, TnTestIdDirective], template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n <!-- Context menu template for overlay -->\n <ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"nestedMenu\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #nestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (nestedItem of item.children; track trackByItemId($index, nestedItem)) {\n @if (nestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!nestedItem.children || nestedItem.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(nestedItem)\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [cdkMenuTriggerFor]=\"deepNestedMenu\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #deepNestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (deepNestedItem of nestedItem.children; track trackByItemId($index, deepNestedItem)) {\n @if (deepNestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"deepNestedItem.testId ?? 'menu-item-' + deepNestedItem.id\"\n [disabled]=\"deepNestedItem.disabled\"\n [class.disabled]=\"deepNestedItem.disabled\"\n [class.tn-menu-item--selected]=\"deepNestedItem.selected\"\n [attr.aria-current]=\"deepNestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(deepNestedItem)\"\n >\n @if (deepNestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"deepNestedItem.icon\" [library]=\"deepNestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ deepNestedItem.label }}</span>\n @if (deepNestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ deepNestedItem.shortcut }}</span>\n }\n </button>\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n\n <!-- Regular menu template -->\n <ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!-- Projected <tn-menu-item> children render here, mixed with the items() array below. -->\n <ng-content select=\"tn-menu-item, .tn-menu-separator\" />\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"nestedMenu\"\n [disabled]=\"item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #nestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (nestedItem of item.children; track trackByItemId($index, nestedItem)) {\n @if (nestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n @if (!nestedItem.children || nestedItem.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(nestedItem)\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"nestedItem.testId ?? 'menu-item-' + nestedItem.id\"\n [cdkMenuTriggerFor]=\"deepNestedMenu\"\n [disabled]=\"nestedItem.disabled\"\n [class.disabled]=\"nestedItem.disabled\"\n [class.tn-menu-item--selected]=\"nestedItem.selected\"\n [attr.aria-current]=\"nestedItem.selected ? 'true' : null\"\n >\n @if (nestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"nestedItem.icon\" [library]=\"nestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ nestedItem.label }}</span>\n @if (nestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ nestedItem.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #deepNestedMenu>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (deepNestedItem of nestedItem.children; track trackByItemId($index, deepNestedItem)) {\n @if (deepNestedItem.separator) {\n <div\n class=\"tn-menu-separator\"\n role=\"separator\"\n ></div>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"deepNestedItem.testId ?? 'menu-item-' + deepNestedItem.id\"\n [disabled]=\"deepNestedItem.disabled\"\n [class.disabled]=\"deepNestedItem.disabled\"\n [class.tn-menu-item--selected]=\"deepNestedItem.selected\"\n [attr.aria-current]=\"deepNestedItem.selected ? 'true' : null\"\n (click)=\"onMenuItemClick(deepNestedItem)\"\n >\n @if (deepNestedItem.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"deepNestedItem.icon\" [library]=\"deepNestedItem.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ deepNestedItem.label }}</span>\n @if (deepNestedItem.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ deepNestedItem.shortcut }}</span>\n }\n </button>\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>\n }\n }\n }\n </div>\n </ng-template>", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:16px;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
2837
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], menuItemClick: [{ type: i0.Output, args: ["menuItemClick"] }], menuOpen: [{ type: i0.Output, args: ["menuOpen"] }], menuClose: [{ type: i0.Output, args: ["menuClose"] }], menuTemplate: [{ type: i0.ViewChild, args: ['menuTemplate', { isSignal: true }] }], contextMenuTemplate: [{ type: i0.ViewChild, args: ['contextMenuTemplate', { isSignal: true }] }], contentItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnMenuItemComponent), { isSignal: true }] }] } });
3299
+ args: [{ selector: 'tn-menu', standalone: true, imports: [CommonModule, CdkMenu, CdkMenuItem, CdkMenuTrigger, TnIconComponent, TnMenuActivateHoverDirective, TnMenuItemRendererComponent, TnTestIdDirective], template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!--\n Projected <tn-menu-item> entries are re-rendered here inside the\n overlay so they participate in CdkMenu's keyboard navigation (arrow\n keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of contentItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedItemClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
3300
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], menuItemClick: [{ type: i0.Output, args: ["menuItemClick"] }], menuOpen: [{ type: i0.Output, args: ["menuOpen"] }], menuClose: [{ type: i0.Output, args: ["menuClose"] }], menuTemplate: [{ type: i0.ViewChild, args: ['menuTemplate', { isSignal: true }] }], contextMenuTemplate: [{ type: i0.ViewChild, args: ['contextMenuTemplate', { isSignal: true }] }], contentItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnMenuItemComponent), { isSignal: true }] }] } });
2838
3301
 
2839
3302
  class TnSlideToggleComponent {
2840
3303
  toggleEl = viewChild.required('toggleEl');
@@ -2998,6 +3461,9 @@ class TnCardComponent {
2998
3461
  hasHeader = computed(() => {
2999
3462
  return !!(this.projectedHeader() || this.title() || this.headerStatus() || this.headerControl() || this.headerMenu());
3000
3463
  }, ...(ngDevMode ? [{ debugName: "hasHeader" }] : []));
3464
+ hasHeaderRight = computed(() => {
3465
+ return !!(this.headerStatus() || this.headerControl() || this.headerMenu()?.length);
3466
+ }, ...(ngDevMode ? [{ debugName: "hasHeaderRight" }] : []));
3001
3467
  hasFooter = computed(() => {
3002
3468
  return !!(this.primaryAction() || this.secondaryAction() || this.footerLink());
3003
3469
  }, ...(ngDevMode ? [{ debugName: "hasFooter" }] : []));
@@ -3020,7 +3486,7 @@ class TnCardComponent {
3020
3486
  return type ? `tn-card__status--${type}` : 'tn-card__status--neutral';
3021
3487
  }
3022
3488
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3023
- 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 <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 </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:.875rem}.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:.875rem;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"] }] });
3024
3490
  }
3025
3491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, decorators: [{
3026
3492
  type: Component,
@@ -3033,7 +3499,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3033
3499
  TnMenuComponent,
3034
3500
  TnMenuTriggerDirective,
3035
3501
  TnTestIdDirective,
3036
- ], 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 <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 </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:.875rem}.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:.875rem;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"] }]
3502
+ ], 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"] }]
3037
3503
  }], ctorParameters: () => [], propDecorators: { projectedHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnCardHeaderDirective), { isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], titleLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleLink", required: false }] }], elevation: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevation", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], padContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "padContent", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], headerStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerStatus", required: false }] }], headerControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerControl", required: false }] }], headerMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenu", required: false }] }], headerMenuTriggerTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenuTriggerTestId", required: false }] }], primaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryAction", required: false }] }], secondaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryAction", required: false }] }], footerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLink", required: false }] }] } });
3038
3504
 
3039
3505
  const expandCollapseAnimation = trigger('expandCollapse', [
@@ -5053,11 +5519,11 @@ class TnFormFieldComponent {
5053
5519
  return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
5054
5520
  }, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
5055
5521
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5056
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
5522
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
5057
5523
  }
5058
5524
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
5059
5525
  type: Component,
5060
- args: [{ selector: 'tn-form-field', standalone: true, imports: [TnTestIdDirective], template: "<div class=\"tn-form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"] }]
5526
+ args: [{ selector: 'tn-form-field', standalone: true, imports: [TnTestIdDirective], template: "<div class=\"tn-form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"] }]
5061
5527
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], subscriptSizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscriptSizing", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { isSignal: true }] }] } });
5062
5528
 
5063
5529
  /**
@@ -5278,6 +5744,11 @@ class TnSelectComponent {
5278
5744
  * fallback uses `JSON.stringify`, which is key-order dependent and can
5279
5745
  * produce false negatives for structurally equal objects. For primitives the
5280
5746
  * default identity check is fine.
5747
+ *
5748
+ * @example
5749
+ * ```ts
5750
+ * compareWith = (a, b) => a?.id === b?.id;
5751
+ * ```
5281
5752
  */
5282
5753
  compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
5283
5754
  selectionChange = output();
@@ -5291,110 +5762,73 @@ class TnSelectComponent {
5291
5762
  /** Index into `flatOptions` of the keyboard-focused row (-1 when none). */
5292
5763
  focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
5293
5764
  formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
5294
- // Name of the CSS custom property that defines the dropdown's max-height
5295
- // (set in select.component.scss). Reading it via getComputedStyle keeps the
5296
- // flip-up heuristic in sync with the stylesheet — no duplicated constant.
5297
- static DROPDOWN_MAX_HEIGHT_VAR = '--tn-select-dropdown-max-height';
5298
- // Fallback used when getComputedStyle can't resolve the variable (older
5299
- // browsers, jsdom in some test configs).
5300
- static DROPDOWN_MAX_HEIGHT_FALLBACK = 200;
5301
- // Per-instance suffix used to namespace DOM ids when `testId` is empty.
5302
- // Without this, two `<tn-select>` elements with no testId would emit
5303
- // colliding option/dropdown/group ids, breaking aria-activedescendant.
5304
- // A random suffix is preferred over a monotonic counter so id values stay
5305
- // stable from test file to test file (a counter would grow unpredictably
5306
- // across suites and break snapshot tests).
5307
- fallbackId = `auto-${Math.random().toString(36).slice(2, 10)}`;
5308
- uniqueId = computed(() => this.testId() || this.fallbackId, ...(ngDevMode ? [{ debugName: "uniqueId" }] : []));
5765
+ // Per-instance fallback id namespace so aria-activedescendant ids stay
5766
+ // unique across selects when no `testId` is provided.
5767
+ static instanceCounter = 0;
5768
+ instanceId = `i${++TnSelectComponent.instanceCounter}`;
5769
+ /**
5770
+ * Id namespace used by all DOM ids the template emits (dropdown panel,
5771
+ * option rows, group labels). Prefers `testId` when set so tests can target
5772
+ * specific instances; otherwise falls back to a per-instance counter so two
5773
+ * `<tn-select>`s on the same page never collide on `aria-controls`/group ids.
5774
+ */
5775
+ idNamespace = computed(() => this.testId() || this.instanceId, ...(ngDevMode ? [{ debugName: "idNamespace" }] : []));
5309
5776
  // Computed disabled state (combines input and form state)
5310
5777
  isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
5311
5778
  /**
5312
- * Flattened option list (ungrouped + grouped, in render order). The keyboard
5313
- * navigation walks this list entries from disabled groups are kept but
5314
- * marked disabled so the cursor skips over them correctly.
5779
+ * Selectable, non-disabled options in display order (regular options first,
5780
+ * then groups). Used by keyboard navigation so we can skip disabled
5781
+ * entries and group headers without a separate filter pass.
5315
5782
  */
5316
- flatOptions = computed(() => {
5317
- const flat = [...this.options()];
5318
- for (const group of this.optionGroups()) {
5319
- for (const opt of group.options) {
5320
- flat.push({ ...opt, disabled: opt.disabled || group.disabled });
5783
+ navigableOptions = computed(() => {
5784
+ const result = [];
5785
+ const baseId = `tn-select-opt-${this.idNamespace()}`;
5786
+ let i = 0;
5787
+ for (const opt of this.options()) {
5788
+ if (!opt.disabled) {
5789
+ result.push({ option: opt, id: `${baseId}-${i}` });
5321
5790
  }
5791
+ i++;
5322
5792
  }
5323
- return flat;
5324
- }, ...(ngDevMode ? [{ debugName: "flatOptions" }] : []));
5325
- /**
5326
- * Starting flat-index of each option group, used by the template to
5327
- * translate a (group, option) pair into the matching `flatOptions` index.
5328
- */
5329
- groupOffsets = computed(() => {
5330
- const offsets = [];
5331
- let offset = this.options().length;
5332
5793
  for (const group of this.optionGroups()) {
5333
- offsets.push(offset);
5334
- offset += group.options.length;
5794
+ for (const opt of group.options) {
5795
+ if (!opt.disabled && !group.disabled) {
5796
+ result.push({ option: opt, id: `${baseId}-${i}` });
5797
+ }
5798
+ i++;
5799
+ }
5335
5800
  }
5336
- return offsets;
5337
- }, ...(ngDevMode ? [{ debugName: "groupOffsets" }] : []));
5338
- /** `aria-activedescendant` id for the focused option (or null). */
5339
- activeOptionId = computed(() => {
5801
+ return result;
5802
+ }, ...(ngDevMode ? [{ debugName: "navigableOptions" }] : []));
5803
+ /** Stable DOM id of the currently-highlighted option, for aria-activedescendant. */
5804
+ focusedOptionId = computed(() => {
5340
5805
  const idx = this.focusedIndex();
5341
- if (idx < 0 || !this.isOpen()) {
5342
- return null;
5343
- }
5344
- return this.optionId(idx);
5345
- }, ...(ngDevMode ? [{ debugName: "activeOptionId" }] : []));
5806
+ const nav = this.navigableOptions();
5807
+ return idx >= 0 && idx < nav.length ? nav[idx].id : null;
5808
+ }, ...(ngDevMode ? [{ debugName: "focusedOptionId" }] : []));
5809
+ /** Stable DOM id for an option; matches what navigableOptions() assigns. */
5810
+ optionId(option) {
5811
+ const entry = this.navigableOptions().find((x) => x.option === option);
5812
+ return entry?.id ?? null;
5813
+ }
5814
+ /** Whether `option` is the keyboard-highlighted item. */
5815
+ isOptionFocused(option) {
5816
+ const idx = this.focusedIndex();
5817
+ const nav = this.navigableOptions();
5818
+ return idx >= 0 && idx < nav.length && nav[idx].option === option;
5819
+ }
5346
5820
  onChange = (_value) => { };
5347
5821
  onTouched = () => { };
5348
5822
  elementRef = inject(ElementRef);
5349
5823
  cdr = inject(ChangeDetectorRef);
5350
- triggerEl = viewChild('trigger', ...(ngDevMode ? [{ debugName: "triggerEl" }] : []));
5351
- constructor() {
5352
- // Click-outside detection. Cleanup is registered via the `onCleanup`
5353
- // callback (returning a function from `effect()` does *not* register one) —
5354
- // which fires both when the effect re-runs and when the component's
5355
- // injector is destroyed, so the listener is removed even if the host is
5356
- // torn down while the dropdown is still open. The `disposed` flag prevents
5357
- // the listener from being attached at all if teardown races the deferred
5358
- // setTimeout — without it, a cleanup that fires between the timeout firing
5359
- // and addEventListener executing could leak a permanent listener.
5360
- effect((onCleanup) => {
5361
- if (!this.isOpen()) {
5362
- return;
5363
- }
5364
- let disposed = false;
5365
- const clickListener = (event) => {
5366
- if (!this.elementRef.nativeElement.contains(event.target)) {
5367
- // Click outside → close, but don't steal focus from whatever the
5368
- // user clicked on.
5369
- this.closeDropdown({ restoreFocus: false });
5370
- }
5371
- };
5372
- // Deferred so the click that opened the dropdown doesn't immediately
5373
- // close it on its bubble back up to the document.
5374
- const timeoutId = setTimeout(() => {
5375
- if (disposed) {
5376
- return;
5377
- }
5378
- document.addEventListener('click', clickListener);
5379
- }, 0);
5380
- onCleanup(() => {
5381
- disposed = true;
5382
- clearTimeout(timeoutId);
5383
- document.removeEventListener('click', clickListener);
5384
- });
5385
- });
5386
- // When the dropdown opens, scroll the focused option into view.
5387
- effect(() => {
5388
- if (!this.isOpen()) {
5389
- return;
5390
- }
5391
- const idx = this.focusedIndex();
5392
- if (idx < 0) {
5393
- return;
5394
- }
5395
- queueMicrotask(() => this.scrollFocusedIntoView());
5396
- });
5397
- }
5824
+ overlay = inject(Overlay);
5825
+ viewContainerRef = inject(ViewContainerRef);
5826
+ triggerEl = viewChild.required('triggerEl');
5827
+ dropdownTemplate = viewChild.required('dropdownTemplate');
5828
+ overlayRef;
5829
+ overlaySubs = [];
5830
+ // CDK Overlay handles outside-click detection and Escape; no constructor
5831
+ // wiring needed. See openDropdown/closeDropdown.
5398
5832
  // ControlValueAccessor implementation
5399
5833
  writeValue(value) {
5400
5834
  if (this.multiple()) {
@@ -5430,100 +5864,107 @@ class TnSelectComponent {
5430
5864
  this.openDropdown();
5431
5865
  }
5432
5866
  }
5433
- /**
5434
- * Open the dropdown, seed the keyboard cursor on the currently-selected
5435
- * option (or the first focusable one), and decide whether to flip up.
5436
- */
5437
5867
  openDropdown() {
5438
- if (this.isDisabled()) {
5868
+ if (this.isDisabled() || this.isOpen()) {
5439
5869
  return;
5440
5870
  }
5441
- this.dropdownPosition.set(this.computeDropdownPosition());
5442
5871
  this.isOpen.set(true);
5443
- this.focusedIndex.set(this.initialFocusIndex());
5872
+ // Seed keyboard focus at the current selection when there is one;
5873
+ // otherwise leave it unset so the next ArrowDown lands on the first item.
5874
+ const selected = this.selectedValue();
5875
+ if (selected !== null && selected !== undefined) {
5876
+ const idx = this.navigableOptions().findIndex((x) => this.compareValues(x.option.value, selected));
5877
+ this.focusedIndex.set(idx);
5878
+ }
5879
+ else {
5880
+ this.focusedIndex.set(-1);
5881
+ }
5882
+ this.attachOverlay();
5444
5883
  }
5445
5884
  /**
5446
- * Close the dropdown.
5885
+ * Attach the dropdown panel as a CDK overlay anchored to the trigger.
5447
5886
  *
5448
- * @param restoreFocus When `true` (default), return focus to the trigger so
5449
- * keyboard users land somewhere sensible. Pass `false` for click-outside
5450
- * so we don't steal focus from the element the user just navigated to.
5887
+ * Why an overlay (vs. an inline absolutely-positioned panel):
5888
+ * - Escapes parent `overflow: hidden`/clipping in surrounding layouts.
5889
+ * - `outsidePointerEvents()` notifies on outside pointerdown WITHOUT
5890
+ * intercepting the click (no backdrop) — so the user's click reaches
5891
+ * the underlying target while the select closes silently.
5892
+ * - Position is recomputed on scroll so the panel stays attached.
5893
+ * - Width is matched to the trigger so the panel doesn't jump in size.
5451
5894
  */
5452
- closeDropdown(options = {}) {
5453
- const restoreFocus = options.restoreFocus ?? true;
5454
- if (!this.isOpen()) {
5455
- return;
5456
- }
5457
- this.isOpen.set(false);
5458
- this.focusedIndex.set(-1);
5459
- this.onTouched();
5460
- if (restoreFocus) {
5461
- this.triggerEl()?.nativeElement.focus({ preventScroll: true });
5462
- }
5463
- }
5464
- /** Picks the initial focused-row index when the dropdown opens. */
5465
- initialFocusIndex() {
5466
- const flat = this.flatOptions();
5467
- if (flat.length === 0) {
5468
- return -1;
5469
- }
5470
- // Prefer the currently selected option (or first selected in multi mode).
5471
- if (this.multiple()) {
5472
- const values = this.selectedValues();
5473
- if (values.length > 0) {
5474
- const idx = flat.findIndex((opt) => values.some((v) => this.compareValues(v, opt.value)));
5475
- if (idx >= 0 && !flat[idx].disabled) {
5476
- return idx;
5477
- }
5895
+ attachOverlay() {
5896
+ const trigger = this.triggerEl().nativeElement;
5897
+ const positionStrategy = this.overlay
5898
+ .position()
5899
+ .flexibleConnectedTo(trigger)
5900
+ .withPositions([
5901
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
5902
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
5903
+ ]);
5904
+ this.overlayRef = this.overlay.create({
5905
+ positionStrategy,
5906
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
5907
+ hasBackdrop: false,
5908
+ width: trigger.offsetWidth,
5909
+ });
5910
+ const portal = new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef);
5911
+ this.overlayRef.attach(portal);
5912
+ // Click-outside (non-intercepting). The pointer event still reaches the
5913
+ // element the user clicked; we just notice and close.
5914
+ //
5915
+ // Important: ignore events whose target is inside the select host. A
5916
+ // pointerdown on the trigger is "outside the overlay" from CDK's POV but
5917
+ // it's our own toggle target letting closeDropdown fire here races the
5918
+ // trigger's click handler and the dropdown immediately reopens.
5919
+ this.overlaySubs.push(this.overlayRef.outsidePointerEvents().subscribe((event) => {
5920
+ const target = event.target;
5921
+ if (target && this.elementRef.nativeElement.contains(target)) {
5922
+ return;
5478
5923
  }
5479
- }
5480
- else {
5481
- const value = this.selectedValue();
5482
- if (value !== null && value !== undefined) {
5483
- const idx = flat.findIndex((opt) => this.compareValues(opt.value, value));
5484
- if (idx >= 0 && !flat[idx].disabled) {
5485
- return idx;
5486
- }
5924
+ this.closeDropdown(false);
5925
+ }));
5926
+ // Escape as a fallback (the trigger keydown handler covers the common case,
5927
+ // but if focus ever moves into the panel, this catches it too).
5928
+ this.overlaySubs.push(this.overlayRef.keydownEvents().subscribe((event) => {
5929
+ if (event.key === 'Escape' && !event.altKey && !event.ctrlKey && !event.metaKey) {
5930
+ event.preventDefault();
5931
+ this.closeDropdown(true);
5487
5932
  }
5488
- }
5489
- // Otherwise the first non-disabled option.
5490
- return flat.findIndex((opt) => !opt.disabled);
5933
+ }));
5491
5934
  }
5492
- /**
5493
- * Decide whether the dropdown should open above or below the trigger.
5494
- * Opens above when there isn't enough space below the trigger AND there is
5495
- * more space above — otherwise stays below. Falls back to `'below'` when no
5496
- * trigger element is found yet.
5497
- */
5498
- computeDropdownPosition() {
5499
- if (typeof window === 'undefined') {
5500
- return 'below';
5501
- }
5502
- const trigger = this.elementRef.nativeElement.querySelector('.tn-select-trigger');
5503
- if (!trigger) {
5504
- return 'below';
5505
- }
5506
- const rect = trigger.getBoundingClientRect();
5507
- const spaceBelow = window.innerHeight - rect.bottom;
5508
- const spaceAbove = rect.top;
5509
- if (spaceBelow < this.readDropdownMaxHeight(trigger) && spaceAbove > spaceBelow) {
5510
- return 'above';
5511
- }
5512
- return 'below';
5935
+ detachOverlay() {
5936
+ this.overlaySubs.forEach((s) => s.unsubscribe());
5937
+ this.overlaySubs = [];
5938
+ this.overlayRef?.dispose();
5939
+ this.overlayRef = undefined;
5940
+ }
5941
+ ngOnDestroy() {
5942
+ // If the component is destroyed while the dropdown is open (e.g. router
5943
+ // navigates away), closeDropdown() never runs — clean up directly here.
5944
+ this.detachOverlay();
5513
5945
  }
5514
5946
  /**
5515
- * Reads the dropdown's max-height from the CSS custom property set in
5516
- * select.component.scss. Single source of truth for the flip-up threshold —
5517
- * if the stylesheet changes, the heuristic follows automatically.
5947
+ * Closes the dropdown.
5948
+ *
5949
+ * @param restoreFocus When `true` (the default), returns focus to the
5950
+ * trigger. Used for explicit closes — Escape, Enter/Space activation,
5951
+ * option click — where the user is still interacting with the select.
5952
+ * Pass `false` for click-outside / blur paths so we don't steal focus
5953
+ * from the element the user actually navigated to.
5518
5954
  */
5519
- readDropdownMaxHeight(trigger) {
5520
- const raw = getComputedStyle(trigger)
5521
- .getPropertyValue(TnSelectComponent.DROPDOWN_MAX_HEIGHT_VAR)
5522
- .trim();
5523
- const parsed = parseFloat(raw);
5524
- return Number.isFinite(parsed) && parsed > 0
5525
- ? parsed
5526
- : TnSelectComponent.DROPDOWN_MAX_HEIGHT_FALLBACK;
5955
+ closeDropdown(restoreFocus = true) {
5956
+ this.isOpen.set(false);
5957
+ this.focusedIndex.set(-1);
5958
+ this.detachOverlay();
5959
+ this.onTouched();
5960
+ if (restoreFocus) {
5961
+ // `focusVisible: true` (Chrome/Firefox) keeps the :focus-visible outline
5962
+ // on the trigger after Escape / Enter / option-pick — without it,
5963
+ // programmatic .focus() is treated as non-keyboard and the focus ring
5964
+ // silently disappears, which users perceive as "focus lost". Safari
5965
+ // ignores the option and falls back to its heuristic.
5966
+ this.triggerEl().nativeElement.focus({ preventScroll: true, focusVisible: true });
5967
+ }
5527
5968
  }
5528
5969
  onOptionClick(option, groupDisabled = false) {
5529
5970
  if (option.disabled || groupDisabled) {
@@ -5567,10 +6008,6 @@ class TnSelectComponent {
5567
6008
  }
5568
6009
  return this.compareValues(this.selectedValue(), option.value);
5569
6010
  }
5570
- /** Build a stable DOM id for the option at `index` for aria-activedescendant. */
5571
- optionId(index) {
5572
- return `tn-select-${this.uniqueId()}-option-${index}`;
5573
- }
5574
6011
  displayText = computed(() => {
5575
6012
  if (this.multiple()) {
5576
6013
  const values = this.selectedValues();
@@ -5604,14 +6041,26 @@ class TnSelectComponent {
5604
6041
  }
5605
6042
  return undefined;
5606
6043
  }
5607
- anyOptionsPresent = computed(() => {
6044
+ hasAnyOptions = computed(() => {
5608
6045
  return this.options().length > 0 || this.optionGroups().length > 0;
5609
- }, ...(ngDevMode ? [{ debugName: "anyOptionsPresent" }] : []));
6046
+ }, ...(ngDevMode ? [{ debugName: "hasAnyOptions" }] : []));
6047
+ /** One-shot guard so the object-compare warning fires at most once per instance. */
6048
+ warnedAboutObjectCompare = false;
5610
6049
  /**
5611
- * Compares two option values for equality. Uses `compareWith` if provided,
5612
- * otherwise identity (`===`). For object values it falls back to
5613
- * `JSON.stringify`, which is key-order dependent consumers with object
5614
- * values should provide `compareWith` to avoid subtle bugs.
6050
+ * Compares two option values for equality.
6051
+ *
6052
+ * - Uses `compareWith` when provided (the supported path for object values).
6053
+ * - Falls back to strict identity (`===`) — adequate for primitives.
6054
+ * - For object values WITHOUT `compareWith` we return `false` (no
6055
+ * structural compare) and emit a one-time warning. The previous
6056
+ * `JSON.stringify` fallback was key-order sensitive and produced silent
6057
+ * false-negatives that were hard to diagnose; returning `false` makes the
6058
+ * misuse loud (selection won't match) and the warning points to the fix.
6059
+ *
6060
+ * The warning is **unconditional** (not gated on `isDevMode()`) so prod
6061
+ * monitoring picks it up — consumers relying on the old stringify fallback
6062
+ * would otherwise see selections silently stop matching after upgrade with
6063
+ * no signal in production logs.
5615
6064
  */
5616
6065
  compareValues(a, b) {
5617
6066
  const customCompare = this.compareWith();
@@ -5622,143 +6071,143 @@ class TnSelectComponent {
5622
6071
  return true;
5623
6072
  }
5624
6073
  if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
5625
- return JSON.stringify(a) === JSON.stringify(b);
6074
+ if (!this.warnedAboutObjectCompare) {
6075
+ this.warnedAboutObjectCompare = true;
6076
+ console.warn('[tn-select] Comparing object option values without a `compareWith` input. ' +
6077
+ 'Identity comparison will not match structurally-equal objects from different ' +
6078
+ 'references. Provide `[compareWith]="(a, b) => a?.id === b?.id"` (or similar).');
6079
+ }
6080
+ return false;
5626
6081
  }
5627
6082
  return false;
5628
6083
  }
5629
6084
  /**
5630
- * Keyboard handling on the trigger (focus stays on the trigger while the
5631
- * dropdown is open — options use mousedown-preventDefault to avoid stealing
5632
- * it). Implements the WAI-ARIA combobox pattern subset we need:
6085
+ * Keyboard navigation for the combobox trigger.
6086
+ *
6087
+ * - **ArrowDown / ArrowUp** opens the dropdown if closed; otherwise moves
6088
+ * the keyboard-focus highlight (via aria-activedescendant) up/down,
6089
+ * skipping disabled options and group headers.
6090
+ * - **Home / End** jump to the first / last enabled option.
6091
+ * - **Enter / Space** opens the dropdown if closed; if open and an option
6092
+ * is highlighted, selects that option (in single mode) or toggles it
6093
+ * (in multiple mode).
6094
+ * - **Escape** closes the dropdown without changing the selection.
5633
6095
  *
5634
- * - **Enter / Space**: open closed dropdown, or select the focused row
5635
- * (toggle in multi-mode).
5636
- * - **ArrowDown / ArrowUp**: move the focused row; opens the dropdown first
5637
- * if it's closed.
5638
- * - **Home / End**: jump to first / last focusable row (when open).
5639
- * - **Escape**: close and restore focus to the trigger.
5640
- * - **Tab**: close without preventing default so focus moves to the next
5641
- * element naturally.
6096
+ * All navigation keys call `event.preventDefault()` so the page does not
6097
+ * scroll while the user is moving through options.
5642
6098
  */
5643
6099
  onKeydown(event) {
6100
+ if (this.isDisabled()) {
6101
+ return;
6102
+ }
5644
6103
  switch (event.key) {
5645
- case 'Enter':
5646
- case ' ':
5647
- if (this.isOpen()) {
5648
- this.selectFocused();
5649
- }
5650
- else {
5651
- this.openDropdown();
5652
- }
5653
- event.preventDefault();
5654
- break;
5655
- case 'Escape':
5656
- if (this.isOpen()) {
5657
- this.closeDropdown();
5658
- event.preventDefault();
5659
- }
5660
- break;
5661
6104
  case 'ArrowDown':
6105
+ event.preventDefault();
5662
6106
  if (!this.isOpen()) {
5663
6107
  this.openDropdown();
6108
+ if (this.focusedIndex() < 0) {
6109
+ this.moveFocus('first');
6110
+ }
5664
6111
  }
5665
6112
  else {
5666
6113
  this.moveFocus(1);
5667
6114
  }
5668
- event.preventDefault();
5669
6115
  break;
5670
6116
  case 'ArrowUp':
6117
+ event.preventDefault();
5671
6118
  if (!this.isOpen()) {
5672
6119
  this.openDropdown();
6120
+ this.moveFocus('last');
5673
6121
  }
5674
6122
  else {
5675
6123
  this.moveFocus(-1);
5676
6124
  }
5677
- event.preventDefault();
5678
6125
  break;
5679
6126
  case 'Home':
5680
6127
  if (this.isOpen()) {
5681
- this.moveFocusTo(0, 1);
5682
6128
  event.preventDefault();
6129
+ this.moveFocus('first');
5683
6130
  }
5684
6131
  break;
5685
6132
  case 'End':
5686
6133
  if (this.isOpen()) {
5687
- this.moveFocusTo(this.flatOptions().length - 1, -1);
5688
6134
  event.preventDefault();
6135
+ this.moveFocus('last');
6136
+ }
6137
+ break;
6138
+ case 'Enter':
6139
+ case ' ':
6140
+ event.preventDefault();
6141
+ if (!this.isOpen()) {
6142
+ this.openDropdown();
6143
+ }
6144
+ else {
6145
+ this.activateFocusedOption();
6146
+ }
6147
+ break;
6148
+ case 'Escape':
6149
+ if (this.isOpen()) {
6150
+ event.preventDefault();
6151
+ this.closeDropdown();
5689
6152
  }
5690
6153
  break;
5691
6154
  case 'Tab':
5692
- // Let the browser move focus to the next element; just close.
6155
+ // Standard combobox: Tab moves focus out of the select; close first
6156
+ // so the trigger stays a clean tab stop. Don't refocus — that would
6157
+ // fight the natural Tab advance to the next focusable element.
5693
6158
  if (this.isOpen()) {
5694
- this.closeDropdown({ restoreFocus: false });
6159
+ this.closeDropdown(false);
5695
6160
  }
5696
6161
  break;
5697
6162
  }
5698
6163
  }
5699
- /** Step the focused row by ±1 (or more), skipping disabled options. */
5700
- moveFocus(delta) {
5701
- const flat = this.flatOptions();
5702
- if (flat.length === 0) {
6164
+ moveFocus(target) {
6165
+ const count = this.navigableOptions().length;
6166
+ if (count === 0) {
5703
6167
  return;
5704
6168
  }
5705
- let idx = this.focusedIndex();
5706
- for (let i = 0; i < flat.length; i++) {
5707
- idx = (idx + delta + flat.length) % flat.length;
5708
- if (!flat[idx].disabled) {
5709
- this.focusedIndex.set(idx);
5710
- this.scrollFocusedIntoView();
5711
- return;
5712
- }
6169
+ let next;
6170
+ if (target === 'first') {
6171
+ next = 0;
5713
6172
  }
5714
- }
5715
- /** Move focus to a specific index, scanning forward/backward to skip disabled. */
5716
- moveFocusTo(start, step) {
5717
- const flat = this.flatOptions();
5718
- if (flat.length === 0) {
5719
- return;
6173
+ else if (target === 'last') {
6174
+ next = count - 1;
5720
6175
  }
5721
- let idx = start;
5722
- while (idx >= 0 && idx < flat.length) {
5723
- if (!flat[idx].disabled) {
5724
- this.focusedIndex.set(idx);
5725
- this.scrollFocusedIntoView();
5726
- return;
5727
- }
5728
- idx += step;
6176
+ else {
6177
+ const current = this.focusedIndex();
6178
+ const start = current < 0 ? (target === 1 ? -1 : count) : current;
6179
+ next = (start + target + count) % count;
5729
6180
  }
6181
+ this.focusedIndex.set(next);
6182
+ this.scrollFocusedIntoView();
5730
6183
  }
5731
- /** Select (or toggle, in multi-mode) the currently keyboard-focused row. */
5732
- selectFocused() {
6184
+ activateFocusedOption() {
5733
6185
  const idx = this.focusedIndex();
5734
- const flat = this.flatOptions();
5735
- if (idx < 0 || idx >= flat.length) {
5736
- return;
5737
- }
5738
- const opt = flat[idx];
5739
- if (opt.disabled) {
6186
+ const nav = this.navigableOptions();
6187
+ if (idx < 0 || idx >= nav.length) {
5740
6188
  return;
5741
6189
  }
5742
- if (this.multiple()) {
5743
- this.toggleOption(opt);
5744
- }
5745
- else {
5746
- this.selectOption(opt);
5747
- }
6190
+ this.onOptionClick(nav[idx].option);
5748
6191
  }
5749
- /** Scrolls the keyboard-focused option into view if it's outside the dropdown's viewport. */
5750
6192
  scrollFocusedIntoView() {
5751
- const idx = this.focusedIndex();
5752
- if (idx < 0) {
6193
+ const id = this.focusedOptionId();
6194
+ if (!id) {
5753
6195
  return;
5754
6196
  }
5755
- const host = this.elementRef.nativeElement;
5756
- // Use attribute-equality instead of #id selectors so we don't need
5757
- // CSS.escape (unavailable in jsdom for tests) — option ids may contain
5758
- // characters that require escaping in #id form.
5759
- const el = host.querySelector(`[id="${this.optionId(idx)}"]`);
5760
- // jsdom doesn't implement scrollIntoView guard so tests don't crash.
5761
- el?.scrollIntoView?.({ block: 'nearest' });
6197
+ const overlayEl = this.overlayRef?.overlayElement;
6198
+ if (!overlayEl) {
6199
+ return;
6200
+ }
6201
+ // Defer to next tick so the DOM has updated with the new .focused class.
6202
+ // The dropdown panel is rendered in a CDK overlay outside the host, so we
6203
+ // scope the query to the overlay element rather than the host (which
6204
+ // would silently miss the option) or `document` (which would pick the
6205
+ // wrong option if two tn-select instances with the same testId are open
6206
+ // simultaneously, and couples the component to a global).
6207
+ queueMicrotask(() => {
6208
+ const el = overlayEl.querySelector(`#${CSS.escape(id)}`);
6209
+ el?.scrollIntoView({ block: 'nearest' });
6210
+ });
5762
6211
  }
5763
6212
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5764
6213
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, noOptionsLabel: { classPropertyName: "noOptionsLabel", publicName: "noOptionsLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", multiSelectionChange: "multiSelectionChange" }, providers: [
@@ -5767,7 +6216,7 @@ class TnSelectComponent {
5767
6216
  useExisting: forwardRef(() => TnSelectComponent),
5768
6217
  multi: true
5769
6218
  }
5770
- ], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-select-container\" [tnTestId]=\"testId()\">\n <!-- Select Trigger -->\n <div\n #trigger\n class=\"tn-select-trigger\"\n role=\"combobox\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + uniqueId() : null\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel() ?? placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ displayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [class.tn-select-dropdown--above]=\"dropdownPosition() === 'above'\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + uniqueId()\">\n\n <!-- Options List -->\n <!-- Options follow the WAI-ARIA combobox pattern: focus stays on the\n trigger, navigation is via the trigger's keydown handler and\n aria-activedescendant. Options handle click only, by design \u2014 they\n must not steal focus on mousedown. -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [class.focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let groupIdx = $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + uniqueId() + '-' + groupIdx\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + uniqueId() + '-' + groupIdx\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index; let optIdx = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [id]=\"optionId(groupOffsets()[groupIdx] + optIdx)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [class.focused]=\"focusedIndex() === groupOffsets()[groupIdx] + optIdx\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!anyOptionsPresent()) {\n <div class=\"tn-select-no-options\">\n {{ noOptionsLabel() }}\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;--tn-select-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-dropdown--above{top:auto;bottom:100%;margin-top:0;margin-bottom:.25rem}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg1, #f1f3f5);outline:2px solid var(--tn-primary, #007bff);outline-offset:-2px}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6219
+ ], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "dropdownTemplate", first: true, predicate: ["dropdownTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-select-container\" [tnTestId]=\"testId()\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + idNamespace() : null\"\n [attr.aria-label]=\"ariaLabel() ?? placeholder()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-activedescendant]=\"isOpen() ? focusedOptionId() : null\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ displayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n</div>\n\n<!-- Dropdown panel \u2014 portaled via CDK Overlay so it escapes parent\n overflow/clipping. The trigger keeps DOM focus throughout (combobox\n pattern); options use aria-activedescendant for virtual focus. -->\n<ng-template #dropdownTemplate>\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + idNamespace()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <!-- Option activation lives on the trigger via aria-activedescendant; options have tabindex=\"-1\" and never receive DOM focus. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"optionId(option)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.focused]=\"isOptionFocused(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + idNamespace() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + idNamespace() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"optionId(option)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.focused]=\"isOptionFocused(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n {{ noOptionsLabel() }}\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;--tn-select-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-error, #dc3545) 25%,transparent)}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg2, #e8f4fd);box-shadow:inset 2px 0 0 var(--tn-primary, #007bff)}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5771
6220
  }
5772
6221
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
5773
6222
  type: Component,
@@ -5777,8 +6226,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
5777
6226
  useExisting: forwardRef(() => TnSelectComponent),
5778
6227
  multi: true
5779
6228
  }
5780
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-select-container\" [tnTestId]=\"testId()\">\n <!-- Select Trigger -->\n <div\n #trigger\n class=\"tn-select-trigger\"\n role=\"combobox\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + uniqueId() : null\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel() ?? placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ displayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [class.tn-select-dropdown--above]=\"dropdownPosition() === 'above'\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + uniqueId()\">\n\n <!-- Options List -->\n <!-- Options follow the WAI-ARIA combobox pattern: focus stays on the\n trigger, navigation is via the trigger's keydown handler and\n aria-activedescendant. Options handle click only, by design \u2014 they\n must not steal focus on mousedown. -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [class.focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let groupIdx = $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + uniqueId() + '-' + groupIdx\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + uniqueId() + '-' + groupIdx\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index; let optIdx = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [id]=\"optionId(groupOffsets()[groupIdx] + optIdx)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [class.focused]=\"focusedIndex() === groupOffsets()[groupIdx] + optIdx\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!anyOptionsPresent()) {\n <div class=\"tn-select-no-options\">\n {{ noOptionsLabel() }}\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;--tn-select-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-dropdown--above{top:auto;bottom:100%;margin-top:0;margin-bottom:.25rem}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg1, #f1f3f5);outline:2px solid var(--tn-primary, #007bff);outline-offset:-2px}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] }]
5781
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], noOptionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noOptionsLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], multiSelectionChange: [{ type: i0.Output, args: ["multiSelectionChange"] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
6229
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-select-container\" [tnTestId]=\"testId()\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + idNamespace() : null\"\n [attr.aria-label]=\"ariaLabel() ?? placeholder()\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-activedescendant]=\"isOpen() ? focusedOptionId() : null\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ displayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n</div>\n\n<!-- Dropdown panel \u2014 portaled via CDK Overlay so it escapes parent\n overflow/clipping. The trigger keeps DOM focus throughout (combobox\n pattern); options use aria-activedescendant for virtual focus. -->\n<ng-template #dropdownTemplate>\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + idNamespace()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <!-- Option activation lives on the trigger via aria-activedescendant; options have tabindex=\"-1\" and never receive DOM focus. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"optionId(option)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.focused]=\"isOptionFocused(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + idNamespace() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + idNamespace() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-select-option\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"optionId(option)\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.focused]=\"isOptionFocused(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n {{ noOptionsLabel() }}\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;--tn-select-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-error, #dc3545) 25%,transparent)}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg2, #e8f4fd);box-shadow:inset 2px 0 0 var(--tn-primary, #007bff)}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] }]
6230
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], noOptionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noOptionsLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], multiSelectionChange: [{ type: i0.Output, args: ["multiSelectionChange"] }], triggerEl: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdownTemplate', { isSignal: true }] }] } });
5782
6231
 
5783
6232
  /**
5784
6233
  * Harness for interacting with tn-select in tests.
@@ -5934,7 +6383,9 @@ class TnSelectHarness extends ComponentHarness {
5934
6383
  */
5935
6384
  async selectOption(filter) {
5936
6385
  await this.open();
5937
- const options = await this.locatorForAll('.tn-select-option')();
6386
+ // Dropdown panel is rendered in a CDK overlay (outside the host element),
6387
+ // so we search the document root rather than the harness-local subtree.
6388
+ const options = await this.documentRootLocatorFactory().locatorForAll('.tn-select-option')();
5938
6389
  for (const option of options) {
5939
6390
  const text = (await option.text()).trim();
5940
6391
  const matches = filter instanceof RegExp
@@ -5961,7 +6412,9 @@ class TnSelectHarness extends ComponentHarness {
5961
6412
  */
5962
6413
  async getOptions() {
5963
6414
  await this.open();
5964
- const options = await this.locatorForAll('.tn-select-option')();
6415
+ // Dropdown panel is rendered in a CDK overlay (outside the host element),
6416
+ // so we search the document root rather than the harness-local subtree.
6417
+ const options = await this.documentRootLocatorFactory().locatorForAll('.tn-select-option')();
5965
6418
  const labels = [];
5966
6419
  for (const option of options) {
5967
6420
  labels.push((await option.text()).trim());
@@ -6368,7 +6821,7 @@ class TnListItemComponent {
6368
6821
  }
6369
6822
  }
6370
6823
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6371
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListItemComponent, isStandalone: true, selector: "tn-list-item", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { attributes: { "role": "listitem" }, listeners: { "click": "onClick($event)" }, properties: { "class.tn-list-item--disabled": "disabled()", "class.tn-list-item--clickable": "clickable()", "class.tn-list-item--two-line": "hasSecondaryText()", "class.tn-list-item--three-line": "hasThirdText()" }, classAttribute: "tn-list-item" }, ngImport: i0, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!-- Primary text -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:16px;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [ixListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] });
6824
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListItemComponent, isStandalone: true, selector: "tn-list-item", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { attributes: { "role": "listitem" }, listeners: { "click": "onClick($event)" }, properties: { "class.tn-list-item--disabled": "disabled()", "class.tn-list-item--clickable": "clickable()", "class.tn-list-item--two-line": "hasSecondaryText()", "class.tn-list-item--three-line": "hasThirdText()" }, classAttribute: "tn-list-item" }, ngImport: i0, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!-- Primary text -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [ixListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] });
6372
6825
  }
6373
6826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, decorators: [{
6374
6827
  type: Component,
@@ -6380,7 +6833,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6380
6833
  '[class.tn-list-item--three-line]': 'hasThirdText()',
6381
6834
  'role': 'listitem',
6382
6835
  '(click)': 'onClick($event)'
6383
- }, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!-- Primary text -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:16px;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [ixListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] }]
6836
+ }, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!-- Primary text -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [ixListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] }]
6384
6837
  }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
6385
6838
 
6386
6839
  class TnListSubheaderComponent {
@@ -6602,7 +7055,7 @@ class TnListOptionComponent {
6602
7055
  }
6603
7056
  }
6604
7057
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6605
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListOptionComponent, isStandalone: true, selector: "tn-list-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { attributes: { "role": "option" }, listeners: { "click": "onClick($event)", "keydown.space": "onKeydown($event)", "keydown.enter": "onKeydown($event)" }, properties: { "class.tn-list-option--selected": "effectiveSelected()", "class.tn-list-option--disabled": "effectiveDisabled()", "attr.aria-selected": "effectiveSelected()", "attr.aria-disabled": "effectiveDisabled()" }, classAttribute: "tn-list-option" }, ngImport: i0, template: "<div class=\"tn-list-option__content\">\n <!-- Leading content (icons, avatars) -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-option__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-option__text\">\n <div class=\"tn-list-option__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-option__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Checkbox on the right -->\n <div class=\"tn-list-option__checkbox\">\n <tn-checkbox\n tabindex=\"-1\"\n [checked]=\"effectiveSelected()\"\n [disabled]=\"effectiveDisabled()\"\n [hideLabel]=\"true\"\n (click)=\"$event.stopPropagation()\" />\n </div>\n</div>", styles: [".tn-list-option{display:block;position:relative;min-height:48px;cursor:pointer;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease;-webkit-user-select:none;user-select:none}.tn-list-option__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box;transition:background-color .2s ease;border-radius:4px}.tn-list-option__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-option__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-option__primary-text{font-size:16px;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__checkbox{flex-shrink:0;margin-left:16px;position:relative;display:flex;align-items:center}.tn-list-option--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}:host(:hover:not(.tn-list-option--disabled)) .tn-list-option__content{background-color:var(--tn-alt-bg2)}:host(:focus){outline:none}:host(:focus) .tn-list-option__content{background-color:var(--tn-alt-bg2)}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }] });
7058
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListOptionComponent, isStandalone: true, selector: "tn-list-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { attributes: { "role": "option" }, listeners: { "click": "onClick($event)", "keydown.space": "onKeydown($event)", "keydown.enter": "onKeydown($event)" }, properties: { "class.tn-list-option--selected": "effectiveSelected()", "class.tn-list-option--disabled": "effectiveDisabled()", "attr.aria-selected": "effectiveSelected()", "attr.aria-disabled": "effectiveDisabled()" }, classAttribute: "tn-list-option" }, ngImport: i0, template: "<div class=\"tn-list-option__content\">\n <!-- Leading content (icons, avatars) -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-option__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-option__text\">\n <div class=\"tn-list-option__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-option__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Checkbox on the right -->\n <div class=\"tn-list-option__checkbox\">\n <tn-checkbox\n tabindex=\"-1\"\n [checked]=\"effectiveSelected()\"\n [disabled]=\"effectiveDisabled()\"\n [hideLabel]=\"true\"\n (click)=\"$event.stopPropagation()\" />\n </div>\n</div>", styles: [".tn-list-option{display:block;position:relative;min-height:48px;cursor:pointer;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease;-webkit-user-select:none;user-select:none}.tn-list-option__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box;transition:background-color .2s ease;border-radius:4px}.tn-list-option__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-option__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-option__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__checkbox{flex-shrink:0;margin-left:16px;position:relative;display:flex;align-items:center}.tn-list-option--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}:host(:hover:not(.tn-list-option--disabled)) .tn-list-option__content{background-color:var(--tn-alt-bg2)}:host(:focus){outline:none}:host(:focus) .tn-list-option__content{background-color:var(--tn-alt-bg2)}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }] });
6606
7059
  }
6607
7060
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListOptionComponent, decorators: [{
6608
7061
  type: Component,
@@ -6613,7 +7066,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6613
7066
  'role': 'option',
6614
7067
  '[attr.aria-selected]': 'effectiveSelected()',
6615
7068
  '[attr.aria-disabled]': 'effectiveDisabled()'
6616
- }, template: "<div class=\"tn-list-option__content\">\n <!-- Leading content (icons, avatars) -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-option__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-option__text\">\n <div class=\"tn-list-option__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-option__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Checkbox on the right -->\n <div class=\"tn-list-option__checkbox\">\n <tn-checkbox\n tabindex=\"-1\"\n [checked]=\"effectiveSelected()\"\n [disabled]=\"effectiveDisabled()\"\n [hideLabel]=\"true\"\n (click)=\"$event.stopPropagation()\" />\n </div>\n</div>", styles: [".tn-list-option{display:block;position:relative;min-height:48px;cursor:pointer;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease;-webkit-user-select:none;user-select:none}.tn-list-option__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box;transition:background-color .2s ease;border-radius:4px}.tn-list-option__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-option__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-option__primary-text{font-size:16px;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__checkbox{flex-shrink:0;margin-left:16px;position:relative;display:flex;align-items:center}.tn-list-option--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}:host(:hover:not(.tn-list-option--disabled)) .tn-list-option__content{background-color:var(--tn-alt-bg2)}:host(:focus){outline:none}:host(:focus) .tn-list-option__content{background-color:var(--tn-alt-bg2)}\n"] }]
7069
+ }, template: "<div class=\"tn-list-option__content\">\n <!-- Leading content (icons, avatars) -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-option__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-option__text\">\n <div class=\"tn-list-option__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n @if (!hasPrimaryTextDirective()) {\n <ng-content />\n }\n </div>\n\n @if (hasSecondaryTextContent()) {\n <div class=\"tn-list-option__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Checkbox on the right -->\n <div class=\"tn-list-option__checkbox\">\n <tn-checkbox\n tabindex=\"-1\"\n [checked]=\"effectiveSelected()\"\n [disabled]=\"effectiveDisabled()\"\n [hideLabel]=\"true\"\n (click)=\"$event.stopPropagation()\" />\n </div>\n</div>", styles: [".tn-list-option{display:block;position:relative;min-height:48px;cursor:pointer;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease;-webkit-user-select:none;user-select:none}.tn-list-option__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box;transition:background-color .2s ease;border-radius:4px}.tn-list-option__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-option__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-option__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-option__checkbox{flex-shrink:0;margin-left:16px;position:relative;display:flex;align-items:center}.tn-list-option--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [ixListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [ixListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [ixListItemTitle]{font-weight:400}::ng-deep [ixListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}:host(:hover:not(.tn-list-option--disabled)) .tn-list-option__content{background-color:var(--tn-alt-bg2)}:host(:focus){outline:none}:host(:focus) .tn-list-option__content{background-color:var(--tn-alt-bg2)}\n"] }]
6617
7070
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], onClick: [{
6618
7071
  type: HostListener,
6619
7072
  args: ['click', ['$event']]
@@ -6732,7 +7185,7 @@ class TnEmptyComponent {
6732
7185
  return this.size() === 'compact' ? 'lg' : 'xl';
6733
7186
  }, ...(ngDevMode ? [{ debugName: "iconSizePreset" }] : []));
6734
7187
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnEmptyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6735
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnEmptyComponent, isStandalone: true, selector: "tn-empty", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconLibrary: { classPropertyName: "iconLibrary", publicName: "iconLibrary", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null }, actionText: { classPropertyName: "actionText", publicName: "actionText", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onAction: "onAction" }, host: { attributes: { "role": "status" }, properties: { "class.tn-empty--compact": "size() === \"compact\"", "class.tn-empty--bordered": "bordered()" }, classAttribute: "tn-empty" }, ngImport: i0, template: "@if (icon()) {\n <div class=\"tn-empty__icon\">\n <tn-icon\n aria-hidden=\"true\"\n [name]=\"icon()!\"\n [library]=\"iconLibrary()\"\n [size]=\"iconSizePreset()\"\n [customSize]=\"iconSize()\"\n />\n </div>\n}\n\n<div class=\"tn-empty__title\">\n {{ title() }}\n</div>\n\n@if (description()) {\n <div class=\"tn-empty__description\">\n {{ description() }}\n </div>\n}\n\n@if (hasAction()) {\n <div class=\"tn-empty__action\">\n <tn-button\n color=\"primary\"\n variant=\"outline\"\n [label]=\"actionText()!\"\n (onClick)=\"onAction.emit()\"\n />\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:48px 24px;border-radius:8px}:host(.tn-empty--bordered){border:1px solid var(--tn-lines)}:host(.tn-empty--compact){padding:24px 16px}:host(.tn-empty--compact) .tn-empty__title{font-size:1rem}:host(.tn-empty--compact) .tn-empty__description{font-size:.8125rem}:host(.tn-empty--compact) .tn-empty__icon{margin-bottom:4px}:host(.tn-empty--compact) .tn-empty__action{margin-top:4px}.tn-empty__icon{color:var(--tn-fg2, #6b7280);margin-bottom:8px}.tn-empty__title{font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #e5e7eb);line-height:1.4}.tn-empty__description{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;max-width:420px}.tn-empty__action{margin-top:8px}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }] });
7188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnEmptyComponent, isStandalone: true, selector: "tn-empty", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconLibrary: { classPropertyName: "iconLibrary", publicName: "iconLibrary", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null }, actionText: { classPropertyName: "actionText", publicName: "actionText", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onAction: "onAction" }, host: { attributes: { "role": "status" }, properties: { "class.tn-empty--compact": "size() === \"compact\"", "class.tn-empty--bordered": "bordered()" }, classAttribute: "tn-empty" }, ngImport: i0, template: "@if (icon()) {\n <div class=\"tn-empty__icon\">\n <tn-icon\n aria-hidden=\"true\"\n [name]=\"icon()!\"\n [library]=\"iconLibrary()\"\n [size]=\"iconSizePreset()\"\n [customSize]=\"iconSize()\"\n />\n </div>\n}\n\n<div class=\"tn-empty__title\">\n {{ title() }}\n</div>\n\n@if (description()) {\n <div class=\"tn-empty__description\">\n {{ description() }}\n </div>\n}\n\n@if (hasAction()) {\n <div class=\"tn-empty__action\">\n <tn-button\n color=\"primary\"\n variant=\"outline\"\n [label]=\"actionText()!\"\n (onClick)=\"onAction.emit()\"\n />\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:48px 24px;border-radius:8px}:host(.tn-empty--bordered){border:1px solid var(--tn-lines)}:host(.tn-empty--compact){padding:24px 16px}:host(.tn-empty--compact) .tn-empty__title{font-size:1rem}:host(.tn-empty--compact) .tn-empty__description{font-size:.8125rem}:host(.tn-empty--compact) .tn-empty__icon{margin-bottom:4px}:host(.tn-empty--compact) .tn-empty__action{margin-top:4px}.tn-empty__icon{color:var(--tn-fg2, #6b7280);margin-bottom:8px}.tn-empty__title{font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #e5e7eb);line-height:1.4}.tn-empty__description{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;max-width:420px}.tn-empty__action{margin-top:8px}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }] });
6736
7189
  }
6737
7190
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnEmptyComponent, decorators: [{
6738
7191
  type: Component,
@@ -6741,9 +7194,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6741
7194
  '[class.tn-empty--compact]': 'size() === "compact"',
6742
7195
  '[class.tn-empty--bordered]': 'bordered()',
6743
7196
  'role': 'status',
6744
- }, template: "@if (icon()) {\n <div class=\"tn-empty__icon\">\n <tn-icon\n aria-hidden=\"true\"\n [name]=\"icon()!\"\n [library]=\"iconLibrary()\"\n [size]=\"iconSizePreset()\"\n [customSize]=\"iconSize()\"\n />\n </div>\n}\n\n<div class=\"tn-empty__title\">\n {{ title() }}\n</div>\n\n@if (description()) {\n <div class=\"tn-empty__description\">\n {{ description() }}\n </div>\n}\n\n@if (hasAction()) {\n <div class=\"tn-empty__action\">\n <tn-button\n color=\"primary\"\n variant=\"outline\"\n [label]=\"actionText()!\"\n (onClick)=\"onAction.emit()\"\n />\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:48px 24px;border-radius:8px}:host(.tn-empty--bordered){border:1px solid var(--tn-lines)}:host(.tn-empty--compact){padding:24px 16px}:host(.tn-empty--compact) .tn-empty__title{font-size:1rem}:host(.tn-empty--compact) .tn-empty__description{font-size:.8125rem}:host(.tn-empty--compact) .tn-empty__icon{margin-bottom:4px}:host(.tn-empty--compact) .tn-empty__action{margin-top:4px}.tn-empty__icon{color:var(--tn-fg2, #6b7280);margin-bottom:8px}.tn-empty__title{font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #e5e7eb);line-height:1.4}.tn-empty__description{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;max-width:420px}.tn-empty__action{margin-top:8px}\n"] }]
7197
+ }, template: "@if (icon()) {\n <div class=\"tn-empty__icon\">\n <tn-icon\n aria-hidden=\"true\"\n [name]=\"icon()!\"\n [library]=\"iconLibrary()\"\n [size]=\"iconSizePreset()\"\n [customSize]=\"iconSize()\"\n />\n </div>\n}\n\n<div class=\"tn-empty__title\">\n {{ title() }}\n</div>\n\n@if (description()) {\n <div class=\"tn-empty__description\">\n {{ description() }}\n </div>\n}\n\n@if (hasAction()) {\n <div class=\"tn-empty__action\">\n <tn-button\n color=\"primary\"\n variant=\"outline\"\n [label]=\"actionText()!\"\n (onClick)=\"onAction.emit()\"\n />\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:48px 24px;border-radius:8px}:host(.tn-empty--bordered){border:1px solid var(--tn-lines)}:host(.tn-empty--compact){padding:24px 16px}:host(.tn-empty--compact) .tn-empty__title{font-size:1rem}:host(.tn-empty--compact) .tn-empty__description{font-size:.8125rem}:host(.tn-empty--compact) .tn-empty__icon{margin-bottom:4px}:host(.tn-empty--compact) .tn-empty__action{margin-top:4px}.tn-empty__icon{color:var(--tn-fg2, #6b7280);margin-bottom:8px}.tn-empty__title{font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #e5e7eb);line-height:1.4}.tn-empty__description{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;max-width:420px}.tn-empty__action{margin-top:8px}\n"] }]
6745
7198
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconLibrary", required: false }] }], iconSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSize", required: false }] }], actionText: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionText", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], onAction: [{ type: i0.Output, args: ["onAction"] }] } });
6746
7199
 
7200
+ class TnSpinnerComponent {
7201
+ mode = input('indeterminate', ...(ngDevMode ? [{ debugName: "mode" }] : []));
7202
+ value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
7203
+ diameter = input(40, ...(ngDevMode ? [{ debugName: "diameter" }] : []));
7204
+ strokeWidth = input(4, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : []));
7205
+ ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
7206
+ ariaLabelledby = input(null, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
7207
+ radius = computed(() => {
7208
+ return (this.diameter() - this.strokeWidth()) / 2;
7209
+ }, ...(ngDevMode ? [{ debugName: "radius" }] : []));
7210
+ circumference = computed(() => {
7211
+ return 2 * Math.PI * this.radius();
7212
+ }, ...(ngDevMode ? [{ debugName: "circumference" }] : []));
7213
+ strokeDasharray = computed(() => {
7214
+ return `${this.circumference()} ${this.circumference()}`;
7215
+ }, ...(ngDevMode ? [{ debugName: "strokeDasharray" }] : []));
7216
+ strokeDashoffset = computed(() => {
7217
+ if (this.mode() === 'indeterminate') {
7218
+ return 0;
7219
+ }
7220
+ const progress = Math.max(0, Math.min(100, this.value()));
7221
+ return this.circumference() - (progress / 100) * this.circumference();
7222
+ }, ...(ngDevMode ? [{ debugName: "strokeDashoffset" }] : []));
7223
+ viewBox = computed(() => {
7224
+ const size = this.diameter();
7225
+ return `0 0 ${size} ${size}`;
7226
+ }, ...(ngDevMode ? [{ debugName: "viewBox" }] : []));
7227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7228
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnSpinnerComponent, isStandalone: true, selector: "tn-spinner", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, diameter: { classPropertyName: "diameter", publicName: "diameter", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.tn-spinner-indeterminate": "mode() === \"indeterminate\"", "class.tn-spinner-determinate": "mode() === \"determinate\"", "attr.aria-valuenow": "mode() === \"determinate\" ? value() : null", "attr.aria-valuemin": "mode() === \"determinate\" ? 0 : null", "attr.aria-valuemax": "mode() === \"determinate\" ? 100 : null", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "ariaLabelledby() || null" }, classAttribute: "tn-spinner" }, ngImport: i0, template: "<svg\n class=\"tn-spinner-svg\"\n [attr.width]=\"diameter()\"\n [attr.height]=\"diameter()\"\n [attr.viewBox]=\"viewBox()\">\n <circle\n class=\"tn-spinner-circle\"\n fill=\"none\"\n [attr.cx]=\"diameter() / 2\"\n [attr.cy]=\"diameter() / 2\"\n [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeWidth()\"\n [attr.stroke-dasharray]=\"strokeDasharray()\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\" />\n</svg>", styles: [".tn-spinner{display:inline-block;vertical-align:middle}.tn-spinner-svg{animation:tn-spinner-rotate 2s linear infinite;transform-origin:center}.tn-spinner-circle{stroke:var(--tn-primary, #007bff);stroke-linecap:round;transition:stroke-dashoffset .35s cubic-bezier(.4,0,.2,1)}.tn-spinner-indeterminate .tn-spinner-circle{animation:tn-spinner-dash 1.4s ease-in-out infinite}.tn-spinner-determinate .tn-spinner-svg{animation:none;transform:rotate(-90deg)}@keyframes tn-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes tn-spinner-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:100,200;stroke-dashoffset:-15}to{stroke-dasharray:100,200;stroke-dashoffset:-125}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7229
+ }
7230
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, decorators: [{
7231
+ type: Component,
7232
+ args: [{ selector: 'tn-spinner', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
7233
+ 'class': 'tn-spinner',
7234
+ '[class.tn-spinner-indeterminate]': 'mode() === "indeterminate"',
7235
+ '[class.tn-spinner-determinate]': 'mode() === "determinate"',
7236
+ '[attr.aria-valuenow]': 'mode() === "determinate" ? value() : null',
7237
+ '[attr.aria-valuemin]': 'mode() === "determinate" ? 0 : null',
7238
+ '[attr.aria-valuemax]': 'mode() === "determinate" ? 100 : null',
7239
+ 'role': 'progressbar',
7240
+ '[attr.aria-label]': 'ariaLabel() || null',
7241
+ '[attr.aria-labelledby]': 'ariaLabelledby() || null'
7242
+ }, template: "<svg\n class=\"tn-spinner-svg\"\n [attr.width]=\"diameter()\"\n [attr.height]=\"diameter()\"\n [attr.viewBox]=\"viewBox()\">\n <circle\n class=\"tn-spinner-circle\"\n fill=\"none\"\n [attr.cx]=\"diameter() / 2\"\n [attr.cy]=\"diameter() / 2\"\n [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeWidth()\"\n [attr.stroke-dasharray]=\"strokeDasharray()\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\" />\n</svg>", styles: [".tn-spinner{display:inline-block;vertical-align:middle}.tn-spinner-svg{animation:tn-spinner-rotate 2s linear infinite;transform-origin:center}.tn-spinner-circle{stroke:var(--tn-primary, #007bff);stroke-linecap:round;transition:stroke-dashoffset .35s cubic-bezier(.4,0,.2,1)}.tn-spinner-indeterminate .tn-spinner-circle{animation:tn-spinner-dash 1.4s ease-in-out infinite}.tn-spinner-determinate .tn-spinner-svg{animation:none;transform:rotate(-90deg)}@keyframes tn-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes tn-spinner-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:100,200;stroke-dashoffset:-15}to{stroke-dasharray:100,200;stroke-dashoffset:-125}}\n"] }]
7243
+ }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], diameter: [{ type: i0.Input, args: [{ isSignal: true, alias: "diameter", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }] } });
7244
+
6747
7245
  class TnHeaderCellDefDirective {
6748
7246
  template = inject((TemplateRef));
6749
7247
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnHeaderCellDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -6814,14 +7312,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6814
7312
  }]
6815
7313
  }] });
6816
7314
 
6817
- const SORT_ICON_ASC = tnIconMarker('arrow_upward', 'material');
6818
- const SORT_ICON_DESC = tnIconMarker('arrow_downward', 'material');
6819
- const SORT_ICON_NONE = tnIconMarker('unfold_more', 'material');
6820
- const EXPAND_ICON_DOWN = tnIconMarker('keyboard_arrow_down', 'material');
6821
- const EXPAND_ICON_UP = tnIconMarker('keyboard_arrow_up', 'material');
7315
+ // Material icons render via the `material-icons` CSS font (see icon.component.ts),
7316
+ // so they don't need sprite scanning — the literal `mat-` prefix matches what
7317
+ // the runtime icon resolver would produce from a Material library name.
7318
+ const SORT_ICON_ASC = 'mat-arrow_upward';
7319
+ const SORT_ICON_DESC = 'mat-arrow_downward';
7320
+ const SORT_ICON_NONE = 'mat-unfold_more';
7321
+ const EXPAND_ICON_DOWN = 'mat-keyboard_arrow_down';
7322
+ const EXPAND_ICON_UP = 'mat-keyboard_arrow_up';
6822
7323
  /**
6823
- * Determines the animation duration for detail row expand/collapse.
6824
- * Respects prefers-reduced-motion by using 0ms when the user prefers reduced motion.
7324
+ * Animation duration for detail row expand/collapse.
7325
+ *
7326
+ * Evaluated once when the `@Component` decorator runs (at module load), so the
7327
+ * value is frozen for the lifetime of the app: if the user toggles their OS
7328
+ * "reduce motion" preference at runtime, this duration will NOT update for
7329
+ * already-loaded components. Angular animations don't expose a per-trigger
7330
+ * dynamic duration, so live updates would require switching from
7331
+ * `@detailExpand` to plain CSS transitions (already used elsewhere in the SCSS,
7332
+ * which respects the live preference via `@media (prefers-reduced-motion)`).
7333
+ * Acceptable tradeoff: the OS preference rarely flips mid-session, and the
7334
+ * surrounding CSS transitions continue to react live.
6825
7335
  */
6826
7336
  function getExpandDuration() {
6827
7337
  if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
@@ -6841,9 +7351,50 @@ class TnTableComponent {
6841
7351
  selectable = input(false, ...(ngDevMode ? [{ debugName: "selectable" }] : []));
6842
7352
  expandable = input(false, ...(ngDevMode ? [{ debugName: "expandable" }] : []));
6843
7353
  bordered = input(false, ...(ngDevMode ? [{ debugName: "bordered" }] : []));
7354
+ /**
7355
+ * Marks a single row as "active" — adds the `tn-table__row--active` class
7356
+ * and a left-side indicator bar. Set to `null` (default) to clear.
7357
+ *
7358
+ * **Matched by object identity (`===`)** against the row references in
7359
+ * `dataSource`. Pass the exact reference you got from the data source (e.g.
7360
+ * via the `rowClick` event or a lookup into `dataSource()`), not a
7361
+ * structurally-equal copy — `{ id: 1 } !== { id: 1 }` and the row will not
7362
+ * highlight. This differs from `tn-select`, which supports a `compareWith`
7363
+ * input for object values; the table intentionally does not, because the
7364
+ * common use case (clicking a row to mark it active) already gives the
7365
+ * caller the original reference. If you need structural equality, look up
7366
+ * the row by id in your data source before assigning here.
7367
+ */
7368
+ activeRow = input(null, ...(ngDevMode ? [{ debugName: "activeRow" }] : []));
7369
+ /**
7370
+ * Overrides the active-row background color. Accepts any CSS color value
7371
+ * (`#hex`, `rgb()`, `var(--token)`). Defaults to `--tn-bg3` when null.
7372
+ */
7373
+ activeBg = input(null, ...(ngDevMode ? [{ debugName: "activeBg" }] : []));
7374
+ /**
7375
+ * Overrides the left-side active-row indicator color. Defaults to
7376
+ * `--tn-primary` when null.
7377
+ */
7378
+ activeIndicator = input(null, ...(ngDevMode ? [{ debugName: "activeIndicator" }] : []));
7379
+ /**
7380
+ * When true, shows a spinner overlay over the table. Existing rows remain
7381
+ * visible (dimmed) so reloads don't cause layout jumps; if there are no rows
7382
+ * yet, the spinner replaces the empty state.
7383
+ */
7384
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
7385
+ /** Accessible label announced while loading. */
7386
+ loadingMessage = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingMessage" }] : []));
7387
+ /**
7388
+ * When true, rows become keyboard-focusable (tabindex=0) and clicking or
7389
+ * pressing Enter/Space emits `rowClick`. Use this for "click row to view
7390
+ * details" patterns. Independent of `selectable` (checkbox) and `expandable`.
7391
+ */
7392
+ clickable = input(false, ...(ngDevMode ? [{ debugName: "clickable" }] : []));
6844
7393
  // --- Outputs ---
6845
7394
  sortChange = output();
6846
7395
  selectionChange = output();
7396
+ /** Emits the row when a clickable row is activated (click or Enter/Space). */
7397
+ rowClick = output();
6847
7398
  // --- Content queries ---
6848
7399
  columnDefs = contentChildren(TnTableColumnDirective, ...(ngDevMode ? [{ debugName: "columnDefs" }] : []));
6849
7400
  detailRowDef = contentChild(TnDetailRowDefDirective, ...(ngDevMode ? [{ debugName: "detailRowDef" }] : []));
@@ -6985,6 +7536,27 @@ class TnTableComponent {
6985
7536
  isRowExpanded(row) {
6986
7537
  return this.expandedRows().has(row);
6987
7538
  }
7539
+ // --- Active row ---
7540
+ isRowActive(row) {
7541
+ const active = this.activeRow();
7542
+ return active !== null && active === row;
7543
+ }
7544
+ // --- Row click ---
7545
+ onRowClick(row) {
7546
+ if (!this.clickable()) {
7547
+ return;
7548
+ }
7549
+ this.rowClick.emit(row);
7550
+ }
7551
+ onRowKeydown(event, row) {
7552
+ if (!this.clickable()) {
7553
+ return;
7554
+ }
7555
+ if (event.key === 'Enter' || event.key === ' ') {
7556
+ event.preventDefault();
7557
+ this.rowClick.emit(row);
7558
+ }
7559
+ }
6988
7560
  // --- Selection methods ---
6989
7561
  toggleSelectAll() {
6990
7562
  if (this.isAllSelected()) {
@@ -7012,7 +7584,7 @@ class TnTableComponent {
7012
7584
  return row[column];
7013
7585
  }
7014
7586
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7015
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTableComponent, isStandalone: true, selector: "tn-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayedColumns: { classPropertyName: "displayedColumns", publicName: "displayedColumns", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, emptyIcon: { classPropertyName: "emptyIcon", publicName: "emptyIcon", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", selectionChange: "selectionChange" }, host: { properties: { "class.tn-table--bordered": "bordered()" }, classAttribute: "tn-table" }, queries: [{ propertyName: "columnDefs", predicate: TnTableColumnDirective, isSignal: true }, { propertyName: "detailRowDef", first: true, predicate: TnDetailRowDefDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: TnTestIdDirective, inputs: ["tnTestId", "testId"] }], ngImport: i0, template: "<table class=\"tn-table__table\">\n <!-- Header Row -->\n <thead class=\"tn-table__header\">\n <tr class=\"tn-table__header-row\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <th class=\"tn-table__header-cell tn-table__select-cell\"\n role=\"checkbox\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isAllSelected()\"\n (click)=\"toggleSelectAll()\"\n (keydown.enter)=\"toggleSelectAll()\"\n (keydown.space)=\"toggleSelectAll(); $event.preventDefault()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n label=\"Select all rows\"\n [hideLabel]=\"true\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isIndeterminate()\" />\n </th>\n } @else if (column === '__expand') {\n <th class=\"tn-table__header-cell tn-table__expand-cell\">\n <span class=\"cdk-visually-hidden\">Expand</span>\n </th>\n } @else {\n <th\n class=\"tn-table__header-cell\"\n [class.tn-table__header-cell--sortable]=\"getColumnDef(column)?.sortable()\"\n [class.tn-table__header-cell--sorted]=\"isSorted(column)\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.aria-sort]=\"\n isSorted(column)\n ? sortDirection() === 'asc' ? 'ascending' : 'descending'\n : null\n \"\n [attr.tabindex]=\"getColumnDef(column)?.sortable() ? 0 : null\"\n [attr.data-column]=\"column\"\n (click)=\"getColumnDef(column)?.sortable() && onSortClick(column)\"\n (keydown.enter)=\"onSortClick(column)\"\n (keydown.space)=\"onSortClick(column); $event.preventDefault()\">\n <span class=\"tn-table__sort-container\">\n <span class=\"tn-table__header-text\">\n @if (getColumnDef(column)?.headerTemplate(); as tmpl) {\n <ng-container [ngTemplateOutlet]=\"tmpl\" />\n } @else {\n {{ column }}\n }\n </span>\n @if (getColumnDef(column)?.sortable()) {\n <tn-icon\n class=\"tn-table__sort-icon\"\n size=\"sm\"\n [name]=\"getSortIcon(column)\" />\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n\n <!-- Data Rows -->\n <tbody class=\"tn-table__body\">\n @for (row of data(); track trackByFn()($index, row); let rowIdx = $index) {\n <tr\n class=\"tn-table__row\"\n [attr.data-row-index]=\"rowIdx\"\n [class.tn-table__row--expandable]=\"expandable()\"\n [class.tn-table__row--expanded]=\"isRowExpanded(row)\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <td class=\"tn-table__cell tn-table__select-cell\"\n (click)=\"toggleRowSelection(row); $event.stopPropagation()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n [label]=\"'Select row ' + (rowIdx + 1)\"\n [hideLabel]=\"true\"\n [checked]=\"isRowSelected(row)\" />\n </td>\n } @else if (column === '__expand') {\n <td class=\"tn-table__cell tn-table__expand-cell\"\n (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"tn-table__expand-button\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n [attr.aria-label]=\"isRowExpanded(row) ? 'Collapse row' : 'Expand row'\"\n (click)=\"toggleRowExpansion(row)\">\n <tn-icon\n class=\"tn-table__expand-icon\"\n [name]=\"getExpandIcon(row)\" />\n </button>\n </td>\n } @else {\n <td\n class=\"tn-table__cell\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.data-column]=\"column\">\n @if (getColumnDef(column)?.cellTemplate(); as tmpl) {\n <ng-container\n [ngTemplateOutlet]=\"tmpl\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\" />\n } @else {\n <span>{{ getCellValue(row, column) }}</span>\n }\n </td>\n }\n }\n </tr>\n\n <!-- Detail / Expanded Row -->\n @if (expandable() && detailRowDef() && isRowExpanded(row)) {\n <tr class=\"tn-table__detail-row\" [@detailExpand]=\"'expanded'\">\n <td\n class=\"tn-table__detail-cell\"\n [attr.colspan]=\"effectiveDisplayedColumns().length\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowDef()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\" />\n </td>\n </tr>\n }\n }\n </tbody>\n</table>\n\n@if (data().length === 0) {\n <tn-empty\n size=\"compact\"\n [title]=\"emptyMessage()\"\n [icon]=\"emptyIcon()\" />\n}\n", styles: [":host{display:block}:host(.tn-table--bordered){border:1px solid var(--tn-lines);border-radius:4px}.tn-table{display:block;width:100%;overflow-x:auto}.tn-table__table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--tn-bg2);border-radius:4px;overflow:hidden}.tn-table__header{background-color:var(--tn-topbar);color:var(--tn-topbar-txt)}.tn-table__header-row{height:56px}.tn-table__header-cell{padding:0 16px;text-align:left;font-weight:600;font-size:14px;border-bottom:1px solid var(--tn-lines);white-space:nowrap;vertical-align:middle}.tn-table__header-cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.tn-table__header-cell--sortable:hover{background-color:var(--tn-alt-bg1)}.tn-table__sort-container{display:inline-flex;align-items:center;gap:4px}.tn-table__sort-icon{opacity:0;transition:opacity .2s ease}.tn-table__header-cell--sortable:hover .tn-table__sort-icon{opacity:.5}.tn-table__header-cell--sorted .tn-table__sort-icon{opacity:1}.tn-table__body{background-color:var(--tn-bg2)}.tn-table__row{height:48px;transition:background-color .2s ease}.tn-table__row:hover{background-color:var(--tn-alt-bg1)}.tn-table__row:not(:last-child){border-bottom:1px solid var(--tn-lines)}.tn-table__row--expanded{background-color:var(--tn-alt-bg1)}.tn-table__cell{padding:0 16px;font-size:14px;color:var(--tn-fg1);vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-table__select-cell{width:48px;padding:0 8px 0 16px;cursor:pointer}.tn-table__checkbox{pointer-events:none}.tn-table__expand-cell{width:48px;padding:0 16px 0 8px;text-align:center}.tn-table__expand-button{background:none;border:none;padding:4px;cursor:pointer;border-radius:4px;display:inline-flex;align-items:center;color:var(--tn-fg2)}.tn-table__expand-button:hover{background-color:var(--tn-alt-bg1)}.tn-table__expand-button:focus-visible{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-table__expand-icon{transition:transform .2s ease}.tn-table__detail-row{background-color:var(--tn-alt-bg1);border-bottom:1px solid var(--tn-lines)}.tn-table__detail-cell{padding:16px}.tn-table--dense .tn-table__header-row{height:40px}.tn-table--dense .tn-table__row{height:32px}.tn-table--dense .tn-table__header-cell,.tn-table--dense .tn-table__cell{padding:0 12px;font-size:13px}@media(max-width:768px){.tn-table__table{font-size:12px}.tn-table__header-cell,.tn-table__cell{padding:0 8px}}@media(prefers-reduced-motion:reduce){.tn-table__sort-icon,.tn-table__expand-icon,.tn-table__row{transition:none}.tn-table__detail-row{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }, { kind: "component", type: TnEmptyComponent, selector: "tn-empty", inputs: ["title", "description", "icon", "iconLibrary", "iconSize", "actionText", "bordered", "size"], outputs: ["onAction"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], animations: [
7587
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTableComponent, isStandalone: true, selector: "tn-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayedColumns: { classPropertyName: "displayedColumns", publicName: "displayedColumns", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, emptyIcon: { classPropertyName: "emptyIcon", publicName: "emptyIcon", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, activeRow: { classPropertyName: "activeRow", publicName: "activeRow", isSignal: true, isRequired: false, transformFunction: null }, activeBg: { classPropertyName: "activeBg", publicName: "activeBg", isSignal: true, isRequired: false, transformFunction: null }, activeIndicator: { classPropertyName: "activeIndicator", publicName: "activeIndicator", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingMessage: { classPropertyName: "loadingMessage", publicName: "loadingMessage", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", selectionChange: "selectionChange", rowClick: "rowClick" }, host: { properties: { "class.tn-table--bordered": "bordered()", "class.tn-table--loading": "loading()", "style.--tn-table-active-bg": "activeBg()", "style.--tn-table-active-indicator": "activeIndicator()" }, classAttribute: "tn-table" }, queries: [{ propertyName: "columnDefs", predicate: TnTableColumnDirective, isSignal: true }, { propertyName: "detailRowDef", first: true, predicate: TnDetailRowDefDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: TnTestIdDirective, inputs: ["tnTestId", "testId"] }], ngImport: i0, template: "<table class=\"tn-table__table\">\n <!-- Header Row -->\n <thead class=\"tn-table__header\">\n <tr class=\"tn-table__header-row\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <th class=\"tn-table__header-cell tn-table__select-cell\"\n role=\"checkbox\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isAllSelected()\"\n (click)=\"toggleSelectAll()\"\n (keydown.enter)=\"toggleSelectAll()\"\n (keydown.space)=\"toggleSelectAll(); $event.preventDefault()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n label=\"Select all rows\"\n [hideLabel]=\"true\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isIndeterminate()\" />\n </th>\n } @else if (column === '__expand') {\n <th class=\"tn-table__header-cell tn-table__expand-cell\">\n <span class=\"cdk-visually-hidden\">Expand</span>\n </th>\n } @else {\n <th\n class=\"tn-table__header-cell\"\n [class.tn-table__header-cell--sortable]=\"getColumnDef(column)?.sortable()\"\n [class.tn-table__header-cell--sorted]=\"isSorted(column)\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.aria-sort]=\"\n isSorted(column)\n ? sortDirection() === 'asc' ? 'ascending' : 'descending'\n : null\n \"\n [attr.tabindex]=\"getColumnDef(column)?.sortable() ? 0 : null\"\n [attr.data-column]=\"column\"\n (click)=\"getColumnDef(column)?.sortable() && onSortClick(column)\"\n (keydown.enter)=\"onSortClick(column)\"\n (keydown.space)=\"onSortClick(column); $event.preventDefault()\">\n <span class=\"tn-table__sort-container\">\n <span class=\"tn-table__header-text\">\n @if (getColumnDef(column)?.headerTemplate(); as tmpl) {\n <ng-container [ngTemplateOutlet]=\"tmpl\" />\n } @else {\n {{ column }}\n }\n </span>\n @if (getColumnDef(column)?.sortable()) {\n <tn-icon\n class=\"tn-table__sort-icon\"\n size=\"sm\"\n [name]=\"getSortIcon(column)\" />\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n\n <!-- Data Rows -->\n <tbody class=\"tn-table__body\">\n @for (row of data(); track trackByFn()($index, row); let rowIdx = $index) {\n <tr\n class=\"tn-table__row\"\n [attr.data-row-index]=\"rowIdx\"\n [class.tn-table__row--expandable]=\"expandable()\"\n [class.tn-table__row--expanded]=\"isRowExpanded(row)\"\n [class.tn-table__row--active]=\"isRowActive(row)\"\n [class.tn-table__row--clickable]=\"clickable()\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n [attr.aria-selected]=\"clickable() ? isRowActive(row) : null\"\n (click)=\"onRowClick(row)\"\n (keydown)=\"onRowKeydown($event, row)\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <td class=\"tn-table__cell tn-table__select-cell\"\n (click)=\"toggleRowSelection(row); $event.stopPropagation()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n [label]=\"'Select row ' + (rowIdx + 1)\"\n [hideLabel]=\"true\"\n [checked]=\"isRowSelected(row)\" />\n </td>\n } @else if (column === '__expand') {\n <td class=\"tn-table__cell tn-table__expand-cell\"\n (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"tn-table__expand-button\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n [attr.aria-label]=\"isRowExpanded(row) ? 'Collapse row' : 'Expand row'\"\n (click)=\"toggleRowExpansion(row)\">\n <tn-icon\n class=\"tn-table__expand-icon\"\n [name]=\"getExpandIcon(row)\" />\n </button>\n </td>\n } @else {\n <td\n class=\"tn-table__cell\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.data-column]=\"column\">\n <div class=\"tn-table__cell-content\">\n @if (getColumnDef(column)?.cellTemplate(); as tmpl) {\n <ng-container\n [ngTemplateOutlet]=\"tmpl\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\" />\n } @else {\n <span>{{ getCellValue(row, column) }}</span>\n }\n </div>\n </td>\n }\n }\n </tr>\n\n <!-- Detail / Expanded Row -->\n @if (expandable() && detailRowDef() && isRowExpanded(row)) {\n <tr class=\"tn-table__detail-row\" [@detailExpand]=\"'expanded'\">\n <td\n class=\"tn-table__detail-cell\"\n [attr.colspan]=\"effectiveDisplayedColumns().length\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowDef()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\" />\n </td>\n </tr>\n }\n }\n </tbody>\n</table>\n\n@if (data().length === 0 && !loading()) {\n <tn-empty\n size=\"compact\"\n [title]=\"emptyMessage()\"\n [icon]=\"emptyIcon()\" />\n}\n\n@if (loading()) {\n <div\n class=\"tn-table__loading-overlay\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"loadingMessage()\">\n <tn-spinner [ariaLabel]=\"loadingMessage()\" />\n </div>\n}\n", styles: [":host{display:block;position:relative}:host(.tn-table--bordered){border:1px solid var(--tn-lines);border-radius:4px}.tn-table{display:block;width:100%;overflow-x:auto}.tn-table__table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--tn-bg2);border-radius:4px;overflow:hidden}.tn-table__header{background-color:var(--tn-topbar);color:var(--tn-topbar-txt)}.tn-table__header-row{height:56px}.tn-table__header-cell{padding:0 16px;text-align:left;font-weight:600;font-size:14px;border-bottom:1px solid var(--tn-lines);white-space:nowrap;vertical-align:middle}.tn-table__header-cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.tn-table__header-cell--sortable:hover{background-color:color-mix(in srgb,var(--tn-topbar) 85%,var(--tn-topbar-txt))}.tn-table__sort-container{display:inline-flex;align-items:center;gap:4px}.tn-table__sort-icon{opacity:0;transition:opacity .2s ease}.tn-table__header-cell--sortable:hover .tn-table__sort-icon{opacity:.5}.tn-table__header-cell--sorted .tn-table__sort-icon{opacity:1}.tn-table__body{background-color:var(--tn-bg2)}.tn-table__row{height:48px;transition:background-color .2s ease}.tn-table__row:hover{background-color:var(--tn-alt-bg1)}.tn-table__row:not(:last-child){border-bottom:1px solid var(--tn-lines)}.tn-table__row--expanded{background-color:var(--tn-alt-bg1)}.tn-table__row--active{background-color:var(--tn-table-active-bg, var(--tn-bg3, #333333))}.tn-table__row--active>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-table-active-indicator, var(--tn-primary, #007db3))}.tn-table__row--clickable{cursor:pointer}.tn-table__row--clickable:focus-visible{outline:none}.tn-table__row--clickable:focus-visible>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-primary),inset 0 0 0 2px var(--tn-primary)}.tn-table__row--active.tn-table__row--clickable:focus-visible>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-table-active-indicator, var(--tn-primary, #007db3)),inset 0 0 0 2px var(--tn-primary)}.tn-table__cell{padding:0 16px;font-size:14px;color:var(--tn-fg1);vertical-align:middle;overflow:hidden}.tn-table__cell-content{display:flex;align-items:center;min-height:24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-table__cell-content>*{min-width:0}.tn-table__select-cell{width:48px;padding:0 8px 0 16px;cursor:pointer}.tn-table__checkbox{pointer-events:none}.tn-table__expand-cell{width:48px;padding:0 16px 0 8px;text-align:center}.tn-table__expand-button{background:none;border:none;padding:4px;cursor:pointer;border-radius:4px;display:inline-flex;align-items:center;color:var(--tn-fg2)}.tn-table__expand-button:hover{background-color:var(--tn-alt-bg1)}.tn-table__expand-button:focus-visible{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-table__expand-icon{transition:transform .2s ease}.tn-table__detail-row{background-color:var(--tn-alt-bg1);border-bottom:1px solid var(--tn-lines)}.tn-table__detail-cell{padding:16px}.tn-table__loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:color-mix(in srgb,var(--tn-bg2, #282828) 65%,transparent);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);pointer-events:all;z-index:1;border-radius:4px}.tn-table--loading .tn-table__table{pointer-events:none;-webkit-user-select:none;user-select:none}.tn-table--dense .tn-table__header-row{height:40px}.tn-table--dense .tn-table__row{height:32px}.tn-table--dense .tn-table__header-cell,.tn-table--dense .tn-table__cell{padding:0 12px;font-size:13px}@media(max-width:768px){.tn-table__table{font-size:12px}.tn-table__header-cell,.tn-table__cell{padding:0 8px}}@media(prefers-reduced-motion:reduce){.tn-table__sort-icon,.tn-table__expand-icon,.tn-table__row{transition:none}.tn-table__detail-row{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }, { kind: "component", type: TnEmptyComponent, selector: "tn-empty", inputs: ["title", "description", "icon", "iconLibrary", "iconSize", "actionText", "bordered", "size"], outputs: ["onAction"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "component", type: TnSpinnerComponent, selector: "tn-spinner", inputs: ["mode", "value", "diameter", "strokeWidth", "ariaLabel", "ariaLabelledby"] }], animations: [
7016
7588
  trigger('detailExpand', [
7017
7589
  state('collapsed,void', style({ height: '0px', minHeight: '0', overflow: 'hidden' })),
7018
7590
  state('expanded', style({ height: '*' })),
@@ -7022,7 +7594,7 @@ class TnTableComponent {
7022
7594
  }
7023
7595
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTableComponent, decorators: [{
7024
7596
  type: Component,
7025
- args: [{ selector: 'tn-table', standalone: true, imports: [CommonModule, TnCheckboxComponent, TnEmptyComponent, TnIconComponent], animations: [
7597
+ args: [{ selector: 'tn-table', standalone: true, imports: [CommonModule, TnCheckboxComponent, TnEmptyComponent, TnIconComponent, TnSpinnerComponent], animations: [
7026
7598
  trigger('detailExpand', [
7027
7599
  state('collapsed,void', style({ height: '0px', minHeight: '0', overflow: 'hidden' })),
7028
7600
  state('expanded', style({ height: '*' })),
@@ -7031,8 +7603,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
7031
7603
  ], hostDirectives: [{ directive: TnTestIdDirective, inputs: ['tnTestId: testId'] }], host: {
7032
7604
  class: 'tn-table',
7033
7605
  '[class.tn-table--bordered]': 'bordered()',
7034
- }, template: "<table class=\"tn-table__table\">\n <!-- Header Row -->\n <thead class=\"tn-table__header\">\n <tr class=\"tn-table__header-row\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <th class=\"tn-table__header-cell tn-table__select-cell\"\n role=\"checkbox\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isAllSelected()\"\n (click)=\"toggleSelectAll()\"\n (keydown.enter)=\"toggleSelectAll()\"\n (keydown.space)=\"toggleSelectAll(); $event.preventDefault()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n label=\"Select all rows\"\n [hideLabel]=\"true\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isIndeterminate()\" />\n </th>\n } @else if (column === '__expand') {\n <th class=\"tn-table__header-cell tn-table__expand-cell\">\n <span class=\"cdk-visually-hidden\">Expand</span>\n </th>\n } @else {\n <th\n class=\"tn-table__header-cell\"\n [class.tn-table__header-cell--sortable]=\"getColumnDef(column)?.sortable()\"\n [class.tn-table__header-cell--sorted]=\"isSorted(column)\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.aria-sort]=\"\n isSorted(column)\n ? sortDirection() === 'asc' ? 'ascending' : 'descending'\n : null\n \"\n [attr.tabindex]=\"getColumnDef(column)?.sortable() ? 0 : null\"\n [attr.data-column]=\"column\"\n (click)=\"getColumnDef(column)?.sortable() && onSortClick(column)\"\n (keydown.enter)=\"onSortClick(column)\"\n (keydown.space)=\"onSortClick(column); $event.preventDefault()\">\n <span class=\"tn-table__sort-container\">\n <span class=\"tn-table__header-text\">\n @if (getColumnDef(column)?.headerTemplate(); as tmpl) {\n <ng-container [ngTemplateOutlet]=\"tmpl\" />\n } @else {\n {{ column }}\n }\n </span>\n @if (getColumnDef(column)?.sortable()) {\n <tn-icon\n class=\"tn-table__sort-icon\"\n size=\"sm\"\n [name]=\"getSortIcon(column)\" />\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n\n <!-- Data Rows -->\n <tbody class=\"tn-table__body\">\n @for (row of data(); track trackByFn()($index, row); let rowIdx = $index) {\n <tr\n class=\"tn-table__row\"\n [attr.data-row-index]=\"rowIdx\"\n [class.tn-table__row--expandable]=\"expandable()\"\n [class.tn-table__row--expanded]=\"isRowExpanded(row)\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <td class=\"tn-table__cell tn-table__select-cell\"\n (click)=\"toggleRowSelection(row); $event.stopPropagation()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n [label]=\"'Select row ' + (rowIdx + 1)\"\n [hideLabel]=\"true\"\n [checked]=\"isRowSelected(row)\" />\n </td>\n } @else if (column === '__expand') {\n <td class=\"tn-table__cell tn-table__expand-cell\"\n (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"tn-table__expand-button\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n [attr.aria-label]=\"isRowExpanded(row) ? 'Collapse row' : 'Expand row'\"\n (click)=\"toggleRowExpansion(row)\">\n <tn-icon\n class=\"tn-table__expand-icon\"\n [name]=\"getExpandIcon(row)\" />\n </button>\n </td>\n } @else {\n <td\n class=\"tn-table__cell\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.data-column]=\"column\">\n @if (getColumnDef(column)?.cellTemplate(); as tmpl) {\n <ng-container\n [ngTemplateOutlet]=\"tmpl\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\" />\n } @else {\n <span>{{ getCellValue(row, column) }}</span>\n }\n </td>\n }\n }\n </tr>\n\n <!-- Detail / Expanded Row -->\n @if (expandable() && detailRowDef() && isRowExpanded(row)) {\n <tr class=\"tn-table__detail-row\" [@detailExpand]=\"'expanded'\">\n <td\n class=\"tn-table__detail-cell\"\n [attr.colspan]=\"effectiveDisplayedColumns().length\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowDef()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\" />\n </td>\n </tr>\n }\n }\n </tbody>\n</table>\n\n@if (data().length === 0) {\n <tn-empty\n size=\"compact\"\n [title]=\"emptyMessage()\"\n [icon]=\"emptyIcon()\" />\n}\n", styles: [":host{display:block}:host(.tn-table--bordered){border:1px solid var(--tn-lines);border-radius:4px}.tn-table{display:block;width:100%;overflow-x:auto}.tn-table__table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--tn-bg2);border-radius:4px;overflow:hidden}.tn-table__header{background-color:var(--tn-topbar);color:var(--tn-topbar-txt)}.tn-table__header-row{height:56px}.tn-table__header-cell{padding:0 16px;text-align:left;font-weight:600;font-size:14px;border-bottom:1px solid var(--tn-lines);white-space:nowrap;vertical-align:middle}.tn-table__header-cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.tn-table__header-cell--sortable:hover{background-color:var(--tn-alt-bg1)}.tn-table__sort-container{display:inline-flex;align-items:center;gap:4px}.tn-table__sort-icon{opacity:0;transition:opacity .2s ease}.tn-table__header-cell--sortable:hover .tn-table__sort-icon{opacity:.5}.tn-table__header-cell--sorted .tn-table__sort-icon{opacity:1}.tn-table__body{background-color:var(--tn-bg2)}.tn-table__row{height:48px;transition:background-color .2s ease}.tn-table__row:hover{background-color:var(--tn-alt-bg1)}.tn-table__row:not(:last-child){border-bottom:1px solid var(--tn-lines)}.tn-table__row--expanded{background-color:var(--tn-alt-bg1)}.tn-table__cell{padding:0 16px;font-size:14px;color:var(--tn-fg1);vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-table__select-cell{width:48px;padding:0 8px 0 16px;cursor:pointer}.tn-table__checkbox{pointer-events:none}.tn-table__expand-cell{width:48px;padding:0 16px 0 8px;text-align:center}.tn-table__expand-button{background:none;border:none;padding:4px;cursor:pointer;border-radius:4px;display:inline-flex;align-items:center;color:var(--tn-fg2)}.tn-table__expand-button:hover{background-color:var(--tn-alt-bg1)}.tn-table__expand-button:focus-visible{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-table__expand-icon{transition:transform .2s ease}.tn-table__detail-row{background-color:var(--tn-alt-bg1);border-bottom:1px solid var(--tn-lines)}.tn-table__detail-cell{padding:16px}.tn-table--dense .tn-table__header-row{height:40px}.tn-table--dense .tn-table__row{height:32px}.tn-table--dense .tn-table__header-cell,.tn-table--dense .tn-table__cell{padding:0 12px;font-size:13px}@media(max-width:768px){.tn-table__table{font-size:12px}.tn-table__header-cell,.tn-table__cell{padding:0 8px}}@media(prefers-reduced-motion:reduce){.tn-table__sort-icon,.tn-table__expand-icon,.tn-table__row{transition:none}.tn-table__detail-row{animation:none}}\n"] }]
7035
- }], ctorParameters: () => [], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayedColumns", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], emptyIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyIcon", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], columnDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTableColumnDirective), { isSignal: true }] }], detailRowDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnDetailRowDefDirective), { isSignal: true }] }] } });
7606
+ '[class.tn-table--loading]': 'loading()',
7607
+ '[style.--tn-table-active-bg]': 'activeBg()',
7608
+ '[style.--tn-table-active-indicator]': 'activeIndicator()',
7609
+ }, template: "<table class=\"tn-table__table\">\n <!-- Header Row -->\n <thead class=\"tn-table__header\">\n <tr class=\"tn-table__header-row\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <th class=\"tn-table__header-cell tn-table__select-cell\"\n role=\"checkbox\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isAllSelected()\"\n (click)=\"toggleSelectAll()\"\n (keydown.enter)=\"toggleSelectAll()\"\n (keydown.space)=\"toggleSelectAll(); $event.preventDefault()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n label=\"Select all rows\"\n [hideLabel]=\"true\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isIndeterminate()\" />\n </th>\n } @else if (column === '__expand') {\n <th class=\"tn-table__header-cell tn-table__expand-cell\">\n <span class=\"cdk-visually-hidden\">Expand</span>\n </th>\n } @else {\n <th\n class=\"tn-table__header-cell\"\n [class.tn-table__header-cell--sortable]=\"getColumnDef(column)?.sortable()\"\n [class.tn-table__header-cell--sorted]=\"isSorted(column)\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.aria-sort]=\"\n isSorted(column)\n ? sortDirection() === 'asc' ? 'ascending' : 'descending'\n : null\n \"\n [attr.tabindex]=\"getColumnDef(column)?.sortable() ? 0 : null\"\n [attr.data-column]=\"column\"\n (click)=\"getColumnDef(column)?.sortable() && onSortClick(column)\"\n (keydown.enter)=\"onSortClick(column)\"\n (keydown.space)=\"onSortClick(column); $event.preventDefault()\">\n <span class=\"tn-table__sort-container\">\n <span class=\"tn-table__header-text\">\n @if (getColumnDef(column)?.headerTemplate(); as tmpl) {\n <ng-container [ngTemplateOutlet]=\"tmpl\" />\n } @else {\n {{ column }}\n }\n </span>\n @if (getColumnDef(column)?.sortable()) {\n <tn-icon\n class=\"tn-table__sort-icon\"\n size=\"sm\"\n [name]=\"getSortIcon(column)\" />\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n\n <!-- Data Rows -->\n <tbody class=\"tn-table__body\">\n @for (row of data(); track trackByFn()($index, row); let rowIdx = $index) {\n <tr\n class=\"tn-table__row\"\n [attr.data-row-index]=\"rowIdx\"\n [class.tn-table__row--expandable]=\"expandable()\"\n [class.tn-table__row--expanded]=\"isRowExpanded(row)\"\n [class.tn-table__row--active]=\"isRowActive(row)\"\n [class.tn-table__row--clickable]=\"clickable()\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n [attr.aria-selected]=\"clickable() ? isRowActive(row) : null\"\n (click)=\"onRowClick(row)\"\n (keydown)=\"onRowKeydown($event, row)\">\n @for (column of effectiveDisplayedColumns(); track $index) {\n @if (column === '__select') {\n <td class=\"tn-table__cell tn-table__select-cell\"\n (click)=\"toggleRowSelection(row); $event.stopPropagation()\">\n <tn-checkbox\n class=\"tn-table__checkbox\"\n [label]=\"'Select row ' + (rowIdx + 1)\"\n [hideLabel]=\"true\"\n [checked]=\"isRowSelected(row)\" />\n </td>\n } @else if (column === '__expand') {\n <td class=\"tn-table__cell tn-table__expand-cell\"\n (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"tn-table__expand-button\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n [attr.aria-label]=\"isRowExpanded(row) ? 'Collapse row' : 'Expand row'\"\n (click)=\"toggleRowExpansion(row)\">\n <tn-icon\n class=\"tn-table__expand-icon\"\n [name]=\"getExpandIcon(row)\" />\n </button>\n </td>\n } @else {\n <td\n class=\"tn-table__cell\"\n [style.width]=\"getColumnDef(column)?.width()\"\n [attr.data-column]=\"column\">\n <div class=\"tn-table__cell-content\">\n @if (getColumnDef(column)?.cellTemplate(); as tmpl) {\n <ng-container\n [ngTemplateOutlet]=\"tmpl\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\" />\n } @else {\n <span>{{ getCellValue(row, column) }}</span>\n }\n </div>\n </td>\n }\n }\n </tr>\n\n <!-- Detail / Expanded Row -->\n @if (expandable() && detailRowDef() && isRowExpanded(row)) {\n <tr class=\"tn-table__detail-row\" [@detailExpand]=\"'expanded'\">\n <td\n class=\"tn-table__detail-cell\"\n [attr.colspan]=\"effectiveDisplayedColumns().length\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowDef()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\" />\n </td>\n </tr>\n }\n }\n </tbody>\n</table>\n\n@if (data().length === 0 && !loading()) {\n <tn-empty\n size=\"compact\"\n [title]=\"emptyMessage()\"\n [icon]=\"emptyIcon()\" />\n}\n\n@if (loading()) {\n <div\n class=\"tn-table__loading-overlay\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"loadingMessage()\">\n <tn-spinner [ariaLabel]=\"loadingMessage()\" />\n </div>\n}\n", styles: [":host{display:block;position:relative}:host(.tn-table--bordered){border:1px solid var(--tn-lines);border-radius:4px}.tn-table{display:block;width:100%;overflow-x:auto}.tn-table__table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--tn-bg2);border-radius:4px;overflow:hidden}.tn-table__header{background-color:var(--tn-topbar);color:var(--tn-topbar-txt)}.tn-table__header-row{height:56px}.tn-table__header-cell{padding:0 16px;text-align:left;font-weight:600;font-size:14px;border-bottom:1px solid var(--tn-lines);white-space:nowrap;vertical-align:middle}.tn-table__header-cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.tn-table__header-cell--sortable:hover{background-color:color-mix(in srgb,var(--tn-topbar) 85%,var(--tn-topbar-txt))}.tn-table__sort-container{display:inline-flex;align-items:center;gap:4px}.tn-table__sort-icon{opacity:0;transition:opacity .2s ease}.tn-table__header-cell--sortable:hover .tn-table__sort-icon{opacity:.5}.tn-table__header-cell--sorted .tn-table__sort-icon{opacity:1}.tn-table__body{background-color:var(--tn-bg2)}.tn-table__row{height:48px;transition:background-color .2s ease}.tn-table__row:hover{background-color:var(--tn-alt-bg1)}.tn-table__row:not(:last-child){border-bottom:1px solid var(--tn-lines)}.tn-table__row--expanded{background-color:var(--tn-alt-bg1)}.tn-table__row--active{background-color:var(--tn-table-active-bg, var(--tn-bg3, #333333))}.tn-table__row--active>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-table-active-indicator, var(--tn-primary, #007db3))}.tn-table__row--clickable{cursor:pointer}.tn-table__row--clickable:focus-visible{outline:none}.tn-table__row--clickable:focus-visible>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-primary),inset 0 0 0 2px var(--tn-primary)}.tn-table__row--active.tn-table__row--clickable:focus-visible>.tn-table__cell:first-child{box-shadow:inset 3px 0 0 0 var(--tn-table-active-indicator, var(--tn-primary, #007db3)),inset 0 0 0 2px var(--tn-primary)}.tn-table__cell{padding:0 16px;font-size:14px;color:var(--tn-fg1);vertical-align:middle;overflow:hidden}.tn-table__cell-content{display:flex;align-items:center;min-height:24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-table__cell-content>*{min-width:0}.tn-table__select-cell{width:48px;padding:0 8px 0 16px;cursor:pointer}.tn-table__checkbox{pointer-events:none}.tn-table__expand-cell{width:48px;padding:0 16px 0 8px;text-align:center}.tn-table__expand-button{background:none;border:none;padding:4px;cursor:pointer;border-radius:4px;display:inline-flex;align-items:center;color:var(--tn-fg2)}.tn-table__expand-button:hover{background-color:var(--tn-alt-bg1)}.tn-table__expand-button:focus-visible{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-table__expand-icon{transition:transform .2s ease}.tn-table__detail-row{background-color:var(--tn-alt-bg1);border-bottom:1px solid var(--tn-lines)}.tn-table__detail-cell{padding:16px}.tn-table__loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:color-mix(in srgb,var(--tn-bg2, #282828) 65%,transparent);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);pointer-events:all;z-index:1;border-radius:4px}.tn-table--loading .tn-table__table{pointer-events:none;-webkit-user-select:none;user-select:none}.tn-table--dense .tn-table__header-row{height:40px}.tn-table--dense .tn-table__row{height:32px}.tn-table--dense .tn-table__header-cell,.tn-table--dense .tn-table__cell{padding:0 12px;font-size:13px}@media(max-width:768px){.tn-table__table{font-size:12px}.tn-table__header-cell,.tn-table__cell{padding:0 8px}}@media(prefers-reduced-motion:reduce){.tn-table__sort-icon,.tn-table__expand-icon,.tn-table__row{transition:none}.tn-table__detail-row{animation:none}}\n"] }]
7610
+ }], ctorParameters: () => [], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayedColumns", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], emptyIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyIcon", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], activeRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeRow", required: false }] }], activeBg: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeBg", required: false }] }], activeIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndicator", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingMessage", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], columnDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTableColumnDirective), { isSignal: true }] }], detailRowDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnDetailRowDefDirective), { isSignal: true }] }] } });
7036
7611
 
7037
7612
  /**
7038
7613
  * Harness for interacting with `tn-table` in tests.
@@ -7223,6 +7798,79 @@ class TnTableHarness extends ComponentHarness {
7223
7798
  const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7224
7799
  return row.hasClass('tn-table__row--expanded');
7225
7800
  }
7801
+ // --- Clickable rows ---
7802
+ /**
7803
+ * Clicks a row (for tables with `clickable` enabled).
7804
+ *
7805
+ * @param rowIndex Zero-based index of the data row.
7806
+ */
7807
+ async clickRow(rowIndex) {
7808
+ await this.assertRowExists(rowIndex);
7809
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7810
+ await row.click();
7811
+ }
7812
+ /**
7813
+ * Sends a keyboard event to a row (Enter/Space activate clickable rows).
7814
+ *
7815
+ * @param rowIndex Zero-based index of the data row.
7816
+ * @param key Which key to press — Enter or Space.
7817
+ */
7818
+ async pressKeyOnRow(rowIndex, key) {
7819
+ await this.assertRowExists(rowIndex);
7820
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7821
+ await row.focus();
7822
+ if (key === 'enter') {
7823
+ await row.sendKeys(TestKey.ENTER);
7824
+ }
7825
+ else {
7826
+ await row.sendKeys(' ');
7827
+ }
7828
+ }
7829
+ /**
7830
+ * Checks if a row is keyboard-focusable (tabindex=0).
7831
+ *
7832
+ * @param rowIndex Zero-based index of the data row.
7833
+ */
7834
+ async isRowFocusable(rowIndex) {
7835
+ await this.assertRowExists(rowIndex);
7836
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7837
+ return (await row.getAttribute('tabindex')) === '0';
7838
+ }
7839
+ // --- Loading ---
7840
+ /**
7841
+ * Checks whether the table is currently in the loading state.
7842
+ *
7843
+ * @returns Promise resolving to true if the loading overlay is visible.
7844
+ */
7845
+ async isLoading() {
7846
+ const overlay = await this.locatorForOptional('.tn-table__loading-overlay')();
7847
+ return overlay !== null;
7848
+ }
7849
+ // --- Active row ---
7850
+ /**
7851
+ * Checks if a data row is currently marked active.
7852
+ *
7853
+ * @param rowIndex Zero-based index of the data row.
7854
+ * @returns Promise resolving to true if the row has the active class.
7855
+ */
7856
+ async isRowActive(rowIndex) {
7857
+ await this.assertRowExists(rowIndex);
7858
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7859
+ return row.hasClass('tn-table__row--active');
7860
+ }
7861
+ /**
7862
+ * Gets the index of the currently active row, or null if none is active.
7863
+ *
7864
+ * @returns Promise resolving to the active row index or null.
7865
+ */
7866
+ async getActiveRowIndex() {
7867
+ const row = await this.locatorForOptional('.tn-table__row--active')();
7868
+ if (!row) {
7869
+ return null;
7870
+ }
7871
+ const attr = await row.getAttribute('data-row-index');
7872
+ return attr === null ? null : Number(attr);
7873
+ }
7226
7874
  /**
7227
7875
  * Gets the text content of an expanded detail row.
7228
7876
  *
@@ -7519,7 +8167,7 @@ class TnTablePagerComponent {
7519
8167
  provider.setPagination(pagination);
7520
8168
  }
7521
8169
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7522
- 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 });
7523
8171
  }
7524
8172
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, decorators: [{
7525
8173
  type: Component,
@@ -7795,7 +8443,7 @@ class TnNestedTreeNodeComponent extends CdkNestedTreeNode {
7795
8443
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnNestedTreeNodeComponent, isStandalone: true, selector: "tn-nested-tree-node", host: { attributes: { "role": "treeitem" }, properties: { "attr.aria-level": "level + 1", "attr.aria-expanded": "isExpandable ? isExpanded : null" }, classAttribute: "tn-nested-tree-node-wrapper" }, providers: [
7796
8444
  { provide: CdkNestedTreeNode, useExisting: TnNestedTreeNodeComponent },
7797
8445
  { provide: CdkTreeNode, useExisting: TnNestedTreeNodeComponent }
7798
- ], exportAs: ["tnNestedTreeNode"], usesInheritance: true, ngImport: i0, template: "<div class=\"tn-nested-tree-node__content\">\n <!-- Toggle button for expandable nodes (provided by component) -->\n @if (isExpandable) {\n <button\n class=\"tn-nested-tree-node__toggle\"\n cdkTreeNodeToggle\n type=\"button\"\n [class.tn-nested-tree-node__toggle--expanded]=\"isExpanded\"\n [attr.aria-label]=\"'Toggle node'\">\n <tn-icon\n library=\"mdi\"\n size=\"sm\"\n style=\"transition: transform 0.2s ease;\"\n [name]=\"isExpanded ? 'chevron-down' : 'chevron-right'\" />\n </button>\n }\n\n <!-- Spacer for non-expandable nodes to maintain alignment -->\n @if (!isExpandable) {\n <div class=\"tn-nested-tree-node__spacer\"></div>\n }\n\n <!-- Consumer content -->\n <ng-content />\n</div>\n\n<!-- Children container -->\n@if (isExpandable) {\n <div class=\"tn-nested-tree-node-container\" role=\"group\" [class.tn-tree-invisible]=\"!isExpanded\">\n <ng-content select=\"[slot=children]\" />\n </div>\n}", styles: [".tn-nested-tree-node-wrapper{display:block;width:100%}.tn-nested-tree-node{display:block;width:100%;font-family:var(--tn-font-family-body);font-size:.875rem;line-height:1.4;color:var(--tn-fg1)}.tn-nested-tree-node--expandable .tn-nested-tree-node__content{cursor:pointer}.tn-nested-tree-node__content{display:flex;align-items:center;gap:8px;min-height:48px;padding:12px 16px;border-bottom:1px solid var(--tn-lines);transition:background-color .2s ease}.tn-nested-tree-node__content:hover{background-color:var(--tn-alt-bg2)}.tn-nested-tree-node__content:focus-within{background-color:var(--tn-alt-bg2);outline:2px solid var(--tn-primary);outline-offset:-2px}.tn-tree-invisible{display:none}.tn-nested-tree-node__toggle{display:flex;align-items:center;justify-content:center;width:24px;height:24px;margin-right:8px;padding:0;border:none;background:transparent;border-radius:4px;cursor:pointer;color:var(--tn-fg2);transition:background-color .2s ease,color .2s ease}.tn-nested-tree-node__toggle:hover{background-color:var(--tn-bg3);color:var(--tn-fg1)}.tn-nested-tree-node__toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-nested-tree-node__toggle svg{transition:transform .2s ease}.tn-nested-tree-node__toggle--expanded svg{transform:rotate(90deg)}.tn-nested-tree-node__spacer{width:24px;height:24px;flex-shrink:0}.tn-nested-tree-node__text{flex:1;display:flex;align-items:center;gap:8px;min-width:0;color:var(--tn-fg1)}div.tn-nested-tree-node-container{padding-left:40px}@media(prefers-reduced-motion:reduce){.tn-nested-tree-node__toggle svg,.tn-nested-tree-node__content,.tn-nested-tree-node__children{transition:none}}@media(prefers-contrast:high){.tn-nested-tree-node__content{border:1px solid transparent}.tn-nested-tree-node__content:hover,.tn-nested-tree-node__content:focus-within{border-color:var(--tn-fg1)}.tn-nested-tree-node__toggle{border:1px solid var(--tn-fg2)}.tn-nested-tree-node__toggle:hover,.tn-nested-tree-node__toggle:focus{border-color:var(--tn-fg1)}}\n"], dependencies: [{ kind: "ngmodule", type: CdkTreeModule }, { kind: "directive", type: i2$1.CdkTreeNodeToggle, selector: "[cdkTreeNodeToggle]", inputs: ["cdkTreeNodeToggleRecursive"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8446
+ ], exportAs: ["tnNestedTreeNode"], usesInheritance: true, ngImport: i0, template: "<div class=\"tn-nested-tree-node__content\">\n <!-- Toggle button for expandable nodes (provided by component) -->\n @if (isExpandable) {\n <button\n class=\"tn-nested-tree-node__toggle\"\n cdkTreeNodeToggle\n type=\"button\"\n [class.tn-nested-tree-node__toggle--expanded]=\"isExpanded\"\n [attr.aria-label]=\"'Toggle node'\">\n <tn-icon\n library=\"mdi\"\n size=\"sm\"\n style=\"transition: transform 0.2s ease;\"\n [name]=\"isExpanded ? 'chevron-down' : 'chevron-right'\" />\n </button>\n }\n\n <!-- Spacer for non-expandable nodes to maintain alignment -->\n @if (!isExpandable) {\n <div class=\"tn-nested-tree-node__spacer\"></div>\n }\n\n <!-- Consumer content -->\n <ng-content />\n</div>\n\n<!-- Children container -->\n@if (isExpandable) {\n <div class=\"tn-nested-tree-node-container\" role=\"group\" [class.tn-tree-invisible]=\"!isExpanded\">\n <ng-content select=\"[slot=children]\" />\n </div>\n}", styles: [".tn-nested-tree-node-wrapper{display:block;width:100%}.tn-nested-tree-node{display:block;width:100%;font-family:var(--tn-font-family-body);font-size:1rem;line-height:1.4;color:var(--tn-fg1)}.tn-nested-tree-node--expandable .tn-nested-tree-node__content{cursor:pointer}.tn-nested-tree-node__content{display:flex;align-items:center;gap:8px;min-height:48px;padding:12px 16px;border-bottom:1px solid var(--tn-lines);transition:background-color .2s ease}.tn-nested-tree-node__content:hover{background-color:var(--tn-alt-bg2)}.tn-nested-tree-node__content:focus-within{background-color:var(--tn-alt-bg2);outline:2px solid var(--tn-primary);outline-offset:-2px}.tn-tree-invisible{display:none}.tn-nested-tree-node__toggle{display:flex;align-items:center;justify-content:center;width:24px;height:24px;margin-right:8px;padding:0;border:none;background:transparent;border-radius:4px;cursor:pointer;color:var(--tn-fg2);transition:background-color .2s ease,color .2s ease}.tn-nested-tree-node__toggle:hover{background-color:var(--tn-bg3);color:var(--tn-fg1)}.tn-nested-tree-node__toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-nested-tree-node__toggle svg{transition:transform .2s ease}.tn-nested-tree-node__toggle--expanded svg{transform:rotate(90deg)}.tn-nested-tree-node__spacer{width:24px;height:24px;flex-shrink:0}.tn-nested-tree-node__text{flex:1;display:flex;align-items:center;gap:8px;min-width:0;color:var(--tn-fg1)}div.tn-nested-tree-node-container{padding-left:40px}@media(prefers-reduced-motion:reduce){.tn-nested-tree-node__toggle svg,.tn-nested-tree-node__content,.tn-nested-tree-node__children{transition:none}}@media(prefers-contrast:high){.tn-nested-tree-node__content{border:1px solid transparent}.tn-nested-tree-node__content:hover,.tn-nested-tree-node__content:focus-within{border-color:var(--tn-fg1)}.tn-nested-tree-node__toggle{border:1px solid var(--tn-fg2)}.tn-nested-tree-node__toggle:hover,.tn-nested-tree-node__toggle:focus{border-color:var(--tn-fg1)}}\n"], dependencies: [{ kind: "ngmodule", type: CdkTreeModule }, { kind: "directive", type: i2$1.CdkTreeNodeToggle, selector: "[cdkTreeNodeToggle]", inputs: ["cdkTreeNodeToggleRecursive"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7799
8447
  }
7800
8448
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnNestedTreeNodeComponent, decorators: [{
7801
8449
  type: Component,
@@ -7807,7 +8455,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
7807
8455
  '[attr.aria-level]': 'level + 1',
7808
8456
  '[attr.aria-expanded]': 'isExpandable ? isExpanded : null',
7809
8457
  'role': 'treeitem'
7810
- }, encapsulation: ViewEncapsulation.Emulated, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-nested-tree-node__content\">\n <!-- Toggle button for expandable nodes (provided by component) -->\n @if (isExpandable) {\n <button\n class=\"tn-nested-tree-node__toggle\"\n cdkTreeNodeToggle\n type=\"button\"\n [class.tn-nested-tree-node__toggle--expanded]=\"isExpanded\"\n [attr.aria-label]=\"'Toggle node'\">\n <tn-icon\n library=\"mdi\"\n size=\"sm\"\n style=\"transition: transform 0.2s ease;\"\n [name]=\"isExpanded ? 'chevron-down' : 'chevron-right'\" />\n </button>\n }\n\n <!-- Spacer for non-expandable nodes to maintain alignment -->\n @if (!isExpandable) {\n <div class=\"tn-nested-tree-node__spacer\"></div>\n }\n\n <!-- Consumer content -->\n <ng-content />\n</div>\n\n<!-- Children container -->\n@if (isExpandable) {\n <div class=\"tn-nested-tree-node-container\" role=\"group\" [class.tn-tree-invisible]=\"!isExpanded\">\n <ng-content select=\"[slot=children]\" />\n </div>\n}", styles: [".tn-nested-tree-node-wrapper{display:block;width:100%}.tn-nested-tree-node{display:block;width:100%;font-family:var(--tn-font-family-body);font-size:.875rem;line-height:1.4;color:var(--tn-fg1)}.tn-nested-tree-node--expandable .tn-nested-tree-node__content{cursor:pointer}.tn-nested-tree-node__content{display:flex;align-items:center;gap:8px;min-height:48px;padding:12px 16px;border-bottom:1px solid var(--tn-lines);transition:background-color .2s ease}.tn-nested-tree-node__content:hover{background-color:var(--tn-alt-bg2)}.tn-nested-tree-node__content:focus-within{background-color:var(--tn-alt-bg2);outline:2px solid var(--tn-primary);outline-offset:-2px}.tn-tree-invisible{display:none}.tn-nested-tree-node__toggle{display:flex;align-items:center;justify-content:center;width:24px;height:24px;margin-right:8px;padding:0;border:none;background:transparent;border-radius:4px;cursor:pointer;color:var(--tn-fg2);transition:background-color .2s ease,color .2s ease}.tn-nested-tree-node__toggle:hover{background-color:var(--tn-bg3);color:var(--tn-fg1)}.tn-nested-tree-node__toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-nested-tree-node__toggle svg{transition:transform .2s ease}.tn-nested-tree-node__toggle--expanded svg{transform:rotate(90deg)}.tn-nested-tree-node__spacer{width:24px;height:24px;flex-shrink:0}.tn-nested-tree-node__text{flex:1;display:flex;align-items:center;gap:8px;min-width:0;color:var(--tn-fg1)}div.tn-nested-tree-node-container{padding-left:40px}@media(prefers-reduced-motion:reduce){.tn-nested-tree-node__toggle svg,.tn-nested-tree-node__content,.tn-nested-tree-node__children{transition:none}}@media(prefers-contrast:high){.tn-nested-tree-node__content{border:1px solid transparent}.tn-nested-tree-node__content:hover,.tn-nested-tree-node__content:focus-within{border-color:var(--tn-fg1)}.tn-nested-tree-node__toggle{border:1px solid var(--tn-fg2)}.tn-nested-tree-node__toggle:hover,.tn-nested-tree-node__toggle:focus{border-color:var(--tn-fg1)}}\n"] }]
8458
+ }, encapsulation: ViewEncapsulation.Emulated, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-nested-tree-node__content\">\n <!-- Toggle button for expandable nodes (provided by component) -->\n @if (isExpandable) {\n <button\n class=\"tn-nested-tree-node__toggle\"\n cdkTreeNodeToggle\n type=\"button\"\n [class.tn-nested-tree-node__toggle--expanded]=\"isExpanded\"\n [attr.aria-label]=\"'Toggle node'\">\n <tn-icon\n library=\"mdi\"\n size=\"sm\"\n style=\"transition: transform 0.2s ease;\"\n [name]=\"isExpanded ? 'chevron-down' : 'chevron-right'\" />\n </button>\n }\n\n <!-- Spacer for non-expandable nodes to maintain alignment -->\n @if (!isExpandable) {\n <div class=\"tn-nested-tree-node__spacer\"></div>\n }\n\n <!-- Consumer content -->\n <ng-content />\n</div>\n\n<!-- Children container -->\n@if (isExpandable) {\n <div class=\"tn-nested-tree-node-container\" role=\"group\" [class.tn-tree-invisible]=\"!isExpanded\">\n <ng-content select=\"[slot=children]\" />\n </div>\n}", styles: [".tn-nested-tree-node-wrapper{display:block;width:100%}.tn-nested-tree-node{display:block;width:100%;font-family:var(--tn-font-family-body);font-size:1rem;line-height:1.4;color:var(--tn-fg1)}.tn-nested-tree-node--expandable .tn-nested-tree-node__content{cursor:pointer}.tn-nested-tree-node__content{display:flex;align-items:center;gap:8px;min-height:48px;padding:12px 16px;border-bottom:1px solid var(--tn-lines);transition:background-color .2s ease}.tn-nested-tree-node__content:hover{background-color:var(--tn-alt-bg2)}.tn-nested-tree-node__content:focus-within{background-color:var(--tn-alt-bg2);outline:2px solid var(--tn-primary);outline-offset:-2px}.tn-tree-invisible{display:none}.tn-nested-tree-node__toggle{display:flex;align-items:center;justify-content:center;width:24px;height:24px;margin-right:8px;padding:0;border:none;background:transparent;border-radius:4px;cursor:pointer;color:var(--tn-fg2);transition:background-color .2s ease,color .2s ease}.tn-nested-tree-node__toggle:hover{background-color:var(--tn-bg3);color:var(--tn-fg1)}.tn-nested-tree-node__toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-nested-tree-node__toggle svg{transition:transform .2s ease}.tn-nested-tree-node__toggle--expanded svg{transform:rotate(90deg)}.tn-nested-tree-node__spacer{width:24px;height:24px;flex-shrink:0}.tn-nested-tree-node__text{flex:1;display:flex;align-items:center;gap:8px;min-width:0;color:var(--tn-fg1)}div.tn-nested-tree-node-container{padding-left:40px}@media(prefers-reduced-motion:reduce){.tn-nested-tree-node__toggle svg,.tn-nested-tree-node__content,.tn-nested-tree-node__children{transition:none}}@media(prefers-contrast:high){.tn-nested-tree-node__content{border:1px solid transparent}.tn-nested-tree-node__content:hover,.tn-nested-tree-node__content:focus-within{border-color:var(--tn-fg1)}.tn-nested-tree-node__toggle{border:1px solid var(--tn-fg2)}.tn-nested-tree-node__toggle:hover,.tn-nested-tree-node__toggle:focus{border-color:var(--tn-fg1)}}\n"] }]
7811
8459
  }], ctorParameters: () => [] });
7812
8460
 
7813
8461
  var ModifierKeys;
@@ -8341,70 +8989,25 @@ class TruncatePathPipe {
8341
8989
  // For subdirectories, show ".." (parent) and current directory
8342
8990
  const segments = [];
8343
8991
  // Calculate parent path
8344
- const lastSlashIndex = path.lastIndexOf('/');
8345
- const parentPath = lastSlashIndex > 0 ? path.substring(0, lastSlashIndex) : '/mnt';
8346
- // Get current directory name
8347
- const currentDirName = path.substring(lastSlashIndex + 1);
8348
- // Add parent navigation (..) and current directory
8349
- segments.push({ name: '..', path: parentPath });
8350
- segments.push({ name: currentDirName, path: path });
8351
- return segments;
8352
- }
8353
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
8354
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, isStandalone: true, name: "tnTruncatePath" });
8355
- }
8356
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, decorators: [{
8357
- type: Pipe,
8358
- args: [{
8359
- name: 'tnTruncatePath',
8360
- standalone: true,
8361
- }]
8362
- }] });
8363
-
8364
- class TnSpinnerComponent {
8365
- mode = input('indeterminate', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8366
- value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
8367
- diameter = input(40, ...(ngDevMode ? [{ debugName: "diameter" }] : []));
8368
- strokeWidth = input(4, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : []));
8369
- ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
8370
- ariaLabelledby = input(null, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
8371
- radius = computed(() => {
8372
- return (this.diameter() - this.strokeWidth()) / 2;
8373
- }, ...(ngDevMode ? [{ debugName: "radius" }] : []));
8374
- circumference = computed(() => {
8375
- return 2 * Math.PI * this.radius();
8376
- }, ...(ngDevMode ? [{ debugName: "circumference" }] : []));
8377
- strokeDasharray = computed(() => {
8378
- return `${this.circumference()} ${this.circumference()}`;
8379
- }, ...(ngDevMode ? [{ debugName: "strokeDasharray" }] : []));
8380
- strokeDashoffset = computed(() => {
8381
- if (this.mode() === 'indeterminate') {
8382
- return 0;
8383
- }
8384
- const progress = Math.max(0, Math.min(100, this.value()));
8385
- return this.circumference() - (progress / 100) * this.circumference();
8386
- }, ...(ngDevMode ? [{ debugName: "strokeDashoffset" }] : []));
8387
- viewBox = computed(() => {
8388
- const size = this.diameter();
8389
- return `0 0 ${size} ${size}`;
8390
- }, ...(ngDevMode ? [{ debugName: "viewBox" }] : []));
8391
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8392
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnSpinnerComponent, isStandalone: true, selector: "tn-spinner", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, diameter: { classPropertyName: "diameter", publicName: "diameter", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.tn-spinner-indeterminate": "mode() === \"indeterminate\"", "class.tn-spinner-determinate": "mode() === \"determinate\"", "attr.aria-valuenow": "mode() === \"determinate\" ? value() : null", "attr.aria-valuemin": "mode() === \"determinate\" ? 0 : null", "attr.aria-valuemax": "mode() === \"determinate\" ? 100 : null", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "ariaLabelledby() || null" }, classAttribute: "tn-spinner" }, ngImport: i0, template: "<svg\n class=\"tn-spinner-svg\"\n [attr.width]=\"diameter()\"\n [attr.height]=\"diameter()\"\n [attr.viewBox]=\"viewBox()\">\n <circle\n class=\"tn-spinner-circle\"\n fill=\"none\"\n [attr.cx]=\"diameter() / 2\"\n [attr.cy]=\"diameter() / 2\"\n [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeWidth()\"\n [attr.stroke-dasharray]=\"strokeDasharray()\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\" />\n</svg>", styles: [".tn-spinner{display:inline-block;vertical-align:middle}.tn-spinner-svg{animation:tn-spinner-rotate 2s linear infinite;transform-origin:center}.tn-spinner-circle{stroke:var(--tn-primary, #007bff);stroke-linecap:round;transition:stroke-dashoffset .35s cubic-bezier(.4,0,.2,1)}.tn-spinner-indeterminate .tn-spinner-circle{animation:tn-spinner-dash 1.4s ease-in-out infinite}.tn-spinner-determinate .tn-spinner-svg{animation:none;transform:rotate(-90deg)}@keyframes tn-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes tn-spinner-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:100,200;stroke-dashoffset:-15}to{stroke-dasharray:100,200;stroke-dashoffset:-125}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8393
- }
8394
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, decorators: [{
8395
- type: Component,
8396
- args: [{ selector: 'tn-spinner', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
8397
- 'class': 'tn-spinner',
8398
- '[class.tn-spinner-indeterminate]': 'mode() === "indeterminate"',
8399
- '[class.tn-spinner-determinate]': 'mode() === "determinate"',
8400
- '[attr.aria-valuenow]': 'mode() === "determinate" ? value() : null',
8401
- '[attr.aria-valuemin]': 'mode() === "determinate" ? 0 : null',
8402
- '[attr.aria-valuemax]': 'mode() === "determinate" ? 100 : null',
8403
- 'role': 'progressbar',
8404
- '[attr.aria-label]': 'ariaLabel() || null',
8405
- '[attr.aria-labelledby]': 'ariaLabelledby() || null'
8406
- }, template: "<svg\n class=\"tn-spinner-svg\"\n [attr.width]=\"diameter()\"\n [attr.height]=\"diameter()\"\n [attr.viewBox]=\"viewBox()\">\n <circle\n class=\"tn-spinner-circle\"\n fill=\"none\"\n [attr.cx]=\"diameter() / 2\"\n [attr.cy]=\"diameter() / 2\"\n [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeWidth()\"\n [attr.stroke-dasharray]=\"strokeDasharray()\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\" />\n</svg>", styles: [".tn-spinner{display:inline-block;vertical-align:middle}.tn-spinner-svg{animation:tn-spinner-rotate 2s linear infinite;transform-origin:center}.tn-spinner-circle{stroke:var(--tn-primary, #007bff);stroke-linecap:round;transition:stroke-dashoffset .35s cubic-bezier(.4,0,.2,1)}.tn-spinner-indeterminate .tn-spinner-circle{animation:tn-spinner-dash 1.4s ease-in-out infinite}.tn-spinner-determinate .tn-spinner-svg{animation:none;transform:rotate(-90deg)}@keyframes tn-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes tn-spinner-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:100,200;stroke-dashoffset:-15}to{stroke-dasharray:100,200;stroke-dashoffset:-125}}\n"] }]
8407
- }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], diameter: [{ type: i0.Input, args: [{ isSignal: true, alias: "diameter", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }] } });
8992
+ const lastSlashIndex = path.lastIndexOf('/');
8993
+ const parentPath = lastSlashIndex > 0 ? path.substring(0, lastSlashIndex) : '/mnt';
8994
+ // Get current directory name
8995
+ const currentDirName = path.substring(lastSlashIndex + 1);
8996
+ // Add parent navigation (..) and current directory
8997
+ segments.push({ name: '..', path: parentPath });
8998
+ segments.push({ name: currentDirName, path: path });
8999
+ return segments;
9000
+ }
9001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
9002
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, isStandalone: true, name: "tnTruncatePath" });
9003
+ }
9004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TruncatePathPipe, decorators: [{
9005
+ type: Pipe,
9006
+ args: [{
9007
+ name: 'tnTruncatePath',
9008
+ standalone: true,
9009
+ }]
9010
+ }] });
8408
9011
 
8409
9012
  class TnBrandedSpinnerComponent {
8410
9013
  ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -8811,11 +9414,11 @@ class TnCalendarHeaderComponent {
8811
9414
  this.nextClicked.emit();
8812
9415
  }
8813
9416
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCalendarHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8814
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnCalendarHeaderComponent, isStandalone: true, selector: "tn-calendar-header", inputs: { currentDate: { classPropertyName: "currentDate", publicName: "currentDate", isSignal: true, isRequired: false, transformFunction: null }, currentView: { classPropertyName: "currentView", publicName: "currentView", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { monthSelected: "monthSelected", yearSelected: "yearSelected", viewChanged: "viewChanged", previousClicked: "previousClicked", nextClicked: "nextClicked" }, ngImport: i0, template: "<div class=\"tn-calendar-header\">\n <div class=\"tn-calendar-controls\">\n <!-- Period label (visually hidden for screen readers) -->\n <span aria-live=\"polite\" class=\"cdk-visually-hidden\" [id]=\"periodLabelId\">\n {{ periodLabel() }}\n </span>\n\n <!-- Period button (month/year selector) -->\n <button\n type=\"button\"\n class=\"tn-calendar-period-button\"\n [attr.aria-label]=\"'Choose month and year'\"\n [attr.aria-describedby]=\"periodLabelId\"\n (click)=\"toggleView()\">\n <span [attr.aria-hidden]=\"true\">{{ periodLabel() }}</span>\n <svg viewBox=\"0 0 10 5\" focusable=\"false\" aria-hidden=\"true\" class=\"tn-calendar-arrow\">\n <polygon points=\"0,0 5,5 10,0\" />\n </svg>\n </button>\n\n <!-- Spacer -->\n <div class=\"tn-calendar-spacer\"></div>\n\n <!-- Previous button -->\n <button\n type=\"button\"\n class=\"tn-calendar-previous-button\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"onPreviousClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n\n <!-- Next button -->\n <button\n type=\"button\"\n class=\"tn-calendar-next-button\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"onNextClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n </div>\n</div>\n", styles: [".tn-calendar-header{display:flex;padding:16px}.tn-calendar-controls{display:flex;align-items:center;width:100%}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-calendar-period-button{background:none;border:none;font-weight:600;font-size:16px;color:var(--tn-fg1, #333);padding:8px 12px;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:4px;transition:background-color .2s ease}.tn-calendar-period-button:hover{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-period-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-arrow{width:10px;height:5px;fill:currentColor}.tn-calendar-spacer{flex:1}.tn-calendar-previous-button,.tn-calendar-next-button{background:none;border:none;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--tn-fg1, #333);transition:background-color .2s ease}.tn-calendar-previous-button svg,.tn-calendar-next-button svg{width:24px;height:24px;fill:currentColor}.tn-calendar-previous-button:hover:not(:disabled),.tn-calendar-next-button:hover:not(:disabled){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:focus,.tn-calendar-next-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:disabled,.tn-calendar-next-button:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:not-allowed}\n"] });
9417
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: TnCalendarHeaderComponent, isStandalone: true, selector: "tn-calendar-header", inputs: { currentDate: { classPropertyName: "currentDate", publicName: "currentDate", isSignal: true, isRequired: false, transformFunction: null }, currentView: { classPropertyName: "currentView", publicName: "currentView", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { monthSelected: "monthSelected", yearSelected: "yearSelected", viewChanged: "viewChanged", previousClicked: "previousClicked", nextClicked: "nextClicked" }, ngImport: i0, template: "<div class=\"tn-calendar-header\">\n <div class=\"tn-calendar-controls\">\n <!-- Period label (visually hidden for screen readers) -->\n <span aria-live=\"polite\" class=\"cdk-visually-hidden\" [id]=\"periodLabelId\">\n {{ periodLabel() }}\n </span>\n\n <!-- Period button (month/year selector) -->\n <button\n type=\"button\"\n class=\"tn-calendar-period-button\"\n [attr.aria-label]=\"'Choose month and year'\"\n [attr.aria-describedby]=\"periodLabelId\"\n (click)=\"toggleView()\">\n <span [attr.aria-hidden]=\"true\">{{ periodLabel() }}</span>\n <svg viewBox=\"0 0 10 5\" focusable=\"false\" aria-hidden=\"true\" class=\"tn-calendar-arrow\">\n <polygon points=\"0,0 5,5 10,0\" />\n </svg>\n </button>\n\n <!-- Spacer -->\n <div class=\"tn-calendar-spacer\"></div>\n\n <!-- Previous button -->\n <button\n type=\"button\"\n class=\"tn-calendar-previous-button\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"onPreviousClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n\n <!-- Next button -->\n <button\n type=\"button\"\n class=\"tn-calendar-next-button\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"onNextClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n </div>\n</div>\n", styles: [".tn-calendar-header{display:flex;padding:16px}.tn-calendar-controls{display:flex;align-items:center;width:100%}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-calendar-period-button{background:none;border:none;font-weight:600;font-size:1rem;color:var(--tn-fg1, #333);padding:8px 12px;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:4px;transition:background-color .2s ease}.tn-calendar-period-button:hover{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-period-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-arrow{width:10px;height:5px;fill:currentColor}.tn-calendar-spacer{flex:1}.tn-calendar-previous-button,.tn-calendar-next-button{background:none;border:none;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--tn-fg1, #333);transition:background-color .2s ease}.tn-calendar-previous-button svg,.tn-calendar-next-button svg{width:24px;height:24px;fill:currentColor}.tn-calendar-previous-button:hover:not(:disabled),.tn-calendar-next-button:hover:not(:disabled){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:focus,.tn-calendar-next-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:disabled,.tn-calendar-next-button:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:not-allowed}\n"] });
8815
9418
  }
8816
9419
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCalendarHeaderComponent, decorators: [{
8817
9420
  type: Component,
8818
- args: [{ selector: 'tn-calendar-header', standalone: true, imports: [], template: "<div class=\"tn-calendar-header\">\n <div class=\"tn-calendar-controls\">\n <!-- Period label (visually hidden for screen readers) -->\n <span aria-live=\"polite\" class=\"cdk-visually-hidden\" [id]=\"periodLabelId\">\n {{ periodLabel() }}\n </span>\n\n <!-- Period button (month/year selector) -->\n <button\n type=\"button\"\n class=\"tn-calendar-period-button\"\n [attr.aria-label]=\"'Choose month and year'\"\n [attr.aria-describedby]=\"periodLabelId\"\n (click)=\"toggleView()\">\n <span [attr.aria-hidden]=\"true\">{{ periodLabel() }}</span>\n <svg viewBox=\"0 0 10 5\" focusable=\"false\" aria-hidden=\"true\" class=\"tn-calendar-arrow\">\n <polygon points=\"0,0 5,5 10,0\" />\n </svg>\n </button>\n\n <!-- Spacer -->\n <div class=\"tn-calendar-spacer\"></div>\n\n <!-- Previous button -->\n <button\n type=\"button\"\n class=\"tn-calendar-previous-button\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"onPreviousClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n\n <!-- Next button -->\n <button\n type=\"button\"\n class=\"tn-calendar-next-button\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"onNextClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n </div>\n</div>\n", styles: [".tn-calendar-header{display:flex;padding:16px}.tn-calendar-controls{display:flex;align-items:center;width:100%}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-calendar-period-button{background:none;border:none;font-weight:600;font-size:16px;color:var(--tn-fg1, #333);padding:8px 12px;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:4px;transition:background-color .2s ease}.tn-calendar-period-button:hover{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-period-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-arrow{width:10px;height:5px;fill:currentColor}.tn-calendar-spacer{flex:1}.tn-calendar-previous-button,.tn-calendar-next-button{background:none;border:none;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--tn-fg1, #333);transition:background-color .2s ease}.tn-calendar-previous-button svg,.tn-calendar-next-button svg{width:24px;height:24px;fill:currentColor}.tn-calendar-previous-button:hover:not(:disabled),.tn-calendar-next-button:hover:not(:disabled){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:focus,.tn-calendar-next-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:disabled,.tn-calendar-next-button:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:not-allowed}\n"] }]
9421
+ args: [{ selector: 'tn-calendar-header', standalone: true, imports: [], template: "<div class=\"tn-calendar-header\">\n <div class=\"tn-calendar-controls\">\n <!-- Period label (visually hidden for screen readers) -->\n <span aria-live=\"polite\" class=\"cdk-visually-hidden\" [id]=\"periodLabelId\">\n {{ periodLabel() }}\n </span>\n\n <!-- Period button (month/year selector) -->\n <button\n type=\"button\"\n class=\"tn-calendar-period-button\"\n [attr.aria-label]=\"'Choose month and year'\"\n [attr.aria-describedby]=\"periodLabelId\"\n (click)=\"toggleView()\">\n <span [attr.aria-hidden]=\"true\">{{ periodLabel() }}</span>\n <svg viewBox=\"0 0 10 5\" focusable=\"false\" aria-hidden=\"true\" class=\"tn-calendar-arrow\">\n <polygon points=\"0,0 5,5 10,0\" />\n </svg>\n </button>\n\n <!-- Spacer -->\n <div class=\"tn-calendar-spacer\"></div>\n\n <!-- Previous button -->\n <button\n type=\"button\"\n class=\"tn-calendar-previous-button\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"onPreviousClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n\n <!-- Next button -->\n <button\n type=\"button\"\n class=\"tn-calendar-next-button\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"onNextClick()\">\n <svg viewBox=\"0 0 24 24\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n </div>\n</div>\n", styles: [".tn-calendar-header{display:flex;padding:16px}.tn-calendar-controls{display:flex;align-items:center;width:100%}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-calendar-period-button{background:none;border:none;font-weight:600;font-size:1rem;color:var(--tn-fg1, #333);padding:8px 12px;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:4px;transition:background-color .2s ease}.tn-calendar-period-button:hover{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-period-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-arrow{width:10px;height:5px;fill:currentColor}.tn-calendar-spacer{flex:1}.tn-calendar-previous-button,.tn-calendar-next-button{background:none;border:none;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--tn-fg1, #333);transition:background-color .2s ease}.tn-calendar-previous-button svg,.tn-calendar-next-button svg{width:24px;height:24px;fill:currentColor}.tn-calendar-previous-button:hover:not(:disabled),.tn-calendar-next-button:hover:not(:disabled){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:focus,.tn-calendar-next-button:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-previous-button:disabled,.tn-calendar-next-button:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:not-allowed}\n"] }]
8819
9422
  }], propDecorators: { currentDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentDate", required: false }] }], currentView: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentView", required: false }] }], monthSelected: [{ type: i0.Output, args: ["monthSelected"] }], yearSelected: [{ type: i0.Output, args: ["yearSelected"] }], viewChanged: [{ type: i0.Output, args: ["viewChanged"] }], previousClicked: [{ type: i0.Output, args: ["previousClicked"] }], nextClicked: [{ type: i0.Output, args: ["nextClicked"] }] } });
8820
9423
 
8821
9424
  class TnMonthViewComponent {
@@ -8981,11 +9584,11 @@ class TnMonthViewComponent {
8981
9584
  }
8982
9585
  }
8983
9586
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMonthViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8984
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMonthViewComponent, isStandalone: true, selector: "tn-month-view", inputs: { activeDate: { classPropertyName: "activeDate", publicName: "activeDate", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, rangeMode: { classPropertyName: "rangeMode", publicName: "rangeMode", isSignal: true, isRequired: false, transformFunction: null }, selectedRange: { classPropertyName: "selectedRange", publicName: "selectedRange", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedChange: "selectedChange", activeDateChange: "activeDateChange" }, ngImport: i0, template: "<table role=\"grid\" class=\"tn-calendar-table\">\n <!-- Table header with day names -->\n <thead class=\"tn-calendar-table-header\">\n <tr>\n @for (day of weekdays; track day) {\n <th scope=\"col\">\n <span class=\"cdk-visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.short }}</span>\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table body with calendar cells -->\n <tbody class=\"tn-calendar-body\">\n <!-- Calendar rows -->\n @for (row of calendarRows(); track trackByRow($index); let rowIndex = $index) {\n <tr role=\"row\">\n @for (cell of row; track trackByDate($index, cell); let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"tn-calendar-body-cell-container\"\n [attr.data-tn-row]=\"rowIndex\"\n [attr.data-tn-col]=\"colIndex\">\n @if (cell.value > 0) {\n <button\n type=\"button\"\n class=\"tn-calendar-body-cell\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-active]=\"cell.selected\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\"\n [disabled]=\"!cell.enabled\"\n [attr.tabindex]=\"cell.selected ? 0 : -1\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-pressed]=\"cell.selected\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onCellClicked(cell)\">\n <span class=\"tn-calendar-body-cell-content tn-focus-indicator\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\">\n {{ cell.value }}\n </span>\n <span aria-hidden=\"true\" class=\"tn-calendar-body-cell-preview\"></span>\n </button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: [":host{--calendar-cell-size: 48px;--calendar-header-height: 40px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}.tn-calendar-table{width:calc(7 * var(--calendar-cell-size));border-spacing:0;border-collapse:separate}.tn-calendar-table-header th{text-align:center;height:var(--calendar-header-height);padding:8px 0;font-size:var(--calendar-header-font-size);font-weight:500;color:var(--tn-fg2, #666)}.tn-calendar-table-header-divider{height:1px;border:0}.tn-calendar-body tr{border:0}.tn-calendar-body-cell-container{position:relative;border:0;outline:0;height:var(--calendar-cell-size);width:14.2857142857%}.tn-calendar-body-cell{position:absolute;inset:0;margin:auto;background:transparent;border:0;outline:0;cursor:pointer;color:var(--tn-fg1, #333);width:var(--calendar-cell-size);height:var(--calendar-cell-size)}.tn-calendar-body-cell:not(:disabled):hover:not(.tn-calendar-body-selected):not(.tn-calendar-body-range-start):not(.tn-calendar-body-range-end):not(.tn-calendar-body-in-range){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-calendar-body-cell:focus .tn-calendar-body-cell-content.tn-focus-indicator{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:default}.tn-calendar-body-cell.tn-calendar-body-today:not(.tn-calendar-body-selected){border:1px solid var(--tn-primary, #007bff);color:var(--tn-primary, #007bff)}.tn-calendar-body-cell.tn-calendar-body-selected,.tn-calendar-body-cell.tn-calendar-body-range-start,.tn-calendar-body-cell.tn-calendar-body-range-end,.tn-calendar-body-cell.tn-calendar-body-in-range{background:var(--tn-primary, #007bff);color:#fff}.tn-calendar-body-cell-content{position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;font-size:var(--calendar-cell-font-size);font-weight:400;transition:background-color .2s cubic-bezier(.25,.8,.25,1)}.tn-calendar-body-cell-preview{position:absolute;inset:0;background:transparent}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-focus-indicator{position:relative}.tn-focus-indicator:before{content:\"\";position:absolute;inset:0;opacity:0;background:currentColor;transition:opacity .2s cubic-bezier(.25,.8,.25,1)}\n"] });
9587
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMonthViewComponent, isStandalone: true, selector: "tn-month-view", inputs: { activeDate: { classPropertyName: "activeDate", publicName: "activeDate", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, rangeMode: { classPropertyName: "rangeMode", publicName: "rangeMode", isSignal: true, isRequired: false, transformFunction: null }, selectedRange: { classPropertyName: "selectedRange", publicName: "selectedRange", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedChange: "selectedChange", activeDateChange: "activeDateChange" }, ngImport: i0, template: "<table role=\"grid\" class=\"tn-calendar-table\">\n <!-- Table header with day names -->\n <thead class=\"tn-calendar-table-header\">\n <tr>\n @for (day of weekdays; track day) {\n <th scope=\"col\">\n <span class=\"cdk-visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.short }}</span>\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table body with calendar cells -->\n <tbody class=\"tn-calendar-body\">\n <!-- Calendar rows -->\n @for (row of calendarRows(); track trackByRow($index); let rowIndex = $index) {\n <tr role=\"row\">\n @for (cell of row; track trackByDate($index, cell); let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"tn-calendar-body-cell-container\"\n [attr.data-tn-row]=\"rowIndex\"\n [attr.data-tn-col]=\"colIndex\">\n @if (cell.value > 0) {\n <button\n type=\"button\"\n class=\"tn-calendar-body-cell\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-active]=\"cell.selected\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\"\n [disabled]=\"!cell.enabled\"\n [attr.tabindex]=\"cell.selected ? 0 : -1\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-pressed]=\"cell.selected\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onCellClicked(cell)\">\n <span class=\"tn-calendar-body-cell-content tn-focus-indicator\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\">\n {{ cell.value }}\n </span>\n <span aria-hidden=\"true\" class=\"tn-calendar-body-cell-preview\"></span>\n </button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: [":host{--calendar-cell-size: 48px;--calendar-header-height: 40px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}.tn-calendar-table{width:calc(7 * var(--calendar-cell-size));border-spacing:0;border-collapse:separate}.tn-calendar-table-header th{text-align:center;height:var(--calendar-header-height);padding:8px 0;font-size:var(--calendar-header-font-size);font-weight:500;color:var(--tn-fg2, #666)}.tn-calendar-table-header-divider{height:1px;border:0}.tn-calendar-body tr{border:0}.tn-calendar-body-cell-container{position:relative;border:0;outline:0;height:var(--calendar-cell-size);width:14.2857142857%}.tn-calendar-body-cell{position:absolute;inset:0;margin:auto;background:transparent;border:0;outline:0;cursor:pointer;color:var(--tn-fg1, #333);width:var(--calendar-cell-size);height:var(--calendar-cell-size)}.tn-calendar-body-cell:not(:disabled):hover:not(.tn-calendar-body-selected):not(.tn-calendar-body-range-start):not(.tn-calendar-body-range-end):not(.tn-calendar-body-in-range){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-calendar-body-cell:focus .tn-calendar-body-cell-content.tn-focus-indicator{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:default}.tn-calendar-body-cell.tn-calendar-body-today:not(.tn-calendar-body-selected){border:1px solid var(--tn-primary, #007bff);color:var(--tn-primary, #007bff)}.tn-calendar-body-cell.tn-calendar-body-selected,.tn-calendar-body-cell.tn-calendar-body-range-start,.tn-calendar-body-cell.tn-calendar-body-range-end,.tn-calendar-body-cell.tn-calendar-body-in-range{background:var(--tn-primary, #007bff);color:#fff}.tn-calendar-body-cell-content{position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;font-size:var(--calendar-cell-font-size);font-weight:400;transition:background-color .2s cubic-bezier(.25,.8,.25,1)}.tn-calendar-body-cell-preview{position:absolute;inset:0;background:transparent}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-focus-indicator{position:relative}.tn-focus-indicator:before{content:\"\";position:absolute;inset:0;opacity:0;background:currentColor;transition:opacity .2s cubic-bezier(.25,.8,.25,1)}\n"] });
8985
9588
  }
8986
9589
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMonthViewComponent, decorators: [{
8987
9590
  type: Component,
8988
- args: [{ selector: 'tn-month-view', standalone: true, imports: [], template: "<table role=\"grid\" class=\"tn-calendar-table\">\n <!-- Table header with day names -->\n <thead class=\"tn-calendar-table-header\">\n <tr>\n @for (day of weekdays; track day) {\n <th scope=\"col\">\n <span class=\"cdk-visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.short }}</span>\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table body with calendar cells -->\n <tbody class=\"tn-calendar-body\">\n <!-- Calendar rows -->\n @for (row of calendarRows(); track trackByRow($index); let rowIndex = $index) {\n <tr role=\"row\">\n @for (cell of row; track trackByDate($index, cell); let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"tn-calendar-body-cell-container\"\n [attr.data-tn-row]=\"rowIndex\"\n [attr.data-tn-col]=\"colIndex\">\n @if (cell.value > 0) {\n <button\n type=\"button\"\n class=\"tn-calendar-body-cell\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-active]=\"cell.selected\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\"\n [disabled]=\"!cell.enabled\"\n [attr.tabindex]=\"cell.selected ? 0 : -1\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-pressed]=\"cell.selected\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onCellClicked(cell)\">\n <span class=\"tn-calendar-body-cell-content tn-focus-indicator\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\">\n {{ cell.value }}\n </span>\n <span aria-hidden=\"true\" class=\"tn-calendar-body-cell-preview\"></span>\n </button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: [":host{--calendar-cell-size: 48px;--calendar-header-height: 40px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}.tn-calendar-table{width:calc(7 * var(--calendar-cell-size));border-spacing:0;border-collapse:separate}.tn-calendar-table-header th{text-align:center;height:var(--calendar-header-height);padding:8px 0;font-size:var(--calendar-header-font-size);font-weight:500;color:var(--tn-fg2, #666)}.tn-calendar-table-header-divider{height:1px;border:0}.tn-calendar-body tr{border:0}.tn-calendar-body-cell-container{position:relative;border:0;outline:0;height:var(--calendar-cell-size);width:14.2857142857%}.tn-calendar-body-cell{position:absolute;inset:0;margin:auto;background:transparent;border:0;outline:0;cursor:pointer;color:var(--tn-fg1, #333);width:var(--calendar-cell-size);height:var(--calendar-cell-size)}.tn-calendar-body-cell:not(:disabled):hover:not(.tn-calendar-body-selected):not(.tn-calendar-body-range-start):not(.tn-calendar-body-range-end):not(.tn-calendar-body-in-range){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-calendar-body-cell:focus .tn-calendar-body-cell-content.tn-focus-indicator{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:default}.tn-calendar-body-cell.tn-calendar-body-today:not(.tn-calendar-body-selected){border:1px solid var(--tn-primary, #007bff);color:var(--tn-primary, #007bff)}.tn-calendar-body-cell.tn-calendar-body-selected,.tn-calendar-body-cell.tn-calendar-body-range-start,.tn-calendar-body-cell.tn-calendar-body-range-end,.tn-calendar-body-cell.tn-calendar-body-in-range{background:var(--tn-primary, #007bff);color:#fff}.tn-calendar-body-cell-content{position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;font-size:var(--calendar-cell-font-size);font-weight:400;transition:background-color .2s cubic-bezier(.25,.8,.25,1)}.tn-calendar-body-cell-preview{position:absolute;inset:0;background:transparent}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-focus-indicator{position:relative}.tn-focus-indicator:before{content:\"\";position:absolute;inset:0;opacity:0;background:currentColor;transition:opacity .2s cubic-bezier(.25,.8,.25,1)}\n"] }]
9591
+ args: [{ selector: 'tn-month-view', standalone: true, imports: [], template: "<table role=\"grid\" class=\"tn-calendar-table\">\n <!-- Table header with day names -->\n <thead class=\"tn-calendar-table-header\">\n <tr>\n @for (day of weekdays; track day) {\n <th scope=\"col\">\n <span class=\"cdk-visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.short }}</span>\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table body with calendar cells -->\n <tbody class=\"tn-calendar-body\">\n <!-- Calendar rows -->\n @for (row of calendarRows(); track trackByRow($index); let rowIndex = $index) {\n <tr role=\"row\">\n @for (cell of row; track trackByDate($index, cell); let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"tn-calendar-body-cell-container\"\n [attr.data-tn-row]=\"rowIndex\"\n [attr.data-tn-col]=\"colIndex\">\n @if (cell.value > 0) {\n <button\n type=\"button\"\n class=\"tn-calendar-body-cell\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-active]=\"cell.selected\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\"\n [disabled]=\"!cell.enabled\"\n [attr.tabindex]=\"cell.selected ? 0 : -1\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-pressed]=\"cell.selected\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onCellClicked(cell)\">\n <span class=\"tn-calendar-body-cell-content tn-focus-indicator\"\n [class.tn-calendar-body-selected]=\"cell.selected\"\n [class.tn-calendar-body-today]=\"cell.today\"\n [class.tn-calendar-body-range-start]=\"cell.rangeStart\"\n [class.tn-calendar-body-range-end]=\"cell.rangeEnd\"\n [class.tn-calendar-body-in-range]=\"cell.inRange\">\n {{ cell.value }}\n </span>\n <span aria-hidden=\"true\" class=\"tn-calendar-body-cell-preview\"></span>\n </button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: [":host{--calendar-cell-size: 48px;--calendar-header-height: 40px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}.tn-calendar-table{width:calc(7 * var(--calendar-cell-size));border-spacing:0;border-collapse:separate}.tn-calendar-table-header th{text-align:center;height:var(--calendar-header-height);padding:8px 0;font-size:var(--calendar-header-font-size);font-weight:500;color:var(--tn-fg2, #666)}.tn-calendar-table-header-divider{height:1px;border:0}.tn-calendar-body tr{border:0}.tn-calendar-body-cell-container{position:relative;border:0;outline:0;height:var(--calendar-cell-size);width:14.2857142857%}.tn-calendar-body-cell{position:absolute;inset:0;margin:auto;background:transparent;border:0;outline:0;cursor:pointer;color:var(--tn-fg1, #333);width:var(--calendar-cell-size);height:var(--calendar-cell-size)}.tn-calendar-body-cell:not(:disabled):hover:not(.tn-calendar-body-selected):not(.tn-calendar-body-range-start):not(.tn-calendar-body-range-end):not(.tn-calendar-body-in-range){background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-calendar-body-cell:focus .tn-calendar-body-cell-content.tn-focus-indicator{background:var(--tn-alt-bg2, #e8f4fd)}.tn-calendar-body-cell:disabled{color:var(--tn-fg2, #666);opacity:.5;cursor:default}.tn-calendar-body-cell.tn-calendar-body-today:not(.tn-calendar-body-selected){border:1px solid var(--tn-primary, #007bff);color:var(--tn-primary, #007bff)}.tn-calendar-body-cell.tn-calendar-body-selected,.tn-calendar-body-cell.tn-calendar-body-range-start,.tn-calendar-body-cell.tn-calendar-body-range-end,.tn-calendar-body-cell.tn-calendar-body-in-range{background:var(--tn-primary, #007bff);color:#fff}.tn-calendar-body-cell-content{position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;font-size:var(--calendar-cell-font-size);font-weight:400;transition:background-color .2s cubic-bezier(.25,.8,.25,1)}.tn-calendar-body-cell-preview{position:absolute;inset:0;background:transparent}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.tn-focus-indicator{position:relative}.tn-focus-indicator:before{content:\"\";position:absolute;inset:0;opacity:0;background:currentColor;transition:opacity .2s cubic-bezier(.25,.8,.25,1)}\n"] }]
8989
9592
  }], propDecorators: { activeDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeDate", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], rangeMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeMode", required: false }] }], selectedRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRange", required: false }] }], selectedChange: [{ type: i0.Output, args: ["selectedChange"] }], activeDateChange: [{ type: i0.Output, args: ["activeDateChange"] }] } });
8990
9593
 
8991
9594
  class TnMultiYearViewComponent {
@@ -9550,7 +10153,7 @@ class TnDateInputComponent {
9550
10153
  useExisting: forwardRef(() => TnDateInputComponent),
9551
10154
  multi: true
9552
10155
  }
9553
- ], viewQueries: [{ propertyName: "monthRef", first: true, predicate: ["monthInput"], descendants: true, isSignal: true }, { propertyName: "dayRef", first: true, predicate: ["dayInput"], descendants: true, isSignal: true }, { propertyName: "yearRef", first: true, predicate: ["yearInput"], descendants: true, isSignal: true }, { propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: TnCalendarComponent, descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-date-input-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-input-wrapper\" style=\"padding-right: 40px;\">\n <!-- Date segments MM/DD/YYYY -->\n <div class=\"tn-date-segment-group\">\n <input\n #monthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('month')\"\n (blur)=\"onSegmentBlur('month')\"\n (keydown)=\"onSegmentKeydown($event, 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #dayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('day')\"\n (blur)=\"onSegmentBlur('day')\"\n (keydown)=\"onSegmentKeydown($event, 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #yearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('year')\"\n (blur)=\"onSegmentBlur('year')\"\n (keydown)=\"onSegmentKeydown($event, 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-input-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"false\"\n [selected]=\"value()\"\n (selectedChange)=\"onDateSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-input-container{position:relative;display:flex;align-items:center}.tn-date-input-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-input-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:16px}.tn-date-input-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-input-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"], dependencies: [{ kind: "component", type: TnCalendarComponent, selector: "tn-calendar", inputs: ["startView", "selected", "minDate", "maxDate", "dateFilter", "rangeMode", "selectedRange"], outputs: ["selectedChange", "activeDateChange", "viewChanged", "selectedRangeChange"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
10156
+ ], viewQueries: [{ propertyName: "monthRef", first: true, predicate: ["monthInput"], descendants: true, isSignal: true }, { propertyName: "dayRef", first: true, predicate: ["dayInput"], descendants: true, isSignal: true }, { propertyName: "yearRef", first: true, predicate: ["yearInput"], descendants: true, isSignal: true }, { propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: TnCalendarComponent, descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-date-input-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-input-wrapper\" style=\"padding-right: 40px;\">\n <!-- Date segments MM/DD/YYYY -->\n <div class=\"tn-date-segment-group\">\n <input\n #monthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('month')\"\n (blur)=\"onSegmentBlur('month')\"\n (keydown)=\"onSegmentKeydown($event, 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #dayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('day')\"\n (blur)=\"onSegmentBlur('day')\"\n (keydown)=\"onSegmentKeydown($event, 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #yearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('year')\"\n (blur)=\"onSegmentBlur('year')\"\n (keydown)=\"onSegmentKeydown($event, 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-input-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"false\"\n [selected]=\"value()\"\n (selectedChange)=\"onDateSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-input-container{position:relative;display:flex;align-items:center}.tn-date-input-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-input-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:1rem}.tn-date-input-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-input-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"], dependencies: [{ kind: "component", type: TnCalendarComponent, selector: "tn-calendar", inputs: ["startView", "selected", "minDate", "maxDate", "dateFilter", "rangeMode", "selectedRange"], outputs: ["selectedChange", "activeDateChange", "viewChanged", "selectedRangeChange"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
9554
10157
  }
9555
10158
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDateInputComponent, decorators: [{
9556
10159
  type: Component,
@@ -9562,7 +10165,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
9562
10165
  }
9563
10166
  ], host: {
9564
10167
  'class': 'tn-date-input'
9565
- }, template: "<div class=\"tn-date-input-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-input-wrapper\" style=\"padding-right: 40px;\">\n <!-- Date segments MM/DD/YYYY -->\n <div class=\"tn-date-segment-group\">\n <input\n #monthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('month')\"\n (blur)=\"onSegmentBlur('month')\"\n (keydown)=\"onSegmentKeydown($event, 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #dayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('day')\"\n (blur)=\"onSegmentBlur('day')\"\n (keydown)=\"onSegmentKeydown($event, 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #yearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('year')\"\n (blur)=\"onSegmentBlur('year')\"\n (keydown)=\"onSegmentKeydown($event, 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-input-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"false\"\n [selected]=\"value()\"\n (selectedChange)=\"onDateSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-input-container{position:relative;display:flex;align-items:center}.tn-date-input-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-input-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:16px}.tn-date-input-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-input-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"] }]
10168
+ }, template: "<div class=\"tn-date-input-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-input-wrapper\" style=\"padding-right: 40px;\">\n <!-- Date segments MM/DD/YYYY -->\n <div class=\"tn-date-segment-group\">\n <input\n #monthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('month')\"\n (blur)=\"onSegmentBlur('month')\"\n (keydown)=\"onSegmentKeydown($event, 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #dayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('day')\"\n (blur)=\"onSegmentBlur('day')\"\n (keydown)=\"onSegmentKeydown($event, 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #yearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('year')\"\n (blur)=\"onSegmentBlur('year')\"\n (keydown)=\"onSegmentKeydown($event, 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-input-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"false\"\n [selected]=\"value()\"\n (selectedChange)=\"onDateSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-input-container{position:relative;display:flex;align-items:center}.tn-date-input-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-input-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:1rem}.tn-date-input-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-input-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"] }]
9566
10169
  }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], monthRef: [{ type: i0.ViewChild, args: ['monthInput', { isSignal: true }] }], dayRef: [{ type: i0.ViewChild, args: ['dayInput', { isSignal: true }] }], yearRef: [{ type: i0.ViewChild, args: ['yearInput', { isSignal: true }] }], calendarTemplate: [{ type: i0.ViewChild, args: ['calendarTemplate', { isSignal: true }] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TnCalendarComponent), { isSignal: true }] }], wrapperEl: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }] } });
9567
10170
 
9568
10171
  /**
@@ -10115,7 +10718,7 @@ class TnDateRangeInputComponent {
10115
10718
  useExisting: forwardRef(() => TnDateRangeInputComponent),
10116
10719
  multi: true
10117
10720
  }
10118
- ], viewQueries: [{ propertyName: "startMonthRef", first: true, predicate: ["startMonthInput"], descendants: true, isSignal: true }, { propertyName: "startDayRef", first: true, predicate: ["startDayInput"], descendants: true, isSignal: true }, { propertyName: "startYearRef", first: true, predicate: ["startYearInput"], descendants: true, isSignal: true }, { propertyName: "endMonthRef", first: true, predicate: ["endMonthInput"], descendants: true, isSignal: true }, { propertyName: "endDayRef", first: true, predicate: ["endDayInput"], descendants: true, isSignal: true }, { propertyName: "endYearRef", first: true, predicate: ["endYearInput"], descendants: true, isSignal: true }, { propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: TnCalendarComponent, descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-date-range-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-range-wrapper\" style=\"padding-right: 40px;\">\n <!-- Start date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #startMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'month')\"\n (blur)=\"onSegmentBlur('start', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'day')\"\n (blur)=\"onSegmentBlur('start', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'year')\"\n (blur)=\"onSegmentBlur('start', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'year')\">\n </div>\n\n <span class=\"tn-date-range-separator\">\u2013</span>\n\n <!-- End date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #endMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'month')\"\n (blur)=\"onSegmentBlur('end', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'day')\"\n (blur)=\"onSegmentBlur('end', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'year')\"\n (blur)=\"onSegmentBlur('end', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-range-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"true\"\n [selectedRange]=\"initialRange()\"\n (selectedRangeChange)=\"onRangeSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-range-container{position:relative;display:flex;align-items:center}.tn-date-range-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-range-separator{padding:0 .25em;-webkit-user-select:none;user-select:none;color:var(--tn-fg2, #666);flex-shrink:0}.tn-date-range-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:16px}.tn-date-range-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-range-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"], dependencies: [{ kind: "component", type: TnCalendarComponent, selector: "tn-calendar", inputs: ["startView", "selected", "minDate", "maxDate", "dateFilter", "rangeMode", "selectedRange"], outputs: ["selectedChange", "activeDateChange", "viewChanged", "selectedRangeChange"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
10721
+ ], viewQueries: [{ propertyName: "startMonthRef", first: true, predicate: ["startMonthInput"], descendants: true, isSignal: true }, { propertyName: "startDayRef", first: true, predicate: ["startDayInput"], descendants: true, isSignal: true }, { propertyName: "startYearRef", first: true, predicate: ["startYearInput"], descendants: true, isSignal: true }, { propertyName: "endMonthRef", first: true, predicate: ["endMonthInput"], descendants: true, isSignal: true }, { propertyName: "endDayRef", first: true, predicate: ["endDayInput"], descendants: true, isSignal: true }, { propertyName: "endYearRef", first: true, predicate: ["endYearInput"], descendants: true, isSignal: true }, { propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: TnCalendarComponent, descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-date-range-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-range-wrapper\" style=\"padding-right: 40px;\">\n <!-- Start date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #startMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'month')\"\n (blur)=\"onSegmentBlur('start', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'day')\"\n (blur)=\"onSegmentBlur('start', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'year')\"\n (blur)=\"onSegmentBlur('start', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'year')\">\n </div>\n\n <span class=\"tn-date-range-separator\">\u2013</span>\n\n <!-- End date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #endMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'month')\"\n (blur)=\"onSegmentBlur('end', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'day')\"\n (blur)=\"onSegmentBlur('end', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'year')\"\n (blur)=\"onSegmentBlur('end', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-range-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"true\"\n [selectedRange]=\"initialRange()\"\n (selectedRangeChange)=\"onRangeSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-range-container{position:relative;display:flex;align-items:center}.tn-date-range-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-range-separator{padding:0 .25em;-webkit-user-select:none;user-select:none;color:var(--tn-fg2, #666);flex-shrink:0}.tn-date-range-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:1rem}.tn-date-range-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-range-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"], dependencies: [{ kind: "component", type: TnCalendarComponent, selector: "tn-calendar", inputs: ["startView", "selected", "minDate", "maxDate", "dateFilter", "rangeMode", "selectedRange"], outputs: ["selectedChange", "activeDateChange", "viewChanged", "selectedRangeChange"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
10119
10722
  }
10120
10723
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDateRangeInputComponent, decorators: [{
10121
10724
  type: Component,
@@ -10127,7 +10730,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
10127
10730
  }
10128
10731
  ], host: {
10129
10732
  'class': 'tn-date-range-input'
10130
- }, template: "<div class=\"tn-date-range-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-range-wrapper\" style=\"padding-right: 40px;\">\n <!-- Start date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #startMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'month')\"\n (blur)=\"onSegmentBlur('start', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'day')\"\n (blur)=\"onSegmentBlur('start', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'year')\"\n (blur)=\"onSegmentBlur('start', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'year')\">\n </div>\n\n <span class=\"tn-date-range-separator\">\u2013</span>\n\n <!-- End date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #endMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'month')\"\n (blur)=\"onSegmentBlur('end', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'day')\"\n (blur)=\"onSegmentBlur('end', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'year')\"\n (blur)=\"onSegmentBlur('end', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-range-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"true\"\n [selectedRange]=\"initialRange()\"\n (selectedRangeChange)=\"onRangeSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-range-container{position:relative;display:flex;align-items:center}.tn-date-range-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-range-separator{padding:0 .25em;-webkit-user-select:none;user-select:none;color:var(--tn-fg2, #666);flex-shrink:0}.tn-date-range-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:16px}.tn-date-range-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-range-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 16px;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"] }]
10733
+ }, template: "<div class=\"tn-date-range-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-date-range-wrapper\" style=\"padding-right: 40px;\">\n <!-- Start date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #startMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'month')\"\n (blur)=\"onSegmentBlur('start', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'day')\"\n (blur)=\"onSegmentBlur('start', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #startYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('start', 'year')\"\n (blur)=\"onSegmentBlur('start', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'start', 'year')\">\n </div>\n\n <span class=\"tn-date-range-separator\">\u2013</span>\n\n <!-- End date segments -->\n <div class=\"tn-date-segment-group\">\n <input\n #endMonthInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-month\"\n placeholder=\"MM\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'month')\"\n (blur)=\"onSegmentBlur('end', 'month')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'month')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endDayInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-day\"\n placeholder=\"DD\"\n maxlength=\"2\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'day')\"\n (blur)=\"onSegmentBlur('end', 'day')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'day')\">\n <span class=\"tn-date-segment-separator\">/</span>\n <input\n #endYearInput\n type=\"text\"\n class=\"tn-date-segment tn-date-segment-year\"\n placeholder=\"YYYY\"\n maxlength=\"4\"\n [disabled]=\"isDisabled()\"\n (focus)=\"onSegmentFocus('end', 'year')\"\n (blur)=\"onSegmentBlur('end', 'year')\"\n (keydown)=\"onSegmentKeydown($event, 'end', 'year')\">\n </div>\n\n <button\n type=\"button\"\n class=\"tn-date-range-toggle\"\n aria-label=\"Open calendar\"\n [disabled]=\"isDisabled()\"\n (click)=\"openDatepicker()\">\n <span aria-hidden=\"true\">\uD83D\uDCC5</span>\n </button>\n </div>\n\n <ng-template #calendarTemplate>\n <tn-calendar\n class=\"tn-calendar\"\n [startView]=\"'month'\"\n [rangeMode]=\"true\"\n [selectedRange]=\"initialRange()\"\n (selectedRangeChange)=\"onRangeSelected($event)\" />\n </ng-template>\n</div>\n", styles: [":host{display:block;width:100%}.tn-date-range-container{position:relative;display:flex;align-items:center}.tn-date-range-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-date-segment-group{display:flex;align-items:center}.tn-date-segment{background:transparent;border:none;outline:none;font:inherit;color:inherit;padding:0;min-width:0;text-align:center;width:2.6ch}.tn-date-segment::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-date-segment:focus{outline:none;background:var(--tn-bg2, rgba(0, 0, 0, .05));border-radius:2px}.tn-date-segment:focus::placeholder{opacity:0}.tn-date-segment.tn-date-segment-year{width:4ch}.tn-date-segment-separator{padding:0 2px;-webkit-user-select:none;user-select:none;color:var(--tn-alt-fg1, #999)}.tn-date-range-separator{padding:0 .25em;-webkit-user-select:none;user-select:none;color:var(--tn-fg2, #666);flex-shrink:0}.tn-date-range-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;font-size:1rem}.tn-date-range-toggle:hover{background:var(--tn-bg2, #f0f0f0);border-radius:4px}.tn-date-range-toggle:disabled{cursor:not-allowed;opacity:.5}:host ::ng-deep .tn-datepicker-overlay .tn-calendar{background:var(--tn-bg1, white);border:1px solid var(--tn-lines, #e0e0e0);border-radius:8px;box-shadow:0 4px 12px #00000026;padding:24px;min-width:380px;--calendar-cell-size: 48px;--calendar-header-height: 44px;--calendar-cell-font-size: 1rem;--calendar-header-font-size: 14px}:host ::ng-deep .tn-datepicker-overlay .tn-calendar .tn-calendar-content{padding:0}\n"] }]
10131
10734
  }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], startMonthRef: [{ type: i0.ViewChild, args: ['startMonthInput', { isSignal: true }] }], startDayRef: [{ type: i0.ViewChild, args: ['startDayInput', { isSignal: true }] }], startYearRef: [{ type: i0.ViewChild, args: ['startYearInput', { isSignal: true }] }], endMonthRef: [{ type: i0.ViewChild, args: ['endMonthInput', { isSignal: true }] }], endDayRef: [{ type: i0.ViewChild, args: ['endDayInput', { isSignal: true }] }], endYearRef: [{ type: i0.ViewChild, args: ['endYearInput', { isSignal: true }] }], calendarTemplate: [{ type: i0.ViewChild, args: ['calendarTemplate', { isSignal: true }] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TnCalendarComponent), { isSignal: true }] }], wrapperEl: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }] } });
10132
10735
 
10133
10736
  /**
@@ -10870,7 +11473,7 @@ class TnButtonToggleComponent {
10870
11473
  useExisting: forwardRef(() => TnButtonToggleComponent),
10871
11474
  multi: true
10872
11475
  }
10873
- ], ngImport: i0, template: "<button\n type=\"button\"\n class=\"tn-button-toggle__button\"\n [class.tn-button-toggle__button--checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-pressed]=\"checked()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.id]=\"buttonId()\"\n [tnTestId]=\"testId()\"\n (click)=\"toggle()\">\n <span class=\"tn-button-toggle__label\">\n @if (checked()) {\n <span class=\"tn-button-toggle__check\">\u2713</span>\n }\n <ng-content />\n </span>\n</button>\n", styles: [".tn-button-toggle{display:inline-block;position:relative}.tn-button-toggle:first-child .tn-button-toggle__button{border-radius:6px 0 0 6px}.tn-button-toggle:last-child .tn-button-toggle__button{border-radius:0 6px 6px 0}.tn-button-toggle:not(:first-child):not(:last-child) .tn-button-toggle__button{border-radius:0}.tn-button-toggle:first-child:last-child .tn-button-toggle__button{border-radius:6px}.tn-button-toggle:not(:first-child) .tn-button-toggle__button{margin-left:-1px}.tn-button-toggle--standalone .tn-button-toggle__button{border-radius:6px}.tn-button-toggle .tn-button-toggle__button{display:inline-flex;align-items:center;justify-content:center;min-width:64px;height:36px;padding:0 16px;border:1px solid var(--tn-lines, #d1d5db);background:var(--tn-bg1, #ffffff);color:var(--tn-fg2, #6b7280);font-family:inherit;font-size:14px;font-weight:500;text-decoration:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);outline:none;position:relative;z-index:1;overflow:hidden}.tn-button-toggle .tn-button-toggle__button:hover:not(:disabled){background:var(--tn-alt-bg1, #f9fafb);z-index:2}.tn-button-toggle .tn-button-toggle__button:focus-visible{outline:2px solid var(--tn-primary, #3b82f6);outline-offset:2px;z-index:3}.tn-button-toggle .tn-button-toggle__button:disabled{cursor:not-allowed!important;background:var(--tn-alt-bg2, #f3f4f6)!important;color:var(--tn-fg1, #000000)!important;opacity:.6!important}.tn-button-toggle .tn-button-toggle__button:disabled:hover{background:var(--tn-alt-bg2, #f3f4f6)!important;cursor:not-allowed!important}.tn-button-toggle .tn-button-toggle__button--checked{background:var(--tn-primary, #3b82f6);color:var(--tn-primary-txt, #ffffff);border-color:var(--tn-primary, #3b82f6);z-index:2;padding:0 20px}.tn-button-toggle .tn-button-toggle__button--checked:hover:not(:disabled){background:var(--tn-primary, #3b82f6)}.tn-button-toggle .tn-button-toggle__label{display:flex;align-items:center;justify-content:center;gap:8px;pointer-events:none;line-height:1}.tn-button-toggle .tn-button-toggle__check{font-size:12px;font-weight:700;line-height:1;margin-right:4px;transform:translate(-4px) scale(.8);opacity:0;animation:checkmarkSlideIn .25s cubic-bezier(.4,0,.2,1) forwards}@keyframes checkmarkSlideIn{0%{transform:translate(-8px) scale(.8);opacity:0}50%{transform:translate(-2px) scale(1.1);opacity:.7}to{transform:translate(0) scale(1);opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11476
+ ], ngImport: i0, template: "<button\n type=\"button\"\n class=\"tn-button-toggle__button\"\n [class.tn-button-toggle__button--checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-pressed]=\"checked()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.id]=\"buttonId()\"\n [tnTestId]=\"testId()\"\n (click)=\"toggle()\">\n <span class=\"tn-button-toggle__label\">\n @if (checked()) {\n <span class=\"tn-button-toggle__check\">\u2713</span>\n }\n <ng-content />\n </span>\n</button>\n", styles: [".tn-button-toggle{display:inline-block;position:relative}.tn-button-toggle:first-child .tn-button-toggle__button{border-radius:6px 0 0 6px}.tn-button-toggle:last-child .tn-button-toggle__button{border-radius:0 6px 6px 0}.tn-button-toggle:not(:first-child):not(:last-child) .tn-button-toggle__button{border-radius:0}.tn-button-toggle:first-child:last-child .tn-button-toggle__button{border-radius:6px}.tn-button-toggle:not(:first-child) .tn-button-toggle__button{margin-left:-1px}.tn-button-toggle--standalone .tn-button-toggle__button{border-radius:6px}.tn-button-toggle .tn-button-toggle__button{display:inline-flex;align-items:center;justify-content:center;min-width:64px;height:36px;padding:0 16px;border:1px solid var(--tn-lines, #d1d5db);background:var(--tn-bg1, #ffffff);color:var(--tn-fg2, #6b7280);font-family:inherit;font-size:14px;font-weight:500;text-decoration:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);outline:none;position:relative;z-index:1;overflow:hidden}.tn-button-toggle .tn-button-toggle__button:hover:not(:disabled){background:var(--tn-alt-bg1, #f9fafb);z-index:2}.tn-button-toggle .tn-button-toggle__button:focus-visible{outline:2px solid var(--tn-primary, #3b82f6);outline-offset:2px;z-index:3}.tn-button-toggle .tn-button-toggle__button:disabled{cursor:not-allowed!important;background:var(--tn-alt-bg2, #f3f4f6)!important;color:var(--tn-fg1, #000000)!important;opacity:.6!important}.tn-button-toggle .tn-button-toggle__button:disabled:hover{background:var(--tn-alt-bg2, #f3f4f6)!important;cursor:not-allowed!important}.tn-button-toggle .tn-button-toggle__button--checked{background:var(--tn-button-toggle-checked-bg, var(--tn-alt-bg2, #f3f4f6));color:var(--tn-button-toggle-checked-color, var(--tn-fg1, #1f2937));border-color:var(--tn-button-toggle-checked-border, var(--tn-lines, #d1d5db));z-index:2;padding:0 20px}.tn-button-toggle .tn-button-toggle__button--checked:hover:not(:disabled){background:var(--tn-button-toggle-checked-bg, var(--tn-alt-bg2, #f3f4f6))}.tn-button-toggle .tn-button-toggle__label{display:flex;align-items:center;justify-content:center;gap:8px;pointer-events:none;line-height:1}.tn-button-toggle .tn-button-toggle__check{font-size:12px;font-weight:700;line-height:1;margin-right:4px;transform:translate(-4px) scale(.8);opacity:0;animation:checkmarkSlideIn .25s cubic-bezier(.4,0,.2,1) forwards}@keyframes checkmarkSlideIn{0%{transform:translate(-8px) scale(.8);opacity:0}50%{transform:translate(-2px) scale(1.1);opacity:.7}to{transform:translate(0) scale(1);opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10874
11477
  }
10875
11478
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnButtonToggleComponent, decorators: [{
10876
11479
  type: Component,
@@ -10887,7 +11490,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
10887
11490
  '[class.tn-button-toggle--disabled]': 'isDisabled()',
10888
11491
  '[class.tn-button-toggle--standalone]': '!buttonToggleGroup',
10889
11492
  '(focus)': 'onFocus()'
10890
- }, template: "<button\n type=\"button\"\n class=\"tn-button-toggle__button\"\n [class.tn-button-toggle__button--checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-pressed]=\"checked()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.id]=\"buttonId()\"\n [tnTestId]=\"testId()\"\n (click)=\"toggle()\">\n <span class=\"tn-button-toggle__label\">\n @if (checked()) {\n <span class=\"tn-button-toggle__check\">\u2713</span>\n }\n <ng-content />\n </span>\n</button>\n", styles: [".tn-button-toggle{display:inline-block;position:relative}.tn-button-toggle:first-child .tn-button-toggle__button{border-radius:6px 0 0 6px}.tn-button-toggle:last-child .tn-button-toggle__button{border-radius:0 6px 6px 0}.tn-button-toggle:not(:first-child):not(:last-child) .tn-button-toggle__button{border-radius:0}.tn-button-toggle:first-child:last-child .tn-button-toggle__button{border-radius:6px}.tn-button-toggle:not(:first-child) .tn-button-toggle__button{margin-left:-1px}.tn-button-toggle--standalone .tn-button-toggle__button{border-radius:6px}.tn-button-toggle .tn-button-toggle__button{display:inline-flex;align-items:center;justify-content:center;min-width:64px;height:36px;padding:0 16px;border:1px solid var(--tn-lines, #d1d5db);background:var(--tn-bg1, #ffffff);color:var(--tn-fg2, #6b7280);font-family:inherit;font-size:14px;font-weight:500;text-decoration:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);outline:none;position:relative;z-index:1;overflow:hidden}.tn-button-toggle .tn-button-toggle__button:hover:not(:disabled){background:var(--tn-alt-bg1, #f9fafb);z-index:2}.tn-button-toggle .tn-button-toggle__button:focus-visible{outline:2px solid var(--tn-primary, #3b82f6);outline-offset:2px;z-index:3}.tn-button-toggle .tn-button-toggle__button:disabled{cursor:not-allowed!important;background:var(--tn-alt-bg2, #f3f4f6)!important;color:var(--tn-fg1, #000000)!important;opacity:.6!important}.tn-button-toggle .tn-button-toggle__button:disabled:hover{background:var(--tn-alt-bg2, #f3f4f6)!important;cursor:not-allowed!important}.tn-button-toggle .tn-button-toggle__button--checked{background:var(--tn-primary, #3b82f6);color:var(--tn-primary-txt, #ffffff);border-color:var(--tn-primary, #3b82f6);z-index:2;padding:0 20px}.tn-button-toggle .tn-button-toggle__button--checked:hover:not(:disabled){background:var(--tn-primary, #3b82f6)}.tn-button-toggle .tn-button-toggle__label{display:flex;align-items:center;justify-content:center;gap:8px;pointer-events:none;line-height:1}.tn-button-toggle .tn-button-toggle__check{font-size:12px;font-weight:700;line-height:1;margin-right:4px;transform:translate(-4px) scale(.8);opacity:0;animation:checkmarkSlideIn .25s cubic-bezier(.4,0,.2,1) forwards}@keyframes checkmarkSlideIn{0%{transform:translate(-8px) scale(.8);opacity:0}50%{transform:translate(-2px) scale(1.1);opacity:.7}to{transform:translate(0) scale(1);opacity:1}}\n"] }]
11493
+ }, template: "<button\n type=\"button\"\n class=\"tn-button-toggle__button\"\n [class.tn-button-toggle__button--checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-pressed]=\"checked()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.id]=\"buttonId()\"\n [tnTestId]=\"testId()\"\n (click)=\"toggle()\">\n <span class=\"tn-button-toggle__label\">\n @if (checked()) {\n <span class=\"tn-button-toggle__check\">\u2713</span>\n }\n <ng-content />\n </span>\n</button>\n", styles: [".tn-button-toggle{display:inline-block;position:relative}.tn-button-toggle:first-child .tn-button-toggle__button{border-radius:6px 0 0 6px}.tn-button-toggle:last-child .tn-button-toggle__button{border-radius:0 6px 6px 0}.tn-button-toggle:not(:first-child):not(:last-child) .tn-button-toggle__button{border-radius:0}.tn-button-toggle:first-child:last-child .tn-button-toggle__button{border-radius:6px}.tn-button-toggle:not(:first-child) .tn-button-toggle__button{margin-left:-1px}.tn-button-toggle--standalone .tn-button-toggle__button{border-radius:6px}.tn-button-toggle .tn-button-toggle__button{display:inline-flex;align-items:center;justify-content:center;min-width:64px;height:36px;padding:0 16px;border:1px solid var(--tn-lines, #d1d5db);background:var(--tn-bg1, #ffffff);color:var(--tn-fg2, #6b7280);font-family:inherit;font-size:14px;font-weight:500;text-decoration:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);outline:none;position:relative;z-index:1;overflow:hidden}.tn-button-toggle .tn-button-toggle__button:hover:not(:disabled){background:var(--tn-alt-bg1, #f9fafb);z-index:2}.tn-button-toggle .tn-button-toggle__button:focus-visible{outline:2px solid var(--tn-primary, #3b82f6);outline-offset:2px;z-index:3}.tn-button-toggle .tn-button-toggle__button:disabled{cursor:not-allowed!important;background:var(--tn-alt-bg2, #f3f4f6)!important;color:var(--tn-fg1, #000000)!important;opacity:.6!important}.tn-button-toggle .tn-button-toggle__button:disabled:hover{background:var(--tn-alt-bg2, #f3f4f6)!important;cursor:not-allowed!important}.tn-button-toggle .tn-button-toggle__button--checked{background:var(--tn-button-toggle-checked-bg, var(--tn-alt-bg2, #f3f4f6));color:var(--tn-button-toggle-checked-color, var(--tn-fg1, #1f2937));border-color:var(--tn-button-toggle-checked-border, var(--tn-lines, #d1d5db));z-index:2;padding:0 20px}.tn-button-toggle .tn-button-toggle__button--checked:hover:not(:disabled){background:var(--tn-button-toggle-checked-bg, var(--tn-alt-bg2, #f3f4f6))}.tn-button-toggle .tn-button-toggle__label{display:flex;align-items:center;justify-content:center;gap:8px;pointer-events:none;line-height:1}.tn-button-toggle .tn-button-toggle__check{font-size:12px;font-weight:700;line-height:1;margin-right:4px;transform:translate(-4px) scale(.8);opacity:0;animation:checkmarkSlideIn .25s cubic-bezier(.4,0,.2,1) forwards}@keyframes checkmarkSlideIn{0%{transform:translate(-8px) scale(.8);opacity:0}50%{transform:translate(-2px) scale(1.1);opacity:.7}to{transform:translate(0) scale(1);opacity:1}}\n"] }]
10891
11494
  }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
10892
11495
 
10893
11496
  class TnButtonToggleGroupComponent {
@@ -10897,6 +11500,22 @@ class TnButtonToggleGroupComponent {
10897
11500
  name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
10898
11501
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
10899
11502
  ariaLabelledby = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
11503
+ /**
11504
+ * Overrides the background color of checked toggles in this group. Accepts
11505
+ * any CSS color value (`#hex`, `rgb()`, `var(--token)`, etc.). When null
11506
+ * (default), falls back to `--tn-alt-bg2`.
11507
+ */
11508
+ checkedBg = input(null, ...(ngDevMode ? [{ debugName: "checkedBg" }] : []));
11509
+ /**
11510
+ * Overrides the text color of checked toggles in this group. Defaults to
11511
+ * `--tn-fg1` when null.
11512
+ */
11513
+ checkedColor = input(null, ...(ngDevMode ? [{ debugName: "checkedColor" }] : []));
11514
+ /**
11515
+ * Overrides the border color of checked toggles in this group. Defaults to
11516
+ * `--tn-lines` when null.
11517
+ */
11518
+ checkedBorder = input(null, ...(ngDevMode ? [{ debugName: "checkedBorder" }] : []));
10900
11519
  /**
10901
11520
  * Test-id applied to the group root. Rendered under whichever attribute name
10902
11521
  * is configured via `TN_TEST_ATTR` (default `data-testid`).
@@ -10916,6 +11535,10 @@ class TnButtonToggleGroupComponent {
10916
11535
  const toggles = this.buttonToggles();
10917
11536
  toggles.forEach(toggle => {
10918
11537
  toggle.buttonToggleGroup = this;
11538
+ // Track each toggle's value signal so the effect re-runs after @for-driven
11539
+ // bindings resolve. Without this, an initial value written before child
11540
+ // value signals have propagated would never reach the toggles.
11541
+ toggle.value();
10919
11542
  });
10920
11543
  // Re-apply stored value to toggles that weren't available during writeValue
10921
11544
  if (toggles.length > 0) {
@@ -11030,7 +11653,7 @@ class TnButtonToggleGroupComponent {
11030
11653
  });
11031
11654
  }
11032
11655
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnButtonToggleGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11033
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnButtonToggleGroupComponent, isStandalone: true, selector: "tn-button-toggle-group", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { classAttribute: "tn-button-toggle-group" }, providers: [
11656
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnButtonToggleGroupComponent, isStandalone: true, selector: "tn-button-toggle-group", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, checkedBg: { classPropertyName: "checkedBg", publicName: "checkedBg", isSignal: true, isRequired: false, transformFunction: null }, checkedColor: { classPropertyName: "checkedColor", publicName: "checkedColor", isSignal: true, isRequired: false, transformFunction: null }, checkedBorder: { classPropertyName: "checkedBorder", publicName: "checkedBorder", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { properties: { "style.--tn-button-toggle-checked-bg": "checkedBg()", "style.--tn-button-toggle-checked-color": "checkedColor()", "style.--tn-button-toggle-checked-border": "checkedBorder()" }, classAttribute: "tn-button-toggle-group" }, providers: [
11034
11657
  {
11035
11658
  provide: NG_VALUE_ACCESSOR,
11036
11659
  useExisting: forwardRef(() => TnButtonToggleGroupComponent),
@@ -11047,9 +11670,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
11047
11670
  multi: true
11048
11671
  }
11049
11672
  ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
11050
- 'class': 'tn-button-toggle-group'
11673
+ 'class': 'tn-button-toggle-group',
11674
+ '[style.--tn-button-toggle-checked-bg]': 'checkedBg()',
11675
+ '[style.--tn-button-toggle-checked-color]': 'checkedColor()',
11676
+ '[style.--tn-button-toggle-checked-border]': 'checkedBorder()'
11051
11677
  }, template: "<div class=\"tn-button-toggle-group\"\n [attr.role]=\"multiple() ? 'group' : 'radiogroup'\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [tnTestId]=\"testId()\">\n <ng-content />\n</div>\n", styles: [".tn-button-toggle-group{display:inline-flex;align-items:stretch;border-radius:6px;box-shadow:0 1px 2px #0000000d}\n"] }]
11052
- }], ctorParameters: () => [], propDecorators: { buttonToggles: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnButtonToggleComponent), { ...{ descendants: true }, isSignal: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
11678
+ }], ctorParameters: () => [], propDecorators: { buttonToggles: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnButtonToggleComponent), { ...{ descendants: true }, isSignal: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], checkedBg: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedBg", required: false }] }], checkedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedColor", required: false }] }], checkedBorder: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedBorder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
11053
11679
 
11054
11680
  /**
11055
11681
  * Harness for interacting with `tn-button-toggle` in tests.
@@ -11240,232 +11866,30 @@ class TnButtonToggleGroupHarness extends ComponentHarness {
11240
11866
  }
11241
11867
  }
11242
11868
 
11243
- class TnTooltipComponent {
11244
- message = input('', ...(ngDevMode ? [{ debugName: "message" }] : []));
11245
- id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
11246
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11247
- 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 });
11248
- }
11249
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipComponent, decorators: [{
11250
- type: Component,
11251
- args: [{ selector: 'tn-tooltip', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
11252
- 'class': 'tn-tooltip-component'
11253
- }, 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"] }]
11254
- }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
11255
-
11256
- /* eslint-disable @angular-eslint/no-input-rename */
11257
- // Input aliasing is intentional for directive API consistency (e.g., ixTooltip, ixTooltipPosition)
11258
- // This follows the standard Angular pattern used by Material and other directive-based components
11259
- class TnTooltipDirective {
11260
- message = input('', { ...(ngDevMode ? { debugName: "message" } : {}), alias: 'tnTooltip' });
11261
- position = input('above', { ...(ngDevMode ? { debugName: "position" } : {}), alias: 'tnTooltipPosition' });
11262
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'tnTooltipDisabled' });
11263
- showDelay = input(0, { ...(ngDevMode ? { debugName: "showDelay" } : {}), alias: 'tnTooltipShowDelay' });
11264
- hideDelay = input(0, { ...(ngDevMode ? { debugName: "hideDelay" } : {}), alias: 'tnTooltipHideDelay' });
11265
- tooltipClass = input('', { ...(ngDevMode ? { debugName: "tooltipClass" } : {}), alias: 'tnTooltipClass' });
11266
- _overlayRef = null;
11267
- _tooltipInstance = null;
11268
- _showTimeout = null;
11269
- _hideTimeout = null;
11270
- _isTooltipVisible = false;
11271
- _positionSub = null;
11272
- _ariaDescribedBy = null;
11273
- _overlay = inject(Overlay);
11274
- _elementRef = inject((ElementRef));
11275
- _viewContainerRef = inject(ViewContainerRef);
11276
- _overlayPositionBuilder = inject(OverlayPositionBuilder);
11277
- ngOnInit() {
11278
- // Generate unique ID for aria-describedby
11279
- this._ariaDescribedBy = `tn-tooltip-${Math.random().toString(36).substr(2, 9)}`;
11280
- }
11281
- ngOnDestroy() {
11282
- this._clearTimeouts();
11283
- this.hide(0);
11284
- this._positionSub?.unsubscribe();
11285
- if (this._overlayRef) {
11286
- this._overlayRef.dispose();
11287
- this._overlayRef = null;
11288
- }
11289
- }
11290
- _onMouseEnter() {
11291
- if (!this.disabled() && this.message()) {
11292
- this.show(this.showDelay());
11293
- }
11294
- }
11295
- _onMouseLeave() {
11296
- this.hide(this.hideDelay());
11297
- }
11298
- _onFocus() {
11299
- if (!this.disabled() && this.message()) {
11300
- this.show(this.showDelay());
11301
- }
11302
- }
11303
- _onBlur() {
11304
- this.hide(this.hideDelay());
11305
- }
11306
- _onKeydown(event) {
11307
- if (event.key === 'Escape' && this._isTooltipVisible) {
11308
- this.hide(0);
11309
- }
11310
- }
11311
- /** Shows the tooltip */
11312
- show(delay = 0) {
11313
- if (this.disabled() || !this.message() || this._isTooltipVisible) {
11314
- return;
11315
- }
11316
- this._clearTimeouts();
11317
- this._showTimeout = setTimeout(() => {
11318
- if (!this._overlayRef) {
11319
- this._createOverlay();
11320
- }
11321
- this._attachTooltip();
11322
- }, delay);
11323
- }
11324
- /** Hides the tooltip */
11325
- hide(delay = 0) {
11326
- this._clearTimeouts();
11327
- this._hideTimeout = setTimeout(() => {
11328
- if (this._tooltipInstance) {
11329
- this._tooltipInstance.destroy();
11330
- this._tooltipInstance = null;
11331
- this._isTooltipVisible = false;
11332
- }
11333
- }, delay);
11334
- }
11335
- /** Toggle the tooltip visibility */
11336
- toggle() {
11337
- this._isTooltipVisible ? this.hide() : this.show();
11338
- }
11339
- _createOverlay() {
11340
- const positions = this._getPositions();
11341
- const positionStrategy = this._overlayPositionBuilder
11342
- .flexibleConnectedTo(this._elementRef)
11343
- .withPositions(positions)
11344
- .withFlexibleDimensions(false)
11345
- .withViewportMargin(8)
11346
- .withScrollableContainers([]);
11347
- this._overlayRef = this._overlay.create({
11348
- positionStrategy,
11349
- scrollStrategy: this._overlay.scrollStrategies.reposition({ scrollThrottle: 20 }),
11350
- panelClass: ['tn-tooltip-panel', `tn-tooltip-panel-${this.position()}`, this.tooltipClass()].filter(Boolean),
11351
- });
11352
- this._positionSub = positionStrategy.positionChanges
11353
- .subscribe((change) => {
11354
- const panel = this._overlayRef?.overlayElement?.parentElement;
11355
- if (!panel) {
11356
- return;
11357
- }
11358
- const actual = this._resolvePosition(change.connectionPair);
11359
- const allPositionClasses = [
11360
- 'tn-tooltip-panel-above', 'tn-tooltip-panel-below',
11361
- 'tn-tooltip-panel-left', 'tn-tooltip-panel-right',
11362
- 'tn-tooltip-panel-before', 'tn-tooltip-panel-after',
11363
- ];
11364
- panel.classList.remove(...allPositionClasses);
11365
- panel.classList.add(`tn-tooltip-panel-${actual}`);
11366
- });
11367
- }
11368
- _resolvePosition(pair) {
11369
- if (pair.overlayY === 'bottom') {
11370
- return 'above';
11371
- }
11372
- if (pair.overlayY === 'top') {
11373
- return 'below';
11374
- }
11375
- if (pair.overlayX === 'end') {
11376
- return 'left';
11377
- }
11378
- return 'right';
11379
- }
11380
- _attachTooltip() {
11381
- if (!this._overlayRef) {
11382
- return;
11383
- }
11384
- if (!this._tooltipInstance) {
11385
- const portal = new ComponentPortal(TnTooltipComponent, this._viewContainerRef);
11386
- this._tooltipInstance = this._overlayRef.attach(portal);
11387
- this._tooltipInstance.setInput('message', this.message());
11388
- this._tooltipInstance.setInput('id', this._ariaDescribedBy);
11389
- this._isTooltipVisible = true;
11390
- }
11391
- }
11392
- _getPositions() {
11393
- switch (this.position()) {
11394
- case 'above':
11395
- return [
11396
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
11397
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11398
- ];
11399
- case 'below':
11400
- return [
11401
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11402
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -12 },
11403
- ];
11404
- case 'left':
11405
- case 'before':
11406
- return [
11407
- { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
11408
- { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
11409
- ];
11410
- case 'right':
11411
- case 'after':
11412
- return [
11413
- { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 12 },
11414
- { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -12 },
11415
- ];
11416
- default:
11417
- return [
11418
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 12 },
11419
- ];
11420
- }
11421
- }
11422
- _clearTimeouts() {
11423
- if (this._showTimeout) {
11424
- clearTimeout(this._showTimeout);
11425
- this._showTimeout = null;
11426
- }
11427
- if (this._hideTimeout) {
11428
- clearTimeout(this._hideTimeout);
11429
- this._hideTimeout = null;
11430
- }
11431
- }
11432
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
11433
- 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 });
11434
- }
11435
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTooltipDirective, decorators: [{
11436
- type: Directive,
11437
- args: [{
11438
- selector: '[tnTooltip]',
11439
- standalone: true,
11440
- host: {
11441
- '[attr.aria-describedby]': '_ariaDescribedBy',
11442
- }
11443
- }]
11444
- }], 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: [{
11445
- type: HostListener,
11446
- args: ['mouseenter']
11447
- }], _onMouseLeave: [{
11448
- type: HostListener,
11449
- args: ['mouseleave']
11450
- }], _onFocus: [{
11451
- type: HostListener,
11452
- args: ['focus']
11453
- }], _onBlur: [{
11454
- type: HostListener,
11455
- args: ['blur']
11456
- }], _onKeydown: [{
11457
- type: HostListener,
11458
- args: ['keydown', ['$event']]
11459
- }] } });
11460
-
11869
+ let nextUniqueId = 0;
11461
11870
  class TnDialogShellComponent {
11462
11871
  title = input('', ...(ngDevMode ? [{ debugName: "title" }] : []));
11463
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++}`;
11464
11875
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
11465
11876
  originalStyles = {};
11466
11877
  ref = inject(DialogRef);
11467
11878
  document = inject(DOCUMENT);
11879
+ host = inject(ElementRef);
11468
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
+ }
11469
11893
  ngOnInit() {
11470
11894
  // Check if dialog was opened in fullscreen mode by looking for existing fullscreen class
11471
11895
  setTimeout(() => {
@@ -11523,14 +11947,14 @@ class TnDialogShellComponent {
11523
11947
  }
11524
11948
  }
11525
11949
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11526
- 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" });
11527
11951
  }
11528
11952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, decorators: [{
11529
11953
  type: Component,
11530
11954
  args: [{ selector: 'tn-dialog-shell', standalone: true, imports: [], host: {
11531
11955
  'class': 'tn-dialog-shell'
11532
- }, 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" }]
11533
- }], 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 }] }] } });
11534
11958
 
11535
11959
  class TnConfirmDialogComponent {
11536
11960
  ref = inject((DialogRef));
@@ -11711,6 +12135,26 @@ class TnDialogHarness extends ComponentHarness {
11711
12135
  const closeBtn = await this._closeButton();
11712
12136
  await closeBtn.click();
11713
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
+ }
11714
12158
  /**
11715
12159
  * Clicks an action button in the dialog footer by its label.
11716
12160
  * Only matches buttons inside the `tnDialogAction` footer area, not buttons in content.
@@ -11961,7 +12405,7 @@ class TnSidePanelComponent {
11961
12405
  });
11962
12406
  }
11963
12407
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11964
- 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"] }] });
11965
12409
  }
11966
12410
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, decorators: [{
11967
12411
  type: Component,
@@ -12129,7 +12573,7 @@ class TnStepperComponent {
12129
12573
  return index;
12130
12574
  }
12131
12575
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12132
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnStepperComponent, isStandalone: true, selector: "tn-stepper", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null }, selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange", selectionChange: "selectionChange", completed: "completed" }, host: { listeners: { "window:resize": "onWindowResize($event)" } }, queries: [{ propertyName: "steps", predicate: TnStepComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-stepper\"\n [class.tn-stepper--horizontal]=\"orientation() === 'horizontal' || (orientation() === 'auto' && isWideScreen())\"\n [class.tn-stepper--vertical]=\"orientation() === 'vertical' || (orientation() === 'auto' && !isWideScreen())\"\n [tnTestId]=\"testId()\">\n\n <!-- Step Headers -->\n <div class=\"tn-stepper__header\">\n @for (step of steps(); track $index; let i = $index) {\n <div class=\"tn-stepper__step-header\"\n tabindex=\"0\"\n role=\"button\"\n [class.tn-stepper__step-header--active]=\"selectedIndex() === i\"\n [class.tn-stepper__step-header--completed]=\"step.completed()\"\n [class.tn-stepper__step-header--error]=\"step.hasError()\"\n [class.tn-stepper__step-header--optional]=\"step.optional()\"\n (click)=\"selectStep(i)\"\n (keydown.enter)=\"selectStep(i)\"\n (keydown.space)=\"selectStep(i)\">\n\n <!-- Step Number/Icon -->\n <div class=\"tn-stepper__step-indicator\">\n @if (step.completed() && !step.hasError()) {\n <span class=\"tn-stepper__step-check\">\u2713</span>\n }\n @if (step.hasError()) {\n <span class=\"tn-stepper__step-error\">!</span>\n }\n @if (!step.completed() && !step.hasError()) {\n @if (step.icon()) {\n <span class=\"tn-stepper__step-icon\">{{ step.icon() }}</span>\n } @else {\n <span class=\"tn-stepper__step-number\">{{ i + 1 }}</span>\n }\n }\n </div>\n\n <!-- Step Label -->\n <div class=\"tn-stepper__step-label\">\n <div class=\"tn-stepper__step-title\">{{ step.label() }}</div>\n @if (step.optional()) {\n <span class=\"tn-stepper__step-subtitle\">Optional</span>\n }\n </div>\n </div>\n\n <!-- Connector Line (except for last step) -->\n @if (i < steps().length - 1) {\n <div class=\"tn-stepper__connector\"></div>\n }\n }\n </div>\n\n <!-- Step Content -->\n <div class=\"tn-stepper__content\">\n @for (step of steps(); track $index; let i = $index) {\n @if (selectedIndex() === i) {\n <div class=\"tn-stepper__step-content\"\n [@stepTransition]=\"selectedIndex()\">\n <ng-container *ngTemplateOutlet=\"step.content()\" />\n </div>\n }\n }\n </div>\n</div>", styles: [".tn-stepper{display:flex;font-family:inherit;--step-diameter: 48px;--step-diameter-sm: 32px;--step-padding: 12px}.tn-stepper--horizontal{flex-direction:column}.tn-stepper--horizontal .tn-stepper__header{display:flex;justify-content:center;margin-bottom:32px;padding:0 16px}.tn-stepper--horizontal .tn-stepper__step-header{display:flex;flex-direction:column;align-items:center;text-align:center;cursor:pointer;transition:all .2s ease-in-out}.tn-stepper--horizontal .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--horizontal .tn-stepper__connector{flex:1;height:2px;background:var(--tn-lines, #e5e7eb);margin:0 16px;position:relative;top:calc(var(--step-diameter) / 2)}.tn-stepper--vertical{flex-direction:row}.tn-stepper--vertical .tn-stepper__header{display:flex;flex-direction:column;width:280px;padding:16px;border-right:1px solid var(--tn-lines, #e5e7eb)}.tn-stepper--vertical .tn-stepper__step-header{display:flex;flex-direction:row;align-items:center;text-align:left;cursor:pointer;transition:all .2s ease-in-out;padding:var(--step-padding);border-radius:8px;margin-bottom:8px}.tn-stepper--vertical .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:12px;margin-top:0}.tn-stepper--vertical .tn-stepper__connector{width:2px;height:24px;background:var(--tn-lines, #e5e7eb);position:relative;left:calc(var(--step-diameter) / 2 + var(--step-padding));margin-bottom:8px}.tn-stepper--vertical .tn-stepper__content{flex:1;padding:16px}.tn-stepper__step-indicator{display:flex;align-items:center;justify-content:center;width:var(--step-diameter);height:var(--step-diameter);border-radius:50%;background:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-alt-fg1, #495057);font-weight:400;font-size:14px;transition:all .2s ease-in-out;position:relative;transform:scale(.65)}.tn-stepper__step-number{font-weight:400}.tn-stepper__step-label{margin-top:8px}.tn-stepper__step-title{display:block;font-weight:400;font-size:14px;color:var(--tn-fg1, #000000);line-height:1.2}.tn-stepper__step-subtitle{display:block;font-size:12px;color:var(--tn-fg2, #6c757d);margin-top:2px}.tn-stepper__step-header--active .tn-stepper__step-indicator{background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, #ffffff);transform:scale(1)}.tn-stepper__step-header--active .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--completed .tn-stepper__step-indicator{background:var(--tn-green, #28a745);color:#fff}.tn-stepper__step-header--completed .tn-stepper__step-check{font-size:16px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-indicator{background:var(--tn-red, #dc3545);color:#fff}.tn-stepper__step-header--error .tn-stepper__step-error{font-size:18px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg1, #000000)}.tn-stepper__step-header--active.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--optional .tn-stepper__step-indicator{border:2px dashed var(--tn-lines, #e5e7eb);background:transparent}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector:after{content:\"\";position:absolute;top:0;left:0;right:0;height:100%;background:var(--tn-green, #28a745);animation:progressFill .3s ease-in-out}.tn-stepper--vertical .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper__content{min-height:200px}.tn-stepper__step-content{padding:16px;background:var(--tn-bg1, #ffffff);border-radius:8px;border:1px solid var(--tn-lines, #e5e7eb)}@keyframes progressFill{0%{width:0}to{width:100%}}.tn-stepper__step-header:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-stepper__step-header:focus:not(:focus-visible){outline:none}@media(max-width:780px){.tn-stepper--vertical .tn-stepper__header{width:180px;border-right:none;border-bottom:1px solid var(--tn-lines, #e5e7eb);padding:16px 0}.tn-stepper--vertical .tn-stepper__step-header{flex-direction:column;align-items:center;text-align:center;min-width:80px;padding:8px 4px}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:0;margin-top:8px}.tn-stepper--vertical .tn-stepper__connector{display:none}}@media(max-width:780px){.tn-stepper .tn-stepper__step-label{display:none}.tn-stepper .tn-stepper__step-indicator{width:var(--step-diameter-sm);height:var(--step-diameter-sm);font-size:12px}.tn-stepper--horizontal .tn-stepper__header{padding:0 8px}.tn-stepper--horizontal .tn-stepper__connector{top:calc(var(--step-diameter-sm) / 2);margin:0 8px}.tn-stepper--vertical .tn-stepper__header{width:60px}.tn-stepper--vertical .tn-stepper__step-header{padding:4px;margin-bottom:4px}.tn-stepper--vertical .tn-stepper__connector{left:calc(var(--step-diameter-sm) / 2 + 4px);height:16px;margin-bottom:4px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], animations: [
12576
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnStepperComponent, isStandalone: true, selector: "tn-stepper", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null }, selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange", selectionChange: "selectionChange", completed: "completed" }, host: { listeners: { "window:resize": "onWindowResize($event)" } }, queries: [{ propertyName: "steps", predicate: TnStepComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-stepper\"\n [class.tn-stepper--horizontal]=\"orientation() === 'horizontal' || (orientation() === 'auto' && isWideScreen())\"\n [class.tn-stepper--vertical]=\"orientation() === 'vertical' || (orientation() === 'auto' && !isWideScreen())\"\n [tnTestId]=\"testId()\">\n\n <!-- Step Headers -->\n <div class=\"tn-stepper__header\">\n @for (step of steps(); track $index; let i = $index) {\n <div class=\"tn-stepper__step-header\"\n tabindex=\"0\"\n role=\"button\"\n [class.tn-stepper__step-header--active]=\"selectedIndex() === i\"\n [class.tn-stepper__step-header--completed]=\"step.completed()\"\n [class.tn-stepper__step-header--error]=\"step.hasError()\"\n [class.tn-stepper__step-header--optional]=\"step.optional()\"\n (click)=\"selectStep(i)\"\n (keydown.enter)=\"selectStep(i)\"\n (keydown.space)=\"selectStep(i)\">\n\n <!-- Step Number/Icon -->\n <div class=\"tn-stepper__step-indicator\">\n @if (step.completed() && !step.hasError()) {\n <span class=\"tn-stepper__step-check\">\u2713</span>\n }\n @if (step.hasError()) {\n <span class=\"tn-stepper__step-error\">!</span>\n }\n @if (!step.completed() && !step.hasError()) {\n @if (step.icon()) {\n <span class=\"tn-stepper__step-icon\">{{ step.icon() }}</span>\n } @else {\n <span class=\"tn-stepper__step-number\">{{ i + 1 }}</span>\n }\n }\n </div>\n\n <!-- Step Label -->\n <div class=\"tn-stepper__step-label\">\n <div class=\"tn-stepper__step-title\">{{ step.label() }}</div>\n @if (step.optional()) {\n <span class=\"tn-stepper__step-subtitle\">Optional</span>\n }\n </div>\n </div>\n\n <!-- Connector Line (except for last step) -->\n @if (i < steps().length - 1) {\n <div class=\"tn-stepper__connector\"></div>\n }\n }\n </div>\n\n <!-- Step Content -->\n <div class=\"tn-stepper__content\">\n @for (step of steps(); track $index; let i = $index) {\n @if (selectedIndex() === i) {\n <div class=\"tn-stepper__step-content\"\n [@stepTransition]=\"selectedIndex()\">\n <ng-container *ngTemplateOutlet=\"step.content()\" />\n </div>\n }\n }\n </div>\n</div>", styles: [".tn-stepper{display:flex;font-family:inherit;--step-diameter: 48px;--step-diameter-sm: 32px;--step-padding: 12px}.tn-stepper--horizontal{flex-direction:column}.tn-stepper--horizontal .tn-stepper__header{display:flex;justify-content:center;margin-bottom:32px;padding:0 16px}.tn-stepper--horizontal .tn-stepper__step-header{display:flex;flex-direction:column;align-items:center;text-align:center;cursor:pointer;transition:all .2s ease-in-out}.tn-stepper--horizontal .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--horizontal .tn-stepper__connector{flex:1;height:2px;background:var(--tn-lines, #e5e7eb);margin:0 16px;position:relative;top:calc(var(--step-diameter) / 2)}.tn-stepper--vertical{flex-direction:row}.tn-stepper--vertical .tn-stepper__header{display:flex;flex-direction:column;width:280px;padding:16px;border-right:1px solid var(--tn-lines, #e5e7eb)}.tn-stepper--vertical .tn-stepper__step-header{display:flex;flex-direction:row;align-items:center;text-align:left;cursor:pointer;transition:all .2s ease-in-out;padding:var(--step-padding);border-radius:8px;margin-bottom:8px}.tn-stepper--vertical .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:12px;margin-top:0}.tn-stepper--vertical .tn-stepper__connector{width:2px;height:24px;background:var(--tn-lines, #e5e7eb);position:relative;left:calc(var(--step-diameter) / 2 + var(--step-padding));margin-bottom:8px}.tn-stepper--vertical .tn-stepper__content{flex:1;padding:16px}.tn-stepper__step-indicator{display:flex;align-items:center;justify-content:center;width:var(--step-diameter);height:var(--step-diameter);border-radius:50%;background:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-alt-fg1, #495057);font-weight:400;font-size:14px;transition:all .2s ease-in-out;position:relative;transform:scale(.65)}.tn-stepper__step-number{font-weight:400}.tn-stepper__step-label{margin-top:8px}.tn-stepper__step-title{display:block;font-weight:400;font-size:14px;color:var(--tn-fg1, #000000);line-height:1.2}.tn-stepper__step-subtitle{display:block;font-size:12px;color:var(--tn-fg2, #6c757d);margin-top:2px}.tn-stepper__step-header--active .tn-stepper__step-indicator{background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, #ffffff);transform:scale(1)}.tn-stepper__step-header--active .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--completed .tn-stepper__step-indicator{background:var(--tn-green, #28a745);color:#fff}.tn-stepper__step-header--completed .tn-stepper__step-check{font-size:1rem;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-indicator{background:var(--tn-red, #dc3545);color:#fff}.tn-stepper__step-header--error .tn-stepper__step-error{font-size:18px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg1, #000000)}.tn-stepper__step-header--active.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--optional .tn-stepper__step-indicator{border:2px dashed var(--tn-lines, #e5e7eb);background:transparent}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector:after{content:\"\";position:absolute;top:0;left:0;right:0;height:100%;background:var(--tn-green, #28a745);animation:progressFill .3s ease-in-out}.tn-stepper--vertical .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper__content{min-height:200px}.tn-stepper__step-content{padding:16px;background:var(--tn-bg1, #ffffff);border-radius:8px;border:1px solid var(--tn-lines, #e5e7eb)}@keyframes progressFill{0%{width:0}to{width:100%}}.tn-stepper__step-header:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-stepper__step-header:focus:not(:focus-visible){outline:none}@media(max-width:780px){.tn-stepper--vertical .tn-stepper__header{width:180px;border-right:none;border-bottom:1px solid var(--tn-lines, #e5e7eb);padding:16px 0}.tn-stepper--vertical .tn-stepper__step-header{flex-direction:column;align-items:center;text-align:center;min-width:80px;padding:8px 4px}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:0;margin-top:8px}.tn-stepper--vertical .tn-stepper__connector{display:none}}@media(max-width:780px){.tn-stepper .tn-stepper__step-label{display:none}.tn-stepper .tn-stepper__step-indicator{width:var(--step-diameter-sm);height:var(--step-diameter-sm);font-size:12px}.tn-stepper--horizontal .tn-stepper__header{padding:0 8px}.tn-stepper--horizontal .tn-stepper__connector{top:calc(var(--step-diameter-sm) / 2);margin:0 8px}.tn-stepper--vertical .tn-stepper__header{width:60px}.tn-stepper--vertical .tn-stepper__step-header{padding:4px;margin-bottom:4px}.tn-stepper--vertical .tn-stepper__connector{left:calc(var(--step-diameter-sm) / 2 + 4px);height:16px;margin-bottom:4px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }], animations: [
12133
12577
  trigger('stepTransition', [
12134
12578
  transition(':enter', [
12135
12579
  style({ opacity: 0, transform: 'translateX(50px)' }),
@@ -12149,7 +12593,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
12149
12593
  ])
12150
12594
  ], host: {
12151
12595
  '(window:resize)': 'onWindowResize($event)'
12152
- }, template: "<div class=\"tn-stepper\"\n [class.tn-stepper--horizontal]=\"orientation() === 'horizontal' || (orientation() === 'auto' && isWideScreen())\"\n [class.tn-stepper--vertical]=\"orientation() === 'vertical' || (orientation() === 'auto' && !isWideScreen())\"\n [tnTestId]=\"testId()\">\n\n <!-- Step Headers -->\n <div class=\"tn-stepper__header\">\n @for (step of steps(); track $index; let i = $index) {\n <div class=\"tn-stepper__step-header\"\n tabindex=\"0\"\n role=\"button\"\n [class.tn-stepper__step-header--active]=\"selectedIndex() === i\"\n [class.tn-stepper__step-header--completed]=\"step.completed()\"\n [class.tn-stepper__step-header--error]=\"step.hasError()\"\n [class.tn-stepper__step-header--optional]=\"step.optional()\"\n (click)=\"selectStep(i)\"\n (keydown.enter)=\"selectStep(i)\"\n (keydown.space)=\"selectStep(i)\">\n\n <!-- Step Number/Icon -->\n <div class=\"tn-stepper__step-indicator\">\n @if (step.completed() && !step.hasError()) {\n <span class=\"tn-stepper__step-check\">\u2713</span>\n }\n @if (step.hasError()) {\n <span class=\"tn-stepper__step-error\">!</span>\n }\n @if (!step.completed() && !step.hasError()) {\n @if (step.icon()) {\n <span class=\"tn-stepper__step-icon\">{{ step.icon() }}</span>\n } @else {\n <span class=\"tn-stepper__step-number\">{{ i + 1 }}</span>\n }\n }\n </div>\n\n <!-- Step Label -->\n <div class=\"tn-stepper__step-label\">\n <div class=\"tn-stepper__step-title\">{{ step.label() }}</div>\n @if (step.optional()) {\n <span class=\"tn-stepper__step-subtitle\">Optional</span>\n }\n </div>\n </div>\n\n <!-- Connector Line (except for last step) -->\n @if (i < steps().length - 1) {\n <div class=\"tn-stepper__connector\"></div>\n }\n }\n </div>\n\n <!-- Step Content -->\n <div class=\"tn-stepper__content\">\n @for (step of steps(); track $index; let i = $index) {\n @if (selectedIndex() === i) {\n <div class=\"tn-stepper__step-content\"\n [@stepTransition]=\"selectedIndex()\">\n <ng-container *ngTemplateOutlet=\"step.content()\" />\n </div>\n }\n }\n </div>\n</div>", styles: [".tn-stepper{display:flex;font-family:inherit;--step-diameter: 48px;--step-diameter-sm: 32px;--step-padding: 12px}.tn-stepper--horizontal{flex-direction:column}.tn-stepper--horizontal .tn-stepper__header{display:flex;justify-content:center;margin-bottom:32px;padding:0 16px}.tn-stepper--horizontal .tn-stepper__step-header{display:flex;flex-direction:column;align-items:center;text-align:center;cursor:pointer;transition:all .2s ease-in-out}.tn-stepper--horizontal .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--horizontal .tn-stepper__connector{flex:1;height:2px;background:var(--tn-lines, #e5e7eb);margin:0 16px;position:relative;top:calc(var(--step-diameter) / 2)}.tn-stepper--vertical{flex-direction:row}.tn-stepper--vertical .tn-stepper__header{display:flex;flex-direction:column;width:280px;padding:16px;border-right:1px solid var(--tn-lines, #e5e7eb)}.tn-stepper--vertical .tn-stepper__step-header{display:flex;flex-direction:row;align-items:center;text-align:left;cursor:pointer;transition:all .2s ease-in-out;padding:var(--step-padding);border-radius:8px;margin-bottom:8px}.tn-stepper--vertical .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:12px;margin-top:0}.tn-stepper--vertical .tn-stepper__connector{width:2px;height:24px;background:var(--tn-lines, #e5e7eb);position:relative;left:calc(var(--step-diameter) / 2 + var(--step-padding));margin-bottom:8px}.tn-stepper--vertical .tn-stepper__content{flex:1;padding:16px}.tn-stepper__step-indicator{display:flex;align-items:center;justify-content:center;width:var(--step-diameter);height:var(--step-diameter);border-radius:50%;background:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-alt-fg1, #495057);font-weight:400;font-size:14px;transition:all .2s ease-in-out;position:relative;transform:scale(.65)}.tn-stepper__step-number{font-weight:400}.tn-stepper__step-label{margin-top:8px}.tn-stepper__step-title{display:block;font-weight:400;font-size:14px;color:var(--tn-fg1, #000000);line-height:1.2}.tn-stepper__step-subtitle{display:block;font-size:12px;color:var(--tn-fg2, #6c757d);margin-top:2px}.tn-stepper__step-header--active .tn-stepper__step-indicator{background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, #ffffff);transform:scale(1)}.tn-stepper__step-header--active .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--completed .tn-stepper__step-indicator{background:var(--tn-green, #28a745);color:#fff}.tn-stepper__step-header--completed .tn-stepper__step-check{font-size:16px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-indicator{background:var(--tn-red, #dc3545);color:#fff}.tn-stepper__step-header--error .tn-stepper__step-error{font-size:18px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg1, #000000)}.tn-stepper__step-header--active.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--optional .tn-stepper__step-indicator{border:2px dashed var(--tn-lines, #e5e7eb);background:transparent}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector:after{content:\"\";position:absolute;top:0;left:0;right:0;height:100%;background:var(--tn-green, #28a745);animation:progressFill .3s ease-in-out}.tn-stepper--vertical .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper__content{min-height:200px}.tn-stepper__step-content{padding:16px;background:var(--tn-bg1, #ffffff);border-radius:8px;border:1px solid var(--tn-lines, #e5e7eb)}@keyframes progressFill{0%{width:0}to{width:100%}}.tn-stepper__step-header:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-stepper__step-header:focus:not(:focus-visible){outline:none}@media(max-width:780px){.tn-stepper--vertical .tn-stepper__header{width:180px;border-right:none;border-bottom:1px solid var(--tn-lines, #e5e7eb);padding:16px 0}.tn-stepper--vertical .tn-stepper__step-header{flex-direction:column;align-items:center;text-align:center;min-width:80px;padding:8px 4px}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:0;margin-top:8px}.tn-stepper--vertical .tn-stepper__connector{display:none}}@media(max-width:780px){.tn-stepper .tn-stepper__step-label{display:none}.tn-stepper .tn-stepper__step-indicator{width:var(--step-diameter-sm);height:var(--step-diameter-sm);font-size:12px}.tn-stepper--horizontal .tn-stepper__header{padding:0 8px}.tn-stepper--horizontal .tn-stepper__connector{top:calc(var(--step-diameter-sm) / 2);margin:0 8px}.tn-stepper--vertical .tn-stepper__header{width:60px}.tn-stepper--vertical .tn-stepper__step-header{padding:4px;margin-bottom:4px}.tn-stepper--vertical .tn-stepper__connector{left:calc(var(--step-diameter-sm) / 2 + 4px);height:16px;margin-bottom:4px}}\n"] }]
12596
+ }, template: "<div class=\"tn-stepper\"\n [class.tn-stepper--horizontal]=\"orientation() === 'horizontal' || (orientation() === 'auto' && isWideScreen())\"\n [class.tn-stepper--vertical]=\"orientation() === 'vertical' || (orientation() === 'auto' && !isWideScreen())\"\n [tnTestId]=\"testId()\">\n\n <!-- Step Headers -->\n <div class=\"tn-stepper__header\">\n @for (step of steps(); track $index; let i = $index) {\n <div class=\"tn-stepper__step-header\"\n tabindex=\"0\"\n role=\"button\"\n [class.tn-stepper__step-header--active]=\"selectedIndex() === i\"\n [class.tn-stepper__step-header--completed]=\"step.completed()\"\n [class.tn-stepper__step-header--error]=\"step.hasError()\"\n [class.tn-stepper__step-header--optional]=\"step.optional()\"\n (click)=\"selectStep(i)\"\n (keydown.enter)=\"selectStep(i)\"\n (keydown.space)=\"selectStep(i)\">\n\n <!-- Step Number/Icon -->\n <div class=\"tn-stepper__step-indicator\">\n @if (step.completed() && !step.hasError()) {\n <span class=\"tn-stepper__step-check\">\u2713</span>\n }\n @if (step.hasError()) {\n <span class=\"tn-stepper__step-error\">!</span>\n }\n @if (!step.completed() && !step.hasError()) {\n @if (step.icon()) {\n <span class=\"tn-stepper__step-icon\">{{ step.icon() }}</span>\n } @else {\n <span class=\"tn-stepper__step-number\">{{ i + 1 }}</span>\n }\n }\n </div>\n\n <!-- Step Label -->\n <div class=\"tn-stepper__step-label\">\n <div class=\"tn-stepper__step-title\">{{ step.label() }}</div>\n @if (step.optional()) {\n <span class=\"tn-stepper__step-subtitle\">Optional</span>\n }\n </div>\n </div>\n\n <!-- Connector Line (except for last step) -->\n @if (i < steps().length - 1) {\n <div class=\"tn-stepper__connector\"></div>\n }\n }\n </div>\n\n <!-- Step Content -->\n <div class=\"tn-stepper__content\">\n @for (step of steps(); track $index; let i = $index) {\n @if (selectedIndex() === i) {\n <div class=\"tn-stepper__step-content\"\n [@stepTransition]=\"selectedIndex()\">\n <ng-container *ngTemplateOutlet=\"step.content()\" />\n </div>\n }\n }\n </div>\n</div>", styles: [".tn-stepper{display:flex;font-family:inherit;--step-diameter: 48px;--step-diameter-sm: 32px;--step-padding: 12px}.tn-stepper--horizontal{flex-direction:column}.tn-stepper--horizontal .tn-stepper__header{display:flex;justify-content:center;margin-bottom:32px;padding:0 16px}.tn-stepper--horizontal .tn-stepper__step-header{display:flex;flex-direction:column;align-items:center;text-align:center;cursor:pointer;transition:all .2s ease-in-out}.tn-stepper--horizontal .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--horizontal .tn-stepper__connector{flex:1;height:2px;background:var(--tn-lines, #e5e7eb);margin:0 16px;position:relative;top:calc(var(--step-diameter) / 2)}.tn-stepper--vertical{flex-direction:row}.tn-stepper--vertical .tn-stepper__header{display:flex;flex-direction:column;width:280px;padding:16px;border-right:1px solid var(--tn-lines, #e5e7eb)}.tn-stepper--vertical .tn-stepper__step-header{display:flex;flex-direction:row;align-items:center;text-align:left;cursor:pointer;transition:all .2s ease-in-out;padding:var(--step-padding);border-radius:8px;margin-bottom:8px}.tn-stepper--vertical .tn-stepper__step-header:not(.tn-stepper__step-header--active):hover .tn-stepper__step-indicator{transform:scale(.95)}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:12px;margin-top:0}.tn-stepper--vertical .tn-stepper__connector{width:2px;height:24px;background:var(--tn-lines, #e5e7eb);position:relative;left:calc(var(--step-diameter) / 2 + var(--step-padding));margin-bottom:8px}.tn-stepper--vertical .tn-stepper__content{flex:1;padding:16px}.tn-stepper__step-indicator{display:flex;align-items:center;justify-content:center;width:var(--step-diameter);height:var(--step-diameter);border-radius:50%;background:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-alt-fg1, #495057);font-weight:400;font-size:14px;transition:all .2s ease-in-out;position:relative;transform:scale(.65)}.tn-stepper__step-number{font-weight:400}.tn-stepper__step-label{margin-top:8px}.tn-stepper__step-title{display:block;font-weight:400;font-size:14px;color:var(--tn-fg1, #000000);line-height:1.2}.tn-stepper__step-subtitle{display:block;font-size:12px;color:var(--tn-fg2, #6c757d);margin-top:2px}.tn-stepper__step-header--active .tn-stepper__step-indicator{background:var(--tn-primary, #007bff);color:var(--tn-primary-txt, #ffffff);transform:scale(1)}.tn-stepper__step-header--active .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--completed .tn-stepper__step-indicator{background:var(--tn-green, #28a745);color:#fff}.tn-stepper__step-header--completed .tn-stepper__step-check{font-size:1rem;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-indicator{background:var(--tn-red, #dc3545);color:#fff}.tn-stepper__step-header--error .tn-stepper__step-error{font-size:18px;font-weight:700}.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg1, #000000)}.tn-stepper__step-header--active.tn-stepper__step-header--error .tn-stepper__step-title{color:var(--tn-fg2, #6c757d);font-weight:600}.tn-stepper__step-header--optional .tn-stepper__step-indicator{border:2px dashed var(--tn-lines, #e5e7eb);background:transparent}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper--horizontal .tn-stepper__step-header--completed+.tn-stepper__connector:after{content:\"\";position:absolute;top:0;left:0;right:0;height:100%;background:var(--tn-green, #28a745);animation:progressFill .3s ease-in-out}.tn-stepper--vertical .tn-stepper__step-header--completed+.tn-stepper__connector{background:var(--tn-green, #28a745)}.tn-stepper__content{min-height:200px}.tn-stepper__step-content{padding:16px;background:var(--tn-bg1, #ffffff);border-radius:8px;border:1px solid var(--tn-lines, #e5e7eb)}@keyframes progressFill{0%{width:0}to{width:100%}}.tn-stepper__step-header:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-stepper__step-header:focus:not(:focus-visible){outline:none}@media(max-width:780px){.tn-stepper--vertical .tn-stepper__header{width:180px;border-right:none;border-bottom:1px solid var(--tn-lines, #e5e7eb);padding:16px 0}.tn-stepper--vertical .tn-stepper__step-header{flex-direction:column;align-items:center;text-align:center;min-width:80px;padding:8px 4px}.tn-stepper--vertical .tn-stepper__step-header .tn-stepper__step-label{margin-left:0;margin-top:8px}.tn-stepper--vertical .tn-stepper__connector{display:none}}@media(max-width:780px){.tn-stepper .tn-stepper__step-label{display:none}.tn-stepper .tn-stepper__step-indicator{width:var(--step-diameter-sm);height:var(--step-diameter-sm);font-size:12px}.tn-stepper--horizontal .tn-stepper__header{padding:0 8px}.tn-stepper--horizontal .tn-stepper__connector{top:calc(var(--step-diameter-sm) / 2);margin:0 8px}.tn-stepper--vertical .tn-stepper__header{width:60px}.tn-stepper--vertical .tn-stepper__step-header{padding:4px;margin-bottom:4px}.tn-stepper--vertical .tn-stepper__connector{left:calc(var(--step-diameter-sm) / 2 + 4px);height:16px;margin-bottom:4px}}\n"] }]
12153
12597
  }], ctorParameters: () => [], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], completed: [{ type: i0.Output, args: ["completed"] }], steps: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnStepComponent), { ...{ descendants: true }, isSignal: true }] }] } });
12154
12598
 
12155
12599
  /**
@@ -12505,7 +12949,7 @@ class TnFilePickerPopupComponent {
12505
12949
  return `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}/${date.getFullYear()} ${timePart}`;
12506
12950
  }
12507
12951
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFilePickerPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12508
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFilePickerPopupComponent, isStandalone: true, selector: "tn-file-picker-popup", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, multiSelect: { classPropertyName: "multiSelect", publicName: "multiSelect", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, allowDatasetCreate: { classPropertyName: "allowDatasetCreate", publicName: "allowDatasetCreate", isSignal: true, isRequired: false, transformFunction: null }, allowZvolCreate: { classPropertyName: "allowZvolCreate", publicName: "allowZvolCreate", isSignal: true, isRequired: false, transformFunction: null }, currentPath: { classPropertyName: "currentPath", publicName: "currentPath", isSignal: true, isRequired: false, transformFunction: null }, fileItems: { classPropertyName: "fileItems", publicName: "fileItems", isSignal: true, isRequired: false, transformFunction: null }, selectedItems: { classPropertyName: "selectedItems", publicName: "selectedItems", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, creationLoading: { classPropertyName: "creationLoading", publicName: "creationLoading", isSignal: true, isRequired: false, transformFunction: null }, fileExtensions: { classPropertyName: "fileExtensions", publicName: "fileExtensions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", itemDoubleClick: "itemDoubleClick", pathNavigate: "pathNavigate", createFolder: "createFolder", clearSelection: "clearSelection", close: "close", submit: "submit", cancel: "cancel", submitFolderName: "submitFolderName", cancelFolderCreation: "cancelFolderCreation" }, host: { classAttribute: "tn-file-picker-popup" }, ngImport: i0, template: "<!-- Header with breadcrumb navigation -->\n<div class=\"tn-file-picker-header\">\n <nav class=\"tn-file-picker-breadcrumb\" aria-label=\"File path\">\n @for (segment of currentPath() | tnTruncatePath; track $index; let last = $last) {\n <button\n class=\"breadcrumb-segment\"\n [class.current]=\"last\"\n [class.parent-nav]=\"segment.name === '..'\"\n [disabled]=\"last\"\n (click)=\"navigateToPath(segment.path)\">\n {{ segment.name }}\n </button>\n }\n </nav>\n\n <div class=\"tn-file-picker-actions\">\n @if (allowCreate()) {\n <tn-button\n variant=\"outline\"\n label=\"New Folder\"\n [disabled]=\"isCreateDisabled()\"\n (onClick)=\"onCreateFolder()\" />\n }\n </div>\n</div>\n\n<!-- Loading indicator -->\n@if (loading()) {\n <div class=\"tn-file-picker-loading\">\n <tn-icon name=\"loading\" library=\"mdi\" />\n <span>Loading...</span>\n </div>\n}\n\n<!-- File table -->\n@if (!loading()) {\n <div class=\"tn-file-picker-content\">\n <tn-table\n [dataSource]=\"filteredFileItems()\"\n [displayedColumns]=\"multiSelect() ? displayedColumns : displayedColumns.slice(1)\">\n\n <!-- Selection column -->\n @if (multiSelect()) {\n <ng-container tnColumnDef=\"select\">\n <ng-template tnHeaderCellDef>\n <!-- Select all checkbox -->\n </ng-template>\n <ng-template let-item tnCellDef>\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(item)\"\n [disabled]=\"!!item.disabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onItemClick(item)\">\n </ng-template>\n </ng-container>\n }\n\n <!-- Name column -->\n <ng-container tnColumnDef=\"name\">\n <ng-template tnHeaderCellDef>Name</ng-template>\n <ng-template let-item tnCellDef>\n\n <!-- NORMAL MODE: Display name -->\n @if (!item.isCreating) {\n <div\n class=\"file-name-cell\"\n [class.disabled]=\"!!item.disabled\"\n [class.zfs-object]=\"isZfsObject(item)\"\n [attr.tabindex]=\"item.disabled ? null : 0\"\n [attr.role]=\"'button'\"\n (click)=\"onItemClick(item)\"\n (dblclick)=\"onItemDoubleClick(item)\"\n (keydown.enter)=\"onItemDoubleClick(item)\"\n (keydown.space)=\"onItemClick(item)\">\n <tn-icon\n [name]=\"getItemIcon(item)\"\n [library]=\"getItemIconLibrary(item)\"\n [class]=\"'file-icon file-icon-' + item.type\" />\n <span class=\"file-name\">{{ item.name }}</span>\n\n <!-- ZFS badge -->\n @if (isZfsObject(item)) {\n <span\n [class]=\"'zfs-badge zfs-badge-' + item.type\">\n {{ getZfsBadge(item) }}\n </span>\n }\n\n <!-- Permission indicator -->\n @if (item.permissions === 'none') {\n <tn-icon\n name=\"lock\"\n library=\"mdi\"\n class=\"permission-icon\" />\n }\n </div>\n }\n\n <!-- EDIT MODE: Inline name input with error display -->\n @if (item.isCreating) {\n <div class=\"file-name-cell-wrapper\">\n <div class=\"file-name-cell editing\" [class.has-error]=\"!!item.creationError\">\n <tn-icon\n name=\"folder\"\n library=\"mdi\"\n class=\"file-icon file-icon-folder\" />\n <input\n #folderNameInput\n type=\"text\"\n role=\"textbox\"\n aria-label=\"Folder name\"\n class=\"folder-name-input\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [class.error]=\"!!item.creationError\"\n [value]=\"item.name\"\n [disabled]=\"creationLoading()\"\n [attr.data-autofocus]=\"true\"\n (keydown)=\"onFolderNameKeyDown($event, item)\"\n (blur)=\"onFolderNameInputBlur($event, item)\">\n\n <!-- Loading indicator during submission -->\n @if (creationLoading()) {\n <tn-icon\n name=\"loading\"\n library=\"mdi\"\n class=\"creation-loading-icon\" />\n }\n </div>\n\n <!-- Inline error message -->\n @if (item.creationError) {\n <div class=\"folder-creation-error\">\n <tn-icon name=\"alert-circle\" library=\"mdi\" class=\"error-icon\" />\n <span class=\"error-text\">{{ item.creationError }}</span>\n </div>\n }\n </div>\n }\n\n </ng-template>\n </ng-container>\n\n <!-- Size column -->\n <ng-container tnColumnDef=\"size\">\n <ng-template tnHeaderCellDef>Size</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.size !== undefined) {\n <span>{{ item.size | tnFileSize }}</span>\n }\n @if (item.size === undefined && item.type === 'folder') {\n <span class=\"folder-indicator\">--</span>\n }\n </ng-template>\n </ng-container>\n\n <!-- Modified column -->\n <ng-container tnColumnDef=\"modified\">\n <ng-template tnHeaderCellDef>Modified</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.modified) {\n <span>{{ formatDate(item.modified) }}</span>\n }\n </ng-template>\n </ng-container>\n\n\n </tn-table>\n\n <!-- Empty state -->\n @if (filteredFileItems().length === 0) {\n <div class=\"empty-state\">\n <tn-icon name=\"folder-open\" library=\"mdi\" customSize=\"48px\" />\n <p>No items found</p>\n </div>\n }\n </div>\n}\n\n<!-- Footer -->\n@if (!loading()) {\n <div class=\"tn-file-picker-footer\">\n @if (selectedItems().length > 0) {\n <span class=\"selection-count\">\n {{ selectedItems().length }} item{{ selectedItems().length !== 1 ? 's' : '' }} selected\n </span>\n }\n @if (selectedItems().length === 0) {\n <span class=\"selection-count\">\n No items selected\n </span>\n }\n <div class=\"footer-actions\">\n <tn-button\n label=\"Select\"\n [disabled]=\"selectedItems().length === 0\"\n (onClick)=\"onSubmit()\" />\n </div>\n </div>\n}", styles: [":host{display:block;background:var(--tn-bg1, white);color:var(--tn-fg1, #333);padding:0;box-shadow:0 4px 16px #0000001f,0 1px 4px #00000014;border-radius:8px;border:1px solid var(--tn-lines, #e0e0e0);min-width:400px;max-width:600px;min-height:500px;max-height:600px;font-family:var(--tn-font-family-body);display:flex;flex-direction:column;overflow:hidden}.tn-file-picker-header{display:flex;align-items:center;justify-content:space-between;padding:var(--tn-content-padding, 24px);padding-bottom:16px;border-bottom:1px solid var(--tn-lines)}.tn-file-picker-breadcrumb{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.tn-file-picker-breadcrumb .breadcrumb-segment{background:transparent;border:none;color:var(--tn-primary);cursor:pointer;padding:4px 8px;border-radius:4px;font-size:.875rem;white-space:nowrap;transition:background-color .15s ease-in-out}.tn-file-picker-breadcrumb .breadcrumb-segment:hover:not(:disabled){background:var(--tn-bg2)}.tn-file-picker-breadcrumb .breadcrumb-segment:disabled,.tn-file-picker-breadcrumb .breadcrumb-segment.current{color:var(--tn-fg1);cursor:default;font-weight:500}.tn-file-picker-breadcrumb .breadcrumb-segment:not(:last-child):after{content:\"/\";margin-left:8px;color:var(--tn-alt-fg1)}.tn-file-picker-actions{display:flex;align-items:center;gap:8px}.tn-file-picker-actions tn-button{font-size:.875rem}.tn-file-picker-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px;color:var(--tn-fg2)}.tn-file-picker-loading tn-icon{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tn-file-picker-content{flex:1;min-height:0;overflow-y:auto}.file-list-viewport{width:100%;height:100%}.file-list-viewport .cdk-virtual-scroll-content-wrapper{width:100%}tn-table{width:100%}tn-table th,tn-table .tn-table__header-cell{font-weight:600;color:var(--tn-fg1);padding:12px 16px;border-bottom:2px solid var(--tn-lines)}tn-table td,tn-table .tn-table__cell{padding:8px 16px;border-bottom:1px solid var(--tn-lines)}.file-checkbox{display:flex;align-items:center}.file-checkbox input[type=checkbox]{margin:0;width:16px;height:16px}.file-name-cell{display:flex;align-items:center;gap:8px;cursor:pointer}.file-name-cell.disabled{opacity:.5;color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-name{color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-icon{opacity:.6}.file-name-cell.disabled:has(.file-icon-folder),.file-name-cell.disabled:has(.file-icon-dataset),.file-name-cell.disabled:has(.file-icon-mountpoint){cursor:pointer}.file-name-cell.disabled:not(:has(.file-icon-folder)):not(:has(.file-icon-dataset)):not(:has(.file-icon-mountpoint)){cursor:not-allowed}.file-name-cell.editing{display:flex;align-items:center;gap:8px;padding:2px;cursor:default}.file-name-cell.editing .folder-name-input{flex:1;border:2px solid var(--tn-primary, #0066cc);padding:4px 8px;font-size:inherit;font-family:inherit;background:var(--tn-bg1, white);color:var(--tn-fg1, black);outline:none;border-radius:3px;min-width:200px}.file-name-cell.editing .folder-name-input:focus{border-color:var(--tn-primary, #0066cc);box-shadow:0 0 0 3px #0066cc1a}.file-name-cell.editing .folder-name-input.error{border-color:var(--tn-error, #d32f2f)}.file-name-cell.editing .folder-name-input:disabled{opacity:.6;cursor:not-allowed;background:var(--tn-bg2, #f5f5f5)}.file-name-cell.editing .creation-loading-icon{animation:spin 1s linear infinite;color:var(--tn-primary, #0066cc);flex-shrink:0}.file-name-cell-wrapper{display:flex;flex-direction:column;gap:4px}.folder-creation-error{display:flex;align-items:center;gap:6px;padding:4px 8px 4px 36px;margin-bottom:12px;background:#d32f2f1a;border-left:3px solid var(--tn-error, #d32f2f);border-radius:3px;font-size:.875rem;color:var(--tn-error, #d32f2f)}.folder-creation-error .error-icon{flex-shrink:0;width:20px;height:20px}.folder-creation-error .error-text{flex:1}.file-icon{display:flex;align-items:center;justify-content:center;font-size:var(--tn-icon-md, 20px);flex-shrink:0;line-height:1}.file-icon.file-icon-folder{color:var(--tn-primary)}.file-icon.file-icon-dataset{color:var(--tn-blue, #007db3)}.file-icon.file-icon-zvol{color:var(--tn-green, #71BF44)}.file-icon.file-icon-mountpoint{color:var(--tn-orange, #E68D37)}.file-name{flex:1;font-weight:500;line-height:1.4}.zfs-badge{display:inline-flex;align-items:center;background:var(--tn-alt-bg2);color:var(--tn-alt-fg2);font-size:.625rem;font-weight:600;padding:2px 6px;border-radius:12px;text-transform:uppercase;letter-spacing:.5px;line-height:1}.zfs-badge.zfs-badge-dataset{background:var(--tn-blue);color:#fff}.zfs-badge.zfs-badge-zvol{background:var(--tn-green);color:#fff}.zfs-badge.zfs-badge-mountpoint{background:var(--tn-orange);color:#fff}.permission-icon{display:flex;align-items:center;justify-content:center;color:var(--tn-red);font-size:var(--tn-icon-sm, 16px);line-height:1}.file-type{font-size:.875rem;padding:2px 8px;border-radius:12px}.file-type.type-folder{background:var(--tn-alt-bg1);color:var(--tn-alt-fg2)}.file-type.type-file{background:var(--tn-bg2);color:var(--tn-fg2)}.file-type.type-dataset{background:#007db31a;color:var(--tn-blue)}.file-type.type-zvol{background:#71bf441a;color:var(--tn-green)}.file-type.type-mountpoint{background:#e68d371a;color:var(--tn-orange)}.folder-indicator{color:var(--tn-alt-fg1);font-style:italic}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--tn-alt-fg1);text-align:center}.empty-state tn-icon{margin-bottom:16px;opacity:.5}.empty-state p{margin:0;font-size:.875rem}.tn-file-picker-footer{display:flex;align-items:center;justify-content:space-between;padding:16px var(--tn-content-padding, 24px);border-top:1px solid var(--tn-lines);background:var(--tn-bg2);border-bottom-left-radius:8px;border-bottom-right-radius:8px}.selection-count{font-size:.875rem;color:var(--tn-fg2);font-weight:500}.footer-actions{display:flex;gap:8px}@media(prefers-reduced-motion:reduce){.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){:host{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:.875rem}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { 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: TnTableComponent, selector: "tn-table", inputs: ["dataSource", "displayedColumns", "trackBy", "emptyMessage", "emptyIcon", "selectable", "expandable", "bordered"], outputs: ["sortChange", "selectionChange"] }, { kind: "directive", type: TnTableColumnDirective, selector: "[tnColumnDef]", inputs: ["tnColumnDef", "sortable", "width"], exportAs: ["tnColumnDef"] }, { kind: "directive", type: TnHeaderCellDefDirective, selector: "[tnHeaderCellDef]" }, { kind: "directive", type: TnCellDefDirective, selector: "[tnCellDef]" }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: A11yModule }, { kind: "pipe", type: FileSizePipe, name: "tnFileSize" }, { kind: "pipe", type: TruncatePathPipe, name: "tnTruncatePath" }] });
12952
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFilePickerPopupComponent, isStandalone: true, selector: "tn-file-picker-popup", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, multiSelect: { classPropertyName: "multiSelect", publicName: "multiSelect", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, allowDatasetCreate: { classPropertyName: "allowDatasetCreate", publicName: "allowDatasetCreate", isSignal: true, isRequired: false, transformFunction: null }, allowZvolCreate: { classPropertyName: "allowZvolCreate", publicName: "allowZvolCreate", isSignal: true, isRequired: false, transformFunction: null }, currentPath: { classPropertyName: "currentPath", publicName: "currentPath", isSignal: true, isRequired: false, transformFunction: null }, fileItems: { classPropertyName: "fileItems", publicName: "fileItems", isSignal: true, isRequired: false, transformFunction: null }, selectedItems: { classPropertyName: "selectedItems", publicName: "selectedItems", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, creationLoading: { classPropertyName: "creationLoading", publicName: "creationLoading", isSignal: true, isRequired: false, transformFunction: null }, fileExtensions: { classPropertyName: "fileExtensions", publicName: "fileExtensions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", itemDoubleClick: "itemDoubleClick", pathNavigate: "pathNavigate", createFolder: "createFolder", clearSelection: "clearSelection", close: "close", submit: "submit", cancel: "cancel", submitFolderName: "submitFolderName", cancelFolderCreation: "cancelFolderCreation" }, host: { classAttribute: "tn-file-picker-popup" }, ngImport: i0, template: "<!-- Header with breadcrumb navigation -->\n<div class=\"tn-file-picker-header\">\n <nav class=\"tn-file-picker-breadcrumb\" aria-label=\"File path\">\n @for (segment of currentPath() | tnTruncatePath; track $index; let last = $last) {\n <button\n class=\"breadcrumb-segment\"\n [class.current]=\"last\"\n [class.parent-nav]=\"segment.name === '..'\"\n [disabled]=\"last\"\n (click)=\"navigateToPath(segment.path)\">\n {{ segment.name }}\n </button>\n }\n </nav>\n\n <div class=\"tn-file-picker-actions\">\n @if (allowCreate()) {\n <tn-button\n variant=\"outline\"\n label=\"New Folder\"\n [disabled]=\"isCreateDisabled()\"\n (onClick)=\"onCreateFolder()\" />\n }\n </div>\n</div>\n\n<!-- Loading indicator -->\n@if (loading()) {\n <div class=\"tn-file-picker-loading\">\n <tn-icon name=\"loading\" library=\"mdi\" />\n <span>Loading...</span>\n </div>\n}\n\n<!-- File table -->\n@if (!loading()) {\n <div class=\"tn-file-picker-content\">\n <tn-table\n [dataSource]=\"filteredFileItems()\"\n [displayedColumns]=\"multiSelect() ? displayedColumns : displayedColumns.slice(1)\">\n\n <!-- Selection column -->\n @if (multiSelect()) {\n <ng-container tnColumnDef=\"select\">\n <ng-template tnHeaderCellDef>\n <!-- Select all checkbox -->\n </ng-template>\n <ng-template let-item tnCellDef>\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(item)\"\n [disabled]=\"!!item.disabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onItemClick(item)\">\n </ng-template>\n </ng-container>\n }\n\n <!-- Name column -->\n <ng-container tnColumnDef=\"name\">\n <ng-template tnHeaderCellDef>Name</ng-template>\n <ng-template let-item tnCellDef>\n\n <!-- NORMAL MODE: Display name -->\n @if (!item.isCreating) {\n <div\n class=\"file-name-cell\"\n [class.disabled]=\"!!item.disabled\"\n [class.zfs-object]=\"isZfsObject(item)\"\n [attr.tabindex]=\"item.disabled ? null : 0\"\n [attr.role]=\"'button'\"\n (click)=\"onItemClick(item)\"\n (dblclick)=\"onItemDoubleClick(item)\"\n (keydown.enter)=\"onItemDoubleClick(item)\"\n (keydown.space)=\"onItemClick(item)\">\n <tn-icon\n [name]=\"getItemIcon(item)\"\n [library]=\"getItemIconLibrary(item)\"\n [class]=\"'file-icon file-icon-' + item.type\" />\n <span class=\"file-name\">{{ item.name }}</span>\n\n <!-- ZFS badge -->\n @if (isZfsObject(item)) {\n <span\n [class]=\"'zfs-badge zfs-badge-' + item.type\">\n {{ getZfsBadge(item) }}\n </span>\n }\n\n <!-- Permission indicator -->\n @if (item.permissions === 'none') {\n <tn-icon\n name=\"lock\"\n library=\"mdi\"\n class=\"permission-icon\" />\n }\n </div>\n }\n\n <!-- EDIT MODE: Inline name input with error display -->\n @if (item.isCreating) {\n <div class=\"file-name-cell-wrapper\">\n <div class=\"file-name-cell editing\" [class.has-error]=\"!!item.creationError\">\n <tn-icon\n name=\"folder\"\n library=\"mdi\"\n class=\"file-icon file-icon-folder\" />\n <input\n #folderNameInput\n type=\"text\"\n role=\"textbox\"\n aria-label=\"Folder name\"\n class=\"folder-name-input\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [class.error]=\"!!item.creationError\"\n [value]=\"item.name\"\n [disabled]=\"creationLoading()\"\n [attr.data-autofocus]=\"true\"\n (keydown)=\"onFolderNameKeyDown($event, item)\"\n (blur)=\"onFolderNameInputBlur($event, item)\">\n\n <!-- Loading indicator during submission -->\n @if (creationLoading()) {\n <tn-icon\n name=\"loading\"\n library=\"mdi\"\n class=\"creation-loading-icon\" />\n }\n </div>\n\n <!-- Inline error message -->\n @if (item.creationError) {\n <div class=\"folder-creation-error\">\n <tn-icon name=\"alert-circle\" library=\"mdi\" class=\"error-icon\" />\n <span class=\"error-text\">{{ item.creationError }}</span>\n </div>\n }\n </div>\n }\n\n </ng-template>\n </ng-container>\n\n <!-- Size column -->\n <ng-container tnColumnDef=\"size\">\n <ng-template tnHeaderCellDef>Size</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.size !== undefined) {\n <span>{{ item.size | tnFileSize }}</span>\n }\n @if (item.size === undefined && item.type === 'folder') {\n <span class=\"folder-indicator\">--</span>\n }\n </ng-template>\n </ng-container>\n\n <!-- Modified column -->\n <ng-container tnColumnDef=\"modified\">\n <ng-template tnHeaderCellDef>Modified</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.modified) {\n <span>{{ formatDate(item.modified) }}</span>\n }\n </ng-template>\n </ng-container>\n\n\n </tn-table>\n\n <!-- Empty state -->\n @if (filteredFileItems().length === 0) {\n <div class=\"empty-state\">\n <tn-icon name=\"folder-open\" library=\"mdi\" customSize=\"48px\" />\n <p>No items found</p>\n </div>\n }\n </div>\n}\n\n<!-- Footer -->\n@if (!loading()) {\n <div class=\"tn-file-picker-footer\">\n @if (selectedItems().length > 0) {\n <span class=\"selection-count\">\n {{ selectedItems().length }} item{{ selectedItems().length !== 1 ? 's' : '' }} selected\n </span>\n }\n @if (selectedItems().length === 0) {\n <span class=\"selection-count\">\n No items selected\n </span>\n }\n <div class=\"footer-actions\">\n <tn-button\n label=\"Select\"\n [disabled]=\"selectedItems().length === 0\"\n (onClick)=\"onSubmit()\" />\n </div>\n </div>\n}", styles: [":host{display:block;background:var(--tn-bg1, white);color:var(--tn-fg1, #333);padding:0;box-shadow:0 4px 16px #0000001f,0 1px 4px #00000014;border-radius:8px;border:1px solid var(--tn-lines, #e0e0e0);min-width:400px;max-width:600px;min-height:500px;max-height:600px;font-family:var(--tn-font-family-body);display:flex;flex-direction:column;overflow:hidden}.tn-file-picker-header{display:flex;align-items:center;justify-content:space-between;padding:var(--tn-content-padding, 24px);padding-bottom:16px;border-bottom:1px solid var(--tn-lines)}.tn-file-picker-breadcrumb{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.tn-file-picker-breadcrumb .breadcrumb-segment{background:transparent;border:none;color:var(--tn-primary);cursor:pointer;padding:4px 8px;border-radius:4px;font-size:1rem;white-space:nowrap;transition:background-color .15s ease-in-out}.tn-file-picker-breadcrumb .breadcrumb-segment:hover:not(:disabled){background:var(--tn-bg2)}.tn-file-picker-breadcrumb .breadcrumb-segment:disabled,.tn-file-picker-breadcrumb .breadcrumb-segment.current{color:var(--tn-fg1);cursor:default;font-weight:500}.tn-file-picker-breadcrumb .breadcrumb-segment:not(:last-child):after{content:\"/\";margin-left:8px;color:var(--tn-alt-fg1)}.tn-file-picker-actions{display:flex;align-items:center;gap:8px}.tn-file-picker-actions tn-button{font-size:1rem}.tn-file-picker-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px;color:var(--tn-fg2)}.tn-file-picker-loading tn-icon{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tn-file-picker-content{flex:1;min-height:0;overflow-y:auto}.file-list-viewport{width:100%;height:100%}.file-list-viewport .cdk-virtual-scroll-content-wrapper{width:100%}tn-table{width:100%}tn-table th,tn-table .tn-table__header-cell{font-weight:600;color:var(--tn-fg1);padding:12px 16px;border-bottom:2px solid var(--tn-lines)}tn-table td,tn-table .tn-table__cell{padding:8px 16px;border-bottom:1px solid var(--tn-lines)}.file-checkbox{display:flex;align-items:center}.file-checkbox input[type=checkbox]{margin:0;width:16px;height:16px}.file-name-cell{display:flex;align-items:center;gap:8px;cursor:pointer}.file-name-cell.disabled{opacity:.5;color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-name{color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-icon{opacity:.6}.file-name-cell.disabled:has(.file-icon-folder),.file-name-cell.disabled:has(.file-icon-dataset),.file-name-cell.disabled:has(.file-icon-mountpoint){cursor:pointer}.file-name-cell.disabled:not(:has(.file-icon-folder)):not(:has(.file-icon-dataset)):not(:has(.file-icon-mountpoint)){cursor:not-allowed}.file-name-cell.editing{display:flex;align-items:center;gap:8px;padding:2px;cursor:default}.file-name-cell.editing .folder-name-input{flex:1;border:2px solid var(--tn-primary, #0066cc);padding:4px 8px;font-size:inherit;font-family:inherit;background:var(--tn-bg1, white);color:var(--tn-fg1, black);outline:none;border-radius:3px;min-width:200px}.file-name-cell.editing .folder-name-input:focus{border-color:var(--tn-primary, #0066cc);box-shadow:0 0 0 3px #0066cc1a}.file-name-cell.editing .folder-name-input.error{border-color:var(--tn-error, #d32f2f)}.file-name-cell.editing .folder-name-input:disabled{opacity:.6;cursor:not-allowed;background:var(--tn-bg2, #f5f5f5)}.file-name-cell.editing .creation-loading-icon{animation:spin 1s linear infinite;color:var(--tn-primary, #0066cc);flex-shrink:0}.file-name-cell-wrapper{display:flex;flex-direction:column;gap:4px}.folder-creation-error{display:flex;align-items:center;gap:6px;padding:4px 8px 4px 36px;margin-bottom:12px;background:#d32f2f1a;border-left:3px solid var(--tn-error, #d32f2f);border-radius:3px;font-size:1rem;color:var(--tn-error, #d32f2f)}.folder-creation-error .error-icon{flex-shrink:0;width:20px;height:20px}.folder-creation-error .error-text{flex:1}.file-icon{display:flex;align-items:center;justify-content:center;font-size:var(--tn-icon-md, 20px);flex-shrink:0;line-height:1}.file-icon.file-icon-folder{color:var(--tn-primary)}.file-icon.file-icon-dataset{color:var(--tn-blue, #007db3)}.file-icon.file-icon-zvol{color:var(--tn-green, #71BF44)}.file-icon.file-icon-mountpoint{color:var(--tn-orange, #E68D37)}.file-name{flex:1;font-weight:500;line-height:1.4}.zfs-badge{display:inline-flex;align-items:center;background:var(--tn-alt-bg2);color:var(--tn-alt-fg2);font-size:.625rem;font-weight:600;padding:2px 6px;border-radius:12px;text-transform:uppercase;letter-spacing:.5px;line-height:1}.zfs-badge.zfs-badge-dataset{background:var(--tn-blue);color:#fff}.zfs-badge.zfs-badge-zvol{background:var(--tn-green);color:#fff}.zfs-badge.zfs-badge-mountpoint{background:var(--tn-orange);color:#fff}.permission-icon{display:flex;align-items:center;justify-content:center;color:var(--tn-red);font-size:var(--tn-icon-sm, 16px);line-height:1}.file-type{font-size:1rem;padding:2px 8px;border-radius:12px}.file-type.type-folder{background:var(--tn-alt-bg1);color:var(--tn-alt-fg2)}.file-type.type-file{background:var(--tn-bg2);color:var(--tn-fg2)}.file-type.type-dataset{background:#007db31a;color:var(--tn-blue)}.file-type.type-zvol{background:#71bf441a;color:var(--tn-green)}.file-type.type-mountpoint{background:#e68d371a;color:var(--tn-orange)}.folder-indicator{color:var(--tn-alt-fg1);font-style:italic}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--tn-alt-fg1);text-align:center}.empty-state tn-icon{margin-bottom:16px;opacity:.5}.empty-state p{margin:0;font-size:1rem}.tn-file-picker-footer{display:flex;align-items:center;justify-content:space-between;padding:16px var(--tn-content-padding, 24px);border-top:1px solid var(--tn-lines);background:var(--tn-bg2);border-bottom-left-radius:8px;border-bottom-right-radius:8px}.selection-count{font-size:1rem;color:var(--tn-fg2);font-weight:500}.footer-actions{display:flex;gap:8px}@media(prefers-reduced-motion:reduce){.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){:host{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:1rem}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { 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: TnTableComponent, selector: "tn-table", inputs: ["dataSource", "displayedColumns", "trackBy", "emptyMessage", "emptyIcon", "selectable", "expandable", "bordered", "activeRow", "activeBg", "activeIndicator", "loading", "loadingMessage", "clickable"], outputs: ["sortChange", "selectionChange", "rowClick"] }, { kind: "directive", type: TnTableColumnDirective, selector: "[tnColumnDef]", inputs: ["tnColumnDef", "sortable", "width"], exportAs: ["tnColumnDef"] }, { kind: "directive", type: TnHeaderCellDefDirective, selector: "[tnHeaderCellDef]" }, { kind: "directive", type: TnCellDefDirective, selector: "[tnCellDef]" }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: A11yModule }, { kind: "pipe", type: FileSizePipe, name: "tnFileSize" }, { kind: "pipe", type: TruncatePathPipe, name: "tnTruncatePath" }] });
12509
12953
  }
12510
12954
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFilePickerPopupComponent, decorators: [{
12511
12955
  type: Component,
@@ -12522,7 +12966,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
12522
12966
  TruncatePathPipe
12523
12967
  ], host: {
12524
12968
  'class': 'tn-file-picker-popup'
12525
- }, template: "<!-- Header with breadcrumb navigation -->\n<div class=\"tn-file-picker-header\">\n <nav class=\"tn-file-picker-breadcrumb\" aria-label=\"File path\">\n @for (segment of currentPath() | tnTruncatePath; track $index; let last = $last) {\n <button\n class=\"breadcrumb-segment\"\n [class.current]=\"last\"\n [class.parent-nav]=\"segment.name === '..'\"\n [disabled]=\"last\"\n (click)=\"navigateToPath(segment.path)\">\n {{ segment.name }}\n </button>\n }\n </nav>\n\n <div class=\"tn-file-picker-actions\">\n @if (allowCreate()) {\n <tn-button\n variant=\"outline\"\n label=\"New Folder\"\n [disabled]=\"isCreateDisabled()\"\n (onClick)=\"onCreateFolder()\" />\n }\n </div>\n</div>\n\n<!-- Loading indicator -->\n@if (loading()) {\n <div class=\"tn-file-picker-loading\">\n <tn-icon name=\"loading\" library=\"mdi\" />\n <span>Loading...</span>\n </div>\n}\n\n<!-- File table -->\n@if (!loading()) {\n <div class=\"tn-file-picker-content\">\n <tn-table\n [dataSource]=\"filteredFileItems()\"\n [displayedColumns]=\"multiSelect() ? displayedColumns : displayedColumns.slice(1)\">\n\n <!-- Selection column -->\n @if (multiSelect()) {\n <ng-container tnColumnDef=\"select\">\n <ng-template tnHeaderCellDef>\n <!-- Select all checkbox -->\n </ng-template>\n <ng-template let-item tnCellDef>\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(item)\"\n [disabled]=\"!!item.disabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onItemClick(item)\">\n </ng-template>\n </ng-container>\n }\n\n <!-- Name column -->\n <ng-container tnColumnDef=\"name\">\n <ng-template tnHeaderCellDef>Name</ng-template>\n <ng-template let-item tnCellDef>\n\n <!-- NORMAL MODE: Display name -->\n @if (!item.isCreating) {\n <div\n class=\"file-name-cell\"\n [class.disabled]=\"!!item.disabled\"\n [class.zfs-object]=\"isZfsObject(item)\"\n [attr.tabindex]=\"item.disabled ? null : 0\"\n [attr.role]=\"'button'\"\n (click)=\"onItemClick(item)\"\n (dblclick)=\"onItemDoubleClick(item)\"\n (keydown.enter)=\"onItemDoubleClick(item)\"\n (keydown.space)=\"onItemClick(item)\">\n <tn-icon\n [name]=\"getItemIcon(item)\"\n [library]=\"getItemIconLibrary(item)\"\n [class]=\"'file-icon file-icon-' + item.type\" />\n <span class=\"file-name\">{{ item.name }}</span>\n\n <!-- ZFS badge -->\n @if (isZfsObject(item)) {\n <span\n [class]=\"'zfs-badge zfs-badge-' + item.type\">\n {{ getZfsBadge(item) }}\n </span>\n }\n\n <!-- Permission indicator -->\n @if (item.permissions === 'none') {\n <tn-icon\n name=\"lock\"\n library=\"mdi\"\n class=\"permission-icon\" />\n }\n </div>\n }\n\n <!-- EDIT MODE: Inline name input with error display -->\n @if (item.isCreating) {\n <div class=\"file-name-cell-wrapper\">\n <div class=\"file-name-cell editing\" [class.has-error]=\"!!item.creationError\">\n <tn-icon\n name=\"folder\"\n library=\"mdi\"\n class=\"file-icon file-icon-folder\" />\n <input\n #folderNameInput\n type=\"text\"\n role=\"textbox\"\n aria-label=\"Folder name\"\n class=\"folder-name-input\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [class.error]=\"!!item.creationError\"\n [value]=\"item.name\"\n [disabled]=\"creationLoading()\"\n [attr.data-autofocus]=\"true\"\n (keydown)=\"onFolderNameKeyDown($event, item)\"\n (blur)=\"onFolderNameInputBlur($event, item)\">\n\n <!-- Loading indicator during submission -->\n @if (creationLoading()) {\n <tn-icon\n name=\"loading\"\n library=\"mdi\"\n class=\"creation-loading-icon\" />\n }\n </div>\n\n <!-- Inline error message -->\n @if (item.creationError) {\n <div class=\"folder-creation-error\">\n <tn-icon name=\"alert-circle\" library=\"mdi\" class=\"error-icon\" />\n <span class=\"error-text\">{{ item.creationError }}</span>\n </div>\n }\n </div>\n }\n\n </ng-template>\n </ng-container>\n\n <!-- Size column -->\n <ng-container tnColumnDef=\"size\">\n <ng-template tnHeaderCellDef>Size</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.size !== undefined) {\n <span>{{ item.size | tnFileSize }}</span>\n }\n @if (item.size === undefined && item.type === 'folder') {\n <span class=\"folder-indicator\">--</span>\n }\n </ng-template>\n </ng-container>\n\n <!-- Modified column -->\n <ng-container tnColumnDef=\"modified\">\n <ng-template tnHeaderCellDef>Modified</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.modified) {\n <span>{{ formatDate(item.modified) }}</span>\n }\n </ng-template>\n </ng-container>\n\n\n </tn-table>\n\n <!-- Empty state -->\n @if (filteredFileItems().length === 0) {\n <div class=\"empty-state\">\n <tn-icon name=\"folder-open\" library=\"mdi\" customSize=\"48px\" />\n <p>No items found</p>\n </div>\n }\n </div>\n}\n\n<!-- Footer -->\n@if (!loading()) {\n <div class=\"tn-file-picker-footer\">\n @if (selectedItems().length > 0) {\n <span class=\"selection-count\">\n {{ selectedItems().length }} item{{ selectedItems().length !== 1 ? 's' : '' }} selected\n </span>\n }\n @if (selectedItems().length === 0) {\n <span class=\"selection-count\">\n No items selected\n </span>\n }\n <div class=\"footer-actions\">\n <tn-button\n label=\"Select\"\n [disabled]=\"selectedItems().length === 0\"\n (onClick)=\"onSubmit()\" />\n </div>\n </div>\n}", styles: [":host{display:block;background:var(--tn-bg1, white);color:var(--tn-fg1, #333);padding:0;box-shadow:0 4px 16px #0000001f,0 1px 4px #00000014;border-radius:8px;border:1px solid var(--tn-lines, #e0e0e0);min-width:400px;max-width:600px;min-height:500px;max-height:600px;font-family:var(--tn-font-family-body);display:flex;flex-direction:column;overflow:hidden}.tn-file-picker-header{display:flex;align-items:center;justify-content:space-between;padding:var(--tn-content-padding, 24px);padding-bottom:16px;border-bottom:1px solid var(--tn-lines)}.tn-file-picker-breadcrumb{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.tn-file-picker-breadcrumb .breadcrumb-segment{background:transparent;border:none;color:var(--tn-primary);cursor:pointer;padding:4px 8px;border-radius:4px;font-size:.875rem;white-space:nowrap;transition:background-color .15s ease-in-out}.tn-file-picker-breadcrumb .breadcrumb-segment:hover:not(:disabled){background:var(--tn-bg2)}.tn-file-picker-breadcrumb .breadcrumb-segment:disabled,.tn-file-picker-breadcrumb .breadcrumb-segment.current{color:var(--tn-fg1);cursor:default;font-weight:500}.tn-file-picker-breadcrumb .breadcrumb-segment:not(:last-child):after{content:\"/\";margin-left:8px;color:var(--tn-alt-fg1)}.tn-file-picker-actions{display:flex;align-items:center;gap:8px}.tn-file-picker-actions tn-button{font-size:.875rem}.tn-file-picker-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px;color:var(--tn-fg2)}.tn-file-picker-loading tn-icon{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tn-file-picker-content{flex:1;min-height:0;overflow-y:auto}.file-list-viewport{width:100%;height:100%}.file-list-viewport .cdk-virtual-scroll-content-wrapper{width:100%}tn-table{width:100%}tn-table th,tn-table .tn-table__header-cell{font-weight:600;color:var(--tn-fg1);padding:12px 16px;border-bottom:2px solid var(--tn-lines)}tn-table td,tn-table .tn-table__cell{padding:8px 16px;border-bottom:1px solid var(--tn-lines)}.file-checkbox{display:flex;align-items:center}.file-checkbox input[type=checkbox]{margin:0;width:16px;height:16px}.file-name-cell{display:flex;align-items:center;gap:8px;cursor:pointer}.file-name-cell.disabled{opacity:.5;color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-name{color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-icon{opacity:.6}.file-name-cell.disabled:has(.file-icon-folder),.file-name-cell.disabled:has(.file-icon-dataset),.file-name-cell.disabled:has(.file-icon-mountpoint){cursor:pointer}.file-name-cell.disabled:not(:has(.file-icon-folder)):not(:has(.file-icon-dataset)):not(:has(.file-icon-mountpoint)){cursor:not-allowed}.file-name-cell.editing{display:flex;align-items:center;gap:8px;padding:2px;cursor:default}.file-name-cell.editing .folder-name-input{flex:1;border:2px solid var(--tn-primary, #0066cc);padding:4px 8px;font-size:inherit;font-family:inherit;background:var(--tn-bg1, white);color:var(--tn-fg1, black);outline:none;border-radius:3px;min-width:200px}.file-name-cell.editing .folder-name-input:focus{border-color:var(--tn-primary, #0066cc);box-shadow:0 0 0 3px #0066cc1a}.file-name-cell.editing .folder-name-input.error{border-color:var(--tn-error, #d32f2f)}.file-name-cell.editing .folder-name-input:disabled{opacity:.6;cursor:not-allowed;background:var(--tn-bg2, #f5f5f5)}.file-name-cell.editing .creation-loading-icon{animation:spin 1s linear infinite;color:var(--tn-primary, #0066cc);flex-shrink:0}.file-name-cell-wrapper{display:flex;flex-direction:column;gap:4px}.folder-creation-error{display:flex;align-items:center;gap:6px;padding:4px 8px 4px 36px;margin-bottom:12px;background:#d32f2f1a;border-left:3px solid var(--tn-error, #d32f2f);border-radius:3px;font-size:.875rem;color:var(--tn-error, #d32f2f)}.folder-creation-error .error-icon{flex-shrink:0;width:20px;height:20px}.folder-creation-error .error-text{flex:1}.file-icon{display:flex;align-items:center;justify-content:center;font-size:var(--tn-icon-md, 20px);flex-shrink:0;line-height:1}.file-icon.file-icon-folder{color:var(--tn-primary)}.file-icon.file-icon-dataset{color:var(--tn-blue, #007db3)}.file-icon.file-icon-zvol{color:var(--tn-green, #71BF44)}.file-icon.file-icon-mountpoint{color:var(--tn-orange, #E68D37)}.file-name{flex:1;font-weight:500;line-height:1.4}.zfs-badge{display:inline-flex;align-items:center;background:var(--tn-alt-bg2);color:var(--tn-alt-fg2);font-size:.625rem;font-weight:600;padding:2px 6px;border-radius:12px;text-transform:uppercase;letter-spacing:.5px;line-height:1}.zfs-badge.zfs-badge-dataset{background:var(--tn-blue);color:#fff}.zfs-badge.zfs-badge-zvol{background:var(--tn-green);color:#fff}.zfs-badge.zfs-badge-mountpoint{background:var(--tn-orange);color:#fff}.permission-icon{display:flex;align-items:center;justify-content:center;color:var(--tn-red);font-size:var(--tn-icon-sm, 16px);line-height:1}.file-type{font-size:.875rem;padding:2px 8px;border-radius:12px}.file-type.type-folder{background:var(--tn-alt-bg1);color:var(--tn-alt-fg2)}.file-type.type-file{background:var(--tn-bg2);color:var(--tn-fg2)}.file-type.type-dataset{background:#007db31a;color:var(--tn-blue)}.file-type.type-zvol{background:#71bf441a;color:var(--tn-green)}.file-type.type-mountpoint{background:#e68d371a;color:var(--tn-orange)}.folder-indicator{color:var(--tn-alt-fg1);font-style:italic}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--tn-alt-fg1);text-align:center}.empty-state tn-icon{margin-bottom:16px;opacity:.5}.empty-state p{margin:0;font-size:.875rem}.tn-file-picker-footer{display:flex;align-items:center;justify-content:space-between;padding:16px var(--tn-content-padding, 24px);border-top:1px solid var(--tn-lines);background:var(--tn-bg2);border-bottom-left-radius:8px;border-bottom-right-radius:8px}.selection-count{font-size:.875rem;color:var(--tn-fg2);font-weight:500}.footer-actions{display:flex;gap:8px}@media(prefers-reduced-motion:reduce){.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){:host{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:.875rem}}\n"] }]
12969
+ }, template: "<!-- Header with breadcrumb navigation -->\n<div class=\"tn-file-picker-header\">\n <nav class=\"tn-file-picker-breadcrumb\" aria-label=\"File path\">\n @for (segment of currentPath() | tnTruncatePath; track $index; let last = $last) {\n <button\n class=\"breadcrumb-segment\"\n [class.current]=\"last\"\n [class.parent-nav]=\"segment.name === '..'\"\n [disabled]=\"last\"\n (click)=\"navigateToPath(segment.path)\">\n {{ segment.name }}\n </button>\n }\n </nav>\n\n <div class=\"tn-file-picker-actions\">\n @if (allowCreate()) {\n <tn-button\n variant=\"outline\"\n label=\"New Folder\"\n [disabled]=\"isCreateDisabled()\"\n (onClick)=\"onCreateFolder()\" />\n }\n </div>\n</div>\n\n<!-- Loading indicator -->\n@if (loading()) {\n <div class=\"tn-file-picker-loading\">\n <tn-icon name=\"loading\" library=\"mdi\" />\n <span>Loading...</span>\n </div>\n}\n\n<!-- File table -->\n@if (!loading()) {\n <div class=\"tn-file-picker-content\">\n <tn-table\n [dataSource]=\"filteredFileItems()\"\n [displayedColumns]=\"multiSelect() ? displayedColumns : displayedColumns.slice(1)\">\n\n <!-- Selection column -->\n @if (multiSelect()) {\n <ng-container tnColumnDef=\"select\">\n <ng-template tnHeaderCellDef>\n <!-- Select all checkbox -->\n </ng-template>\n <ng-template let-item tnCellDef>\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(item)\"\n [disabled]=\"!!item.disabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onItemClick(item)\">\n </ng-template>\n </ng-container>\n }\n\n <!-- Name column -->\n <ng-container tnColumnDef=\"name\">\n <ng-template tnHeaderCellDef>Name</ng-template>\n <ng-template let-item tnCellDef>\n\n <!-- NORMAL MODE: Display name -->\n @if (!item.isCreating) {\n <div\n class=\"file-name-cell\"\n [class.disabled]=\"!!item.disabled\"\n [class.zfs-object]=\"isZfsObject(item)\"\n [attr.tabindex]=\"item.disabled ? null : 0\"\n [attr.role]=\"'button'\"\n (click)=\"onItemClick(item)\"\n (dblclick)=\"onItemDoubleClick(item)\"\n (keydown.enter)=\"onItemDoubleClick(item)\"\n (keydown.space)=\"onItemClick(item)\">\n <tn-icon\n [name]=\"getItemIcon(item)\"\n [library]=\"getItemIconLibrary(item)\"\n [class]=\"'file-icon file-icon-' + item.type\" />\n <span class=\"file-name\">{{ item.name }}</span>\n\n <!-- ZFS badge -->\n @if (isZfsObject(item)) {\n <span\n [class]=\"'zfs-badge zfs-badge-' + item.type\">\n {{ getZfsBadge(item) }}\n </span>\n }\n\n <!-- Permission indicator -->\n @if (item.permissions === 'none') {\n <tn-icon\n name=\"lock\"\n library=\"mdi\"\n class=\"permission-icon\" />\n }\n </div>\n }\n\n <!-- EDIT MODE: Inline name input with error display -->\n @if (item.isCreating) {\n <div class=\"file-name-cell-wrapper\">\n <div class=\"file-name-cell editing\" [class.has-error]=\"!!item.creationError\">\n <tn-icon\n name=\"folder\"\n library=\"mdi\"\n class=\"file-icon file-icon-folder\" />\n <input\n #folderNameInput\n type=\"text\"\n role=\"textbox\"\n aria-label=\"Folder name\"\n class=\"folder-name-input\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [class.error]=\"!!item.creationError\"\n [value]=\"item.name\"\n [disabled]=\"creationLoading()\"\n [attr.data-autofocus]=\"true\"\n (keydown)=\"onFolderNameKeyDown($event, item)\"\n (blur)=\"onFolderNameInputBlur($event, item)\">\n\n <!-- Loading indicator during submission -->\n @if (creationLoading()) {\n <tn-icon\n name=\"loading\"\n library=\"mdi\"\n class=\"creation-loading-icon\" />\n }\n </div>\n\n <!-- Inline error message -->\n @if (item.creationError) {\n <div class=\"folder-creation-error\">\n <tn-icon name=\"alert-circle\" library=\"mdi\" class=\"error-icon\" />\n <span class=\"error-text\">{{ item.creationError }}</span>\n </div>\n }\n </div>\n }\n\n </ng-template>\n </ng-container>\n\n <!-- Size column -->\n <ng-container tnColumnDef=\"size\">\n <ng-template tnHeaderCellDef>Size</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.size !== undefined) {\n <span>{{ item.size | tnFileSize }}</span>\n }\n @if (item.size === undefined && item.type === 'folder') {\n <span class=\"folder-indicator\">--</span>\n }\n </ng-template>\n </ng-container>\n\n <!-- Modified column -->\n <ng-container tnColumnDef=\"modified\">\n <ng-template tnHeaderCellDef>Modified</ng-template>\n <ng-template let-item tnCellDef>\n @if (item.modified) {\n <span>{{ formatDate(item.modified) }}</span>\n }\n </ng-template>\n </ng-container>\n\n\n </tn-table>\n\n <!-- Empty state -->\n @if (filteredFileItems().length === 0) {\n <div class=\"empty-state\">\n <tn-icon name=\"folder-open\" library=\"mdi\" customSize=\"48px\" />\n <p>No items found</p>\n </div>\n }\n </div>\n}\n\n<!-- Footer -->\n@if (!loading()) {\n <div class=\"tn-file-picker-footer\">\n @if (selectedItems().length > 0) {\n <span class=\"selection-count\">\n {{ selectedItems().length }} item{{ selectedItems().length !== 1 ? 's' : '' }} selected\n </span>\n }\n @if (selectedItems().length === 0) {\n <span class=\"selection-count\">\n No items selected\n </span>\n }\n <div class=\"footer-actions\">\n <tn-button\n label=\"Select\"\n [disabled]=\"selectedItems().length === 0\"\n (onClick)=\"onSubmit()\" />\n </div>\n </div>\n}", styles: [":host{display:block;background:var(--tn-bg1, white);color:var(--tn-fg1, #333);padding:0;box-shadow:0 4px 16px #0000001f,0 1px 4px #00000014;border-radius:8px;border:1px solid var(--tn-lines, #e0e0e0);min-width:400px;max-width:600px;min-height:500px;max-height:600px;font-family:var(--tn-font-family-body);display:flex;flex-direction:column;overflow:hidden}.tn-file-picker-header{display:flex;align-items:center;justify-content:space-between;padding:var(--tn-content-padding, 24px);padding-bottom:16px;border-bottom:1px solid var(--tn-lines)}.tn-file-picker-breadcrumb{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.tn-file-picker-breadcrumb .breadcrumb-segment{background:transparent;border:none;color:var(--tn-primary);cursor:pointer;padding:4px 8px;border-radius:4px;font-size:1rem;white-space:nowrap;transition:background-color .15s ease-in-out}.tn-file-picker-breadcrumb .breadcrumb-segment:hover:not(:disabled){background:var(--tn-bg2)}.tn-file-picker-breadcrumb .breadcrumb-segment:disabled,.tn-file-picker-breadcrumb .breadcrumb-segment.current{color:var(--tn-fg1);cursor:default;font-weight:500}.tn-file-picker-breadcrumb .breadcrumb-segment:not(:last-child):after{content:\"/\";margin-left:8px;color:var(--tn-alt-fg1)}.tn-file-picker-actions{display:flex;align-items:center;gap:8px}.tn-file-picker-actions tn-button{font-size:1rem}.tn-file-picker-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px;color:var(--tn-fg2)}.tn-file-picker-loading tn-icon{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tn-file-picker-content{flex:1;min-height:0;overflow-y:auto}.file-list-viewport{width:100%;height:100%}.file-list-viewport .cdk-virtual-scroll-content-wrapper{width:100%}tn-table{width:100%}tn-table th,tn-table .tn-table__header-cell{font-weight:600;color:var(--tn-fg1);padding:12px 16px;border-bottom:2px solid var(--tn-lines)}tn-table td,tn-table .tn-table__cell{padding:8px 16px;border-bottom:1px solid var(--tn-lines)}.file-checkbox{display:flex;align-items:center}.file-checkbox input[type=checkbox]{margin:0;width:16px;height:16px}.file-name-cell{display:flex;align-items:center;gap:8px;cursor:pointer}.file-name-cell.disabled{opacity:.5;color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-name{color:var(--tn-fg2, #757575)}.file-name-cell.disabled .file-icon{opacity:.6}.file-name-cell.disabled:has(.file-icon-folder),.file-name-cell.disabled:has(.file-icon-dataset),.file-name-cell.disabled:has(.file-icon-mountpoint){cursor:pointer}.file-name-cell.disabled:not(:has(.file-icon-folder)):not(:has(.file-icon-dataset)):not(:has(.file-icon-mountpoint)){cursor:not-allowed}.file-name-cell.editing{display:flex;align-items:center;gap:8px;padding:2px;cursor:default}.file-name-cell.editing .folder-name-input{flex:1;border:2px solid var(--tn-primary, #0066cc);padding:4px 8px;font-size:inherit;font-family:inherit;background:var(--tn-bg1, white);color:var(--tn-fg1, black);outline:none;border-radius:3px;min-width:200px}.file-name-cell.editing .folder-name-input:focus{border-color:var(--tn-primary, #0066cc);box-shadow:0 0 0 3px #0066cc1a}.file-name-cell.editing .folder-name-input.error{border-color:var(--tn-error, #d32f2f)}.file-name-cell.editing .folder-name-input:disabled{opacity:.6;cursor:not-allowed;background:var(--tn-bg2, #f5f5f5)}.file-name-cell.editing .creation-loading-icon{animation:spin 1s linear infinite;color:var(--tn-primary, #0066cc);flex-shrink:0}.file-name-cell-wrapper{display:flex;flex-direction:column;gap:4px}.folder-creation-error{display:flex;align-items:center;gap:6px;padding:4px 8px 4px 36px;margin-bottom:12px;background:#d32f2f1a;border-left:3px solid var(--tn-error, #d32f2f);border-radius:3px;font-size:1rem;color:var(--tn-error, #d32f2f)}.folder-creation-error .error-icon{flex-shrink:0;width:20px;height:20px}.folder-creation-error .error-text{flex:1}.file-icon{display:flex;align-items:center;justify-content:center;font-size:var(--tn-icon-md, 20px);flex-shrink:0;line-height:1}.file-icon.file-icon-folder{color:var(--tn-primary)}.file-icon.file-icon-dataset{color:var(--tn-blue, #007db3)}.file-icon.file-icon-zvol{color:var(--tn-green, #71BF44)}.file-icon.file-icon-mountpoint{color:var(--tn-orange, #E68D37)}.file-name{flex:1;font-weight:500;line-height:1.4}.zfs-badge{display:inline-flex;align-items:center;background:var(--tn-alt-bg2);color:var(--tn-alt-fg2);font-size:.625rem;font-weight:600;padding:2px 6px;border-radius:12px;text-transform:uppercase;letter-spacing:.5px;line-height:1}.zfs-badge.zfs-badge-dataset{background:var(--tn-blue);color:#fff}.zfs-badge.zfs-badge-zvol{background:var(--tn-green);color:#fff}.zfs-badge.zfs-badge-mountpoint{background:var(--tn-orange);color:#fff}.permission-icon{display:flex;align-items:center;justify-content:center;color:var(--tn-red);font-size:var(--tn-icon-sm, 16px);line-height:1}.file-type{font-size:1rem;padding:2px 8px;border-radius:12px}.file-type.type-folder{background:var(--tn-alt-bg1);color:var(--tn-alt-fg2)}.file-type.type-file{background:var(--tn-bg2);color:var(--tn-fg2)}.file-type.type-dataset{background:#007db31a;color:var(--tn-blue)}.file-type.type-zvol{background:#71bf441a;color:var(--tn-green)}.file-type.type-mountpoint{background:#e68d371a;color:var(--tn-orange)}.folder-indicator{color:var(--tn-alt-fg1);font-style:italic}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--tn-alt-fg1);text-align:center}.empty-state tn-icon{margin-bottom:16px;opacity:.5}.empty-state p{margin:0;font-size:1rem}.tn-file-picker-footer{display:flex;align-items:center;justify-content:space-between;padding:16px var(--tn-content-padding, 24px);border-top:1px solid var(--tn-lines);background:var(--tn-bg2);border-bottom-left-radius:8px;border-bottom-right-radius:8px}.selection-count{font-size:1rem;color:var(--tn-fg2);font-weight:500}.footer-actions{display:flex;gap:8px}@media(prefers-reduced-motion:reduce){.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){:host{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:1rem}}\n"] }]
12526
12970
  }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], multiSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSelect", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], allowDatasetCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDatasetCreate", required: false }] }], allowZvolCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowZvolCreate", required: false }] }], currentPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPath", required: false }] }], fileItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileItems", required: false }] }], selectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItems", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], creationLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "creationLoading", required: false }] }], fileExtensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileExtensions", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], itemDoubleClick: [{ type: i0.Output, args: ["itemDoubleClick"] }], pathNavigate: [{ type: i0.Output, args: ["pathNavigate"] }], createFolder: [{ type: i0.Output, args: ["createFolder"] }], clearSelection: [{ type: i0.Output, args: ["clearSelection"] }], close: [{ type: i0.Output, args: ["close"] }], submit: [{ type: i0.Output, args: ["submit"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], submitFolderName: [{ type: i0.Output, args: ["submitFolderName"] }], cancelFolderCreation: [{ type: i0.Output, args: ["cancelFolderCreation"] }] } });
12527
12971
 
12528
12972
  class TnFilePickerComponent {
@@ -12982,7 +13426,7 @@ class TnFilePickerComponent {
12982
13426
  useExisting: forwardRef(() => TnFilePickerComponent),
12983
13427
  multi: true
12984
13428
  }
12985
- ], viewQueries: [{ propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }, { propertyName: "filePickerTemplate", first: true, predicate: ["filePickerTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-file-picker-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-file-picker-wrapper\" style=\"padding-right: 40px;\">\n <input\n type=\"text\"\n class=\"tn-file-picker-input\"\n [class.error]=\"hasError()\"\n [value]=\"selectedPath() | tnStripMntPrefix\"\n [placeholder]=\"placeholder()\"\n [readonly]=\"!allowManualInput()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onPathInput($event)\">\n\n <button\n type=\"button\"\n class=\"tn-file-picker-toggle\"\n aria-label=\"Open file picker\"\n [disabled]=\"isDisabled()\"\n (click)=\"openFilePicker()\">\n <tn-icon name=\"folder\" library=\"mdi\" />\n </button>\n </div>\n \n <ng-template #filePickerTemplate>\n <tn-file-picker-popup\n class=\"tn-file-picker-popup\"\n [mode]=\"mode()\"\n [multiSelect]=\"multiSelect()\"\n [allowCreate]=\"allowCreate()\"\n [allowDatasetCreate]=\"allowDatasetCreate()\"\n [allowZvolCreate]=\"allowZvolCreate()\"\n [currentPath]=\"currentPath()\"\n [fileItems]=\"fileItems()\"\n [selectedItems]=\"selectedItems()\"\n [loading]=\"loading()\"\n [creationLoading]=\"creationLoading()\"\n [fileExtensions]=\"fileExtensions()\"\n (itemClick)=\"onItemClick($event)\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (pathNavigate)=\"navigateToPath($event)\"\n (createFolder)=\"onCreateFolder()\"\n (submitFolderName)=\"onSubmitFolderName($event.name, $event.tempId)\"\n (cancelFolderCreation)=\"onCancelFolderCreation($event)\"\n (clearSelection)=\"onClearSelection()\"\n (submit)=\"onSubmit()\"\n (cancel)=\"onCancel()\"\n (close)=\"close()\" />\n </ng-template>\n</div>", styles: [":host{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-file-picker-container{position:relative;display:flex;align-items:center;width:100%}.tn-file-picker-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-file-picker-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;outline:none;box-sizing:border-box;font-family:inherit}.tn-file-picker-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-file-picker-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-file-picker-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-file-picker-input.error{border-color:var(--tn-error, #dc3545)}.tn-file-picker-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-file-picker-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;color:var(--tn-fg1);border-radius:4px}.tn-file-picker-toggle:hover{background:var(--tn-bg2, #f0f0f0)}.tn-file-picker-toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-file-picker-toggle:disabled{cursor:not-allowed;opacity:.5}.tn-file-picker-toggle tn-icon{font-size:var(--tn-icon-md, 20px)}:host:focus-within .tn-file-picker-input{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}:host.error .tn-file-picker-input{border-color:var(--tn-error, #dc3545)}:host.error .tn-file-picker-input:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}@media(prefers-reduced-motion:reduce){.tn-file-picker-input,.tn-file-picker-toggle,.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){.tn-file-picker-input{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host ::ng-deep .tn-file-picker-overlay .tn-file-picker-dialog{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:.875rem}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "component", type: TnFilePickerPopupComponent, selector: "tn-file-picker-popup", inputs: ["mode", "multiSelect", "allowCreate", "allowDatasetCreate", "allowZvolCreate", "currentPath", "fileItems", "selectedItems", "loading", "creationLoading", "fileExtensions"], outputs: ["itemClick", "itemDoubleClick", "pathNavigate", "createFolder", "clearSelection", "close", "submit", "cancel", "submitFolderName", "cancelFolderCreation"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }, { kind: "pipe", type: StripMntPrefixPipe, name: "tnStripMntPrefix" }] });
13429
+ ], viewQueries: [{ propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }, { propertyName: "filePickerTemplate", first: true, predicate: ["filePickerTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-file-picker-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-file-picker-wrapper\" style=\"padding-right: 40px;\">\n <input\n type=\"text\"\n class=\"tn-file-picker-input\"\n [class.error]=\"hasError()\"\n [value]=\"selectedPath() | tnStripMntPrefix\"\n [placeholder]=\"placeholder()\"\n [readonly]=\"!allowManualInput()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onPathInput($event)\">\n\n <button\n type=\"button\"\n class=\"tn-file-picker-toggle\"\n aria-label=\"Open file picker\"\n [disabled]=\"isDisabled()\"\n (click)=\"openFilePicker()\">\n <tn-icon name=\"folder\" library=\"mdi\" />\n </button>\n </div>\n \n <ng-template #filePickerTemplate>\n <tn-file-picker-popup\n class=\"tn-file-picker-popup\"\n [mode]=\"mode()\"\n [multiSelect]=\"multiSelect()\"\n [allowCreate]=\"allowCreate()\"\n [allowDatasetCreate]=\"allowDatasetCreate()\"\n [allowZvolCreate]=\"allowZvolCreate()\"\n [currentPath]=\"currentPath()\"\n [fileItems]=\"fileItems()\"\n [selectedItems]=\"selectedItems()\"\n [loading]=\"loading()\"\n [creationLoading]=\"creationLoading()\"\n [fileExtensions]=\"fileExtensions()\"\n (itemClick)=\"onItemClick($event)\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (pathNavigate)=\"navigateToPath($event)\"\n (createFolder)=\"onCreateFolder()\"\n (submitFolderName)=\"onSubmitFolderName($event.name, $event.tempId)\"\n (cancelFolderCreation)=\"onCancelFolderCreation($event)\"\n (clearSelection)=\"onClearSelection()\"\n (submit)=\"onSubmit()\"\n (cancel)=\"onCancel()\"\n (close)=\"close()\" />\n </ng-template>\n</div>", styles: [":host{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-file-picker-container{position:relative;display:flex;align-items:center;width:100%}.tn-file-picker-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-file-picker-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;outline:none;box-sizing:border-box;font-family:inherit}.tn-file-picker-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-file-picker-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-file-picker-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-file-picker-input.error{border-color:var(--tn-error, #dc3545)}.tn-file-picker-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-file-picker-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;color:var(--tn-fg1);border-radius:4px}.tn-file-picker-toggle:hover{background:var(--tn-bg2, #f0f0f0)}.tn-file-picker-toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-file-picker-toggle:disabled{cursor:not-allowed;opacity:.5}.tn-file-picker-toggle tn-icon{font-size:var(--tn-icon-md, 20px)}:host:focus-within .tn-file-picker-input{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}:host.error .tn-file-picker-input{border-color:var(--tn-error, #dc3545)}:host.error .tn-file-picker-input:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}@media(prefers-reduced-motion:reduce){.tn-file-picker-input,.tn-file-picker-toggle,.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){.tn-file-picker-input{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host ::ng-deep .tn-file-picker-overlay .tn-file-picker-dialog{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:1rem}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "component", type: TnFilePickerPopupComponent, selector: "tn-file-picker-popup", inputs: ["mode", "multiSelect", "allowCreate", "allowDatasetCreate", "allowZvolCreate", "currentPath", "fileItems", "selectedItems", "loading", "creationLoading", "fileExtensions"], outputs: ["itemClick", "itemDoubleClick", "pathNavigate", "createFolder", "clearSelection", "close", "submit", "cancel", "submitFolderName", "cancelFolderCreation"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }, { kind: "pipe", type: StripMntPrefixPipe, name: "tnStripMntPrefix" }] });
12986
13430
  }
12987
13431
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFilePickerComponent, decorators: [{
12988
13432
  type: Component,
@@ -13004,7 +13448,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
13004
13448
  ], host: {
13005
13449
  'class': 'tn-file-picker',
13006
13450
  '[class.error]': 'hasError()'
13007
- }, template: "<div class=\"tn-file-picker-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-file-picker-wrapper\" style=\"padding-right: 40px;\">\n <input\n type=\"text\"\n class=\"tn-file-picker-input\"\n [class.error]=\"hasError()\"\n [value]=\"selectedPath() | tnStripMntPrefix\"\n [placeholder]=\"placeholder()\"\n [readonly]=\"!allowManualInput()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onPathInput($event)\">\n\n <button\n type=\"button\"\n class=\"tn-file-picker-toggle\"\n aria-label=\"Open file picker\"\n [disabled]=\"isDisabled()\"\n (click)=\"openFilePicker()\">\n <tn-icon name=\"folder\" library=\"mdi\" />\n </button>\n </div>\n \n <ng-template #filePickerTemplate>\n <tn-file-picker-popup\n class=\"tn-file-picker-popup\"\n [mode]=\"mode()\"\n [multiSelect]=\"multiSelect()\"\n [allowCreate]=\"allowCreate()\"\n [allowDatasetCreate]=\"allowDatasetCreate()\"\n [allowZvolCreate]=\"allowZvolCreate()\"\n [currentPath]=\"currentPath()\"\n [fileItems]=\"fileItems()\"\n [selectedItems]=\"selectedItems()\"\n [loading]=\"loading()\"\n [creationLoading]=\"creationLoading()\"\n [fileExtensions]=\"fileExtensions()\"\n (itemClick)=\"onItemClick($event)\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (pathNavigate)=\"navigateToPath($event)\"\n (createFolder)=\"onCreateFolder()\"\n (submitFolderName)=\"onSubmitFolderName($event.name, $event.tempId)\"\n (cancelFolderCreation)=\"onCancelFolderCreation($event)\"\n (clearSelection)=\"onClearSelection()\"\n (submit)=\"onSubmit()\"\n (cancel)=\"onCancel()\"\n (close)=\"close()\" />\n </ng-template>\n</div>", styles: [":host{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-file-picker-container{position:relative;display:flex;align-items:center;width:100%}.tn-file-picker-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-file-picker-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;outline:none;box-sizing:border-box;font-family:inherit}.tn-file-picker-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-file-picker-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-file-picker-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-file-picker-input.error{border-color:var(--tn-error, #dc3545)}.tn-file-picker-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-file-picker-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;color:var(--tn-fg1);border-radius:4px}.tn-file-picker-toggle:hover{background:var(--tn-bg2, #f0f0f0)}.tn-file-picker-toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-file-picker-toggle:disabled{cursor:not-allowed;opacity:.5}.tn-file-picker-toggle tn-icon{font-size:var(--tn-icon-md, 20px)}:host:focus-within .tn-file-picker-input{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}:host.error .tn-file-picker-input{border-color:var(--tn-error, #dc3545)}:host.error .tn-file-picker-input:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}@media(prefers-reduced-motion:reduce){.tn-file-picker-input,.tn-file-picker-toggle,.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){.tn-file-picker-input{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host ::ng-deep .tn-file-picker-overlay .tn-file-picker-dialog{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:.875rem}}\n"] }]
13451
+ }, template: "<div class=\"tn-file-picker-container\" [tnTestId]=\"testId()\">\n <div #wrapper ixInput class=\"tn-file-picker-wrapper\" style=\"padding-right: 40px;\">\n <input\n type=\"text\"\n class=\"tn-file-picker-input\"\n [class.error]=\"hasError()\"\n [value]=\"selectedPath() | tnStripMntPrefix\"\n [placeholder]=\"placeholder()\"\n [readonly]=\"!allowManualInput()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onPathInput($event)\">\n\n <button\n type=\"button\"\n class=\"tn-file-picker-toggle\"\n aria-label=\"Open file picker\"\n [disabled]=\"isDisabled()\"\n (click)=\"openFilePicker()\">\n <tn-icon name=\"folder\" library=\"mdi\" />\n </button>\n </div>\n \n <ng-template #filePickerTemplate>\n <tn-file-picker-popup\n class=\"tn-file-picker-popup\"\n [mode]=\"mode()\"\n [multiSelect]=\"multiSelect()\"\n [allowCreate]=\"allowCreate()\"\n [allowDatasetCreate]=\"allowDatasetCreate()\"\n [allowZvolCreate]=\"allowZvolCreate()\"\n [currentPath]=\"currentPath()\"\n [fileItems]=\"fileItems()\"\n [selectedItems]=\"selectedItems()\"\n [loading]=\"loading()\"\n [creationLoading]=\"creationLoading()\"\n [fileExtensions]=\"fileExtensions()\"\n (itemClick)=\"onItemClick($event)\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (pathNavigate)=\"navigateToPath($event)\"\n (createFolder)=\"onCreateFolder()\"\n (submitFolderName)=\"onSubmitFolderName($event.name, $event.tempId)\"\n (cancelFolderCreation)=\"onCancelFolderCreation($event)\"\n (clearSelection)=\"onClearSelection()\"\n (submit)=\"onSubmit()\"\n (cancel)=\"onCancel()\"\n (close)=\"close()\" />\n </ng-template>\n</div>", styles: [":host{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-file-picker-container{position:relative;display:flex;align-items:center;width:100%}.tn-file-picker-wrapper{display:flex;align-items:center;width:100%;position:relative}.tn-file-picker-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;outline:none;box-sizing:border-box;font-family:inherit}.tn-file-picker-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-file-picker-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-file-picker-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-file-picker-input.error{border-color:var(--tn-error, #dc3545)}.tn-file-picker-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-file-picker-toggle{position:absolute;right:8px;z-index:2;pointer-events:auto;background:transparent;border:none;cursor:pointer;padding:4px;color:var(--tn-fg1);border-radius:4px}.tn-file-picker-toggle:hover{background:var(--tn-bg2, #f0f0f0)}.tn-file-picker-toggle:focus{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-file-picker-toggle:disabled{cursor:not-allowed;opacity:.5}.tn-file-picker-toggle tn-icon{font-size:var(--tn-icon-md, 20px)}:host:focus-within .tn-file-picker-input{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}:host.error .tn-file-picker-input{border-color:var(--tn-error, #dc3545)}:host.error .tn-file-picker-input:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}@media(prefers-reduced-motion:reduce){.tn-file-picker-input,.tn-file-picker-toggle,.file-item,.breadcrumb-segment{transition:none}.tn-file-picker-loading tn-icon{animation:none}}@media(prefers-contrast:high){.tn-file-picker-input{border-width:2px}.file-item:hover,.file-item.selected{border:2px solid var(--tn-fg1)}.zfs-badge{border:1px solid var(--tn-fg1)}}@media(max-width:768px){:host ::ng-deep .tn-file-picker-overlay .tn-file-picker-dialog{min-width:300px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px)}.tn-file-picker-header{flex-direction:column;gap:12px;align-items:stretch}.tn-file-picker-breadcrumb{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-file-picker-breadcrumb::-webkit-scrollbar{display:none}.file-item{padding:12px;min-height:56px}.file-info{font-size:1rem}}\n"] }]
13008
13452
  }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], multiSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSelect", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], allowDatasetCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDatasetCreate", required: false }] }], allowZvolCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowZvolCreate", required: false }] }], allowManualInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowManualInput", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], startPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPath", required: false }] }], rootPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPath", required: false }] }], fileExtensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileExtensions", required: false }] }], callbacks: [{ type: i0.Input, args: [{ isSignal: true, alias: "callbacks", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pathChange: [{ type: i0.Output, args: ["pathChange"] }], createFolder: [{ type: i0.Output, args: ["createFolder"] }], error: [{ type: i0.Output, args: ["error"] }], wrapperEl: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }], filePickerTemplate: [{ type: i0.ViewChild, args: ['filePickerTemplate', { isSignal: true }] }] } });
13009
13453
 
13010
13454
  /**
@@ -13124,16 +13568,19 @@ var TnToastPosition;
13124
13568
  TnToastPosition["Bottom"] = "bottom";
13125
13569
  })(TnToastPosition || (TnToastPosition = {}));
13126
13570
 
13127
- // 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.
13128
13574
  const TOAST_ICONS = {
13129
- [TnToastType.Info]: tnIconMarker('info', 'material'),
13130
- [TnToastType.Success]: tnIconMarker('check_circle', 'material'),
13131
- [TnToastType.Warning]: tnIconMarker('warning', 'material'),
13132
- [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',
13133
13579
  };
13134
13580
  class TnToastComponent {
13135
13581
  message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : []));
13136
13582
  action = signal(null, ...(ngDevMode ? [{ debugName: "action" }] : []));
13583
+ actionTestId = signal(undefined, ...(ngDevMode ? [{ debugName: "actionTestId" }] : []));
13137
13584
  type = signal(TnToastType.Info, ...(ngDevMode ? [{ debugName: "type" }] : []));
13138
13585
  position = signal(TnToastPosition.Top, ...(ngDevMode ? [{ debugName: "position" }] : []));
13139
13586
  visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
@@ -13141,14 +13588,14 @@ class TnToastComponent {
13141
13588
  onAction = () => { };
13142
13589
  onDismiss = () => { };
13143
13590
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13144
- 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:.875rem;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:.875rem;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 });
13145
13592
  }
13146
13593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, decorators: [{
13147
13594
  type: Component,
13148
- 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: {
13149
13596
  '[class.tn-toast--top]': 'position() === "top"',
13150
13597
  '[class.tn-toast--bottom]': 'position() === "bottom"',
13151
- }, 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:.875rem;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:.875rem;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"] }]
13152
13599
  }] });
13153
13600
 
13154
13601
  class TnToastRef {