@truenas/ui-components 0.1.59 → 0.1.60

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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
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';
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';
@@ -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.
@@ -1771,6 +1801,8 @@ class TnIconButtonComponent {
1771
1801
  tooltip = input(undefined, ...(ngDevMode ? [{ debugName: "tooltip" }] : []));
1772
1802
  library = input(undefined, ...(ngDevMode ? [{ debugName: "library" }] : []));
1773
1803
  onClick = output();
1804
+ hostRef = inject((ElementRef));
1805
+ buttonRef = viewChild.required('button');
1774
1806
  classes = computed(() => {
1775
1807
  const result = ['tn-icon-button'];
1776
1808
  if (this.color()) {
@@ -1781,13 +1813,41 @@ class TnIconButtonComponent {
1781
1813
  effectiveAriaLabel = computed(() => {
1782
1814
  return this.ariaLabel() || this.name() || 'Icon button';
1783
1815
  }, ...(ngDevMode ? [{ debugName: "effectiveAriaLabel" }] : []));
1816
+ /**
1817
+ * Focuses the inner native `<button>`. Exposed as a public method so callers
1818
+ * with a `TnIconButtonComponent` reference (e.g. `@ViewChild`) can focus it
1819
+ * without reaching into the DOM themselves.
1820
+ */
1821
+ focus(options) {
1822
+ this.buttonRef().nativeElement.focus(options);
1823
+ }
1824
+ ngAfterViewInit() {
1825
+ // Make `host.focus()` delegate to the inner `<button>`. Triggers that take
1826
+ // an element ref and call `.focus()` on it (MatMenuTrigger restores focus
1827
+ // this way; CDK A11y FocusMonitor also uses raw `.focus()`) target the
1828
+ // host custom element, which isn't focusable on its own — so without this
1829
+ // override the focus call silently no-ops and users see the focus ring
1830
+ // disappear after a menu closes. Forwarding to the inner button keeps the
1831
+ // public DOM API behaving like a native button.
1832
+ //
1833
+ // Intentionally per-instance, not on the prototype: each host owns its own
1834
+ // inner button ref. We don't restore the original `focus` on destroy
1835
+ // because the host element is destroyed at the same time.
1836
+ //
1837
+ // If this component is ever moved to `ViewEncapsulation.ShadowDom`, drop
1838
+ // this override and rely on the shadow root's `delegatesFocus: true`
1839
+ // option, which is the platform-native equivalent.
1840
+ const host = this.hostRef.nativeElement;
1841
+ const inner = this.buttonRef().nativeElement;
1842
+ host.focus = (options) => inner.focus(options);
1843
+ }
1784
1844
  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"] }] });
1845
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.0", type: TnIconButtonComponent, isStandalone: true, selector: "tn-icon-button", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, viewQueries: [{ propertyName: "buttonRef", first: true, predicate: ["button"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
1786
1846
  }
1787
1847
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconButtonComponent, decorators: [{
1788
1848
  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"] }] } });
1849
+ args: [{ selector: 'tn-icon-button', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective], template: "<button\n #button\n type=\"button\"\n [ngClass]=\"classes()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\"\n [tnTestId]=\"testId()\"\n (click)=\"onClick.emit($event)\"\n>\n <tn-icon\n [name]=\"name()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [library]=\"library()\"\n [ariaLabel]=\"effectiveAriaLabel()\" />\n</button>\n", styles: [":host{display:inline-block;width:fit-content}.tn-icon-button{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;border:none;background:transparent;padding:8px;border-radius:4px;transition:background-color .2s ease,color .2s ease;color:var(--tn-fg2, #6b7280)}.tn-icon-button:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-icon-button:active:not(:disabled){background-color:var(--tn-bg3, #e5e7eb)}.tn-icon-button.tn-icon-button--custom-color:hover:not(:disabled),.tn-icon-button.tn-icon-button--custom-color:active:not(:disabled){background-color:#ffffff1a;color:inherit}.tn-icon-button:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px}.tn-icon-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}\n"] }]
1850
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], library: [{ type: i0.Input, args: [{ isSignal: true, alias: "library", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }], buttonRef: [{ type: i0.ViewChild, args: ['button', { isSignal: true }] }] } });
1791
1851
 
1792
1852
  /**
1793
1853
  * Harness for interacting with tn-icon-button in tests.
@@ -2533,6 +2593,13 @@ class TnMenuTriggerDirective {
2533
2593
  overlayRef;
2534
2594
  isMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMenuOpen" }] : []));
2535
2595
  itemClickSub;
2596
+ /**
2597
+ * RxJS subscriptions for the current overlay's `backdropClick()` and
2598
+ * `keydownEvents()`. Each `openMenu()` creates a fresh overlay (and fresh
2599
+ * subscriptions), so we collect them here and tear them down in `closeMenu`
2600
+ * / `ngOnDestroy` to keep open→close cycles leak-free.
2601
+ */
2602
+ overlaySubs = [];
2536
2603
  elementRef = inject(ElementRef);
2537
2604
  overlay = inject(Overlay);
2538
2605
  viewContainerRef = inject(ViewContainerRef);
@@ -2544,6 +2611,12 @@ class TnMenuTriggerDirective {
2544
2611
  this.openMenu();
2545
2612
  }
2546
2613
  }
2614
+ onArrowDown(event) {
2615
+ if (!this.isMenuOpen()) {
2616
+ event.preventDefault();
2617
+ this.openMenu();
2618
+ }
2619
+ }
2547
2620
  openMenu() {
2548
2621
  const menuComponent = this.menu();
2549
2622
  if (!menuComponent || this.isMenuOpen()) {
@@ -2572,26 +2645,91 @@ class TnMenuTriggerDirective {
2572
2645
  const portal = new TemplatePortal(menuTemplate, this.viewContainerRef);
2573
2646
  this.overlayRef.attach(portal);
2574
2647
  this.isMenuOpen.set(true);
2575
- // Handle backdrop click
2576
- this.overlayRef.backdropClick().subscribe(() => {
2648
+ // Handle backdrop click. Track the subscription so a future close/open
2649
+ // cycle (or component destroy) tears it down explicitly instead of relying
2650
+ // on the overlay's GC.
2651
+ this.overlaySubs.push(this.overlayRef.backdropClick().subscribe(() => {
2577
2652
  this.closeMenu();
2578
- });
2653
+ }));
2654
+ // Escape and Tab both close the menu. CdkMenu has handlers for both, but
2655
+ // they only tell the menu stack to close — our overlay lifecycle isn't
2656
+ // wired to that stack, so we listen on the overlay's keydown events
2657
+ // directly.
2658
+ //
2659
+ // - Escape: preventDefault + close + restore focus (so the user lands
2660
+ // somewhere sensible after dismissing).
2661
+ // - Tab / Shift+Tab: close + restore focus to the trigger, but do NOT
2662
+ // preventDefault — the browser's default Tab action then advances focus
2663
+ // from the trigger to the next/previous focusable element on the page,
2664
+ // which is what the user wanted by pressing Tab.
2665
+ this.overlaySubs.push(this.overlayRef.keydownEvents().subscribe((event) => {
2666
+ const hasMod = event.altKey || event.ctrlKey || event.metaKey;
2667
+ if (event.key === 'Escape' && !hasMod) {
2668
+ event.preventDefault();
2669
+ this.closeMenu();
2670
+ }
2671
+ else if (event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey) {
2672
+ this.closeMenu();
2673
+ }
2674
+ }));
2579
2675
  // Close menu when a leaf item is selected
2580
2676
  this.itemClickSub = menuComponent.menuItemClick.subscribe(() => {
2581
2677
  this.closeMenu();
2582
2678
  });
2583
- // Notify menu component
2679
+ // Notify menu component. TnMenuActivateHoverDirective (applied inside the
2680
+ // overlay template) will focus the first enabled item via
2681
+ // CdkMenu.focusFirstItem() once the embedded view is fully attached, so
2682
+ // there is no need to reach into the overlay from here.
2584
2683
  menuComponent.onMenuOpen();
2585
2684
  }
2586
2685
  closeMenu() {
2587
2686
  this.itemClickSub?.unsubscribe();
2588
2687
  this.itemClickSub = undefined;
2688
+ this.disposeOverlaySubs();
2589
2689
  if (this.overlayRef) {
2590
2690
  this.overlayRef.dispose();
2591
2691
  this.overlayRef = undefined;
2592
2692
  this.isMenuOpen.set(false);
2593
2693
  this.menu().onMenuClose();
2694
+ this.restoreFocusToTrigger();
2695
+ }
2696
+ }
2697
+ ngOnDestroy() {
2698
+ // If the host is destroyed while the menu is open, closeMenu() never runs.
2699
+ // Dispose directly without trying to restore focus or notify the menu — the
2700
+ // surrounding view is being torn down anyway.
2701
+ this.itemClickSub?.unsubscribe();
2702
+ this.itemClickSub = undefined;
2703
+ this.disposeOverlaySubs();
2704
+ this.overlayRef?.dispose();
2705
+ this.overlayRef = undefined;
2706
+ }
2707
+ disposeOverlaySubs() {
2708
+ for (const sub of this.overlaySubs) {
2709
+ sub.unsubscribe();
2594
2710
  }
2711
+ this.overlaySubs.length = 0;
2712
+ }
2713
+ /**
2714
+ * Return focus to the trigger element so keyboard users land somewhere
2715
+ * sensible after the menu closes (Escape, item click, or backdrop click).
2716
+ *
2717
+ * The host might be a custom-element wrapper like `<tn-button>` /
2718
+ * `<tn-icon-button>` whose host element isn't itself focusable — focus
2719
+ * lives on the inner `<button>` or `<a>`. We focus the host if it's
2720
+ * focusable; otherwise we drill into the first focusable descendant.
2721
+ *
2722
+ * Passes `focusVisible: true` (Chrome/Firefox) so the `:focus-visible`
2723
+ * outline reappears on the restored trigger — without it, browsers treat a
2724
+ * programmatic `.focus()` as non-keyboard and skip the focus ring, which
2725
+ * looks to users like focus has been lost. Safari ignores the option and
2726
+ * falls back to its heuristic; that's acceptable.
2727
+ */
2728
+ restoreFocusToTrigger() {
2729
+ const host = this.elementRef.nativeElement;
2730
+ const FOCUSABLE = 'button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
2731
+ const target = host.matches(FOCUSABLE) ? host : host.querySelector(FOCUSABLE);
2732
+ target?.focus({ preventScroll: true, focusVisible: true });
2595
2733
  }
2596
2734
  getPositions() {
2597
2735
  switch (this.tnMenuPosition()) {
@@ -2618,7 +2756,7 @@ class TnMenuTriggerDirective {
2618
2756
  }
2619
2757
  }
2620
2758
  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 });
2759
+ 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
2760
  }
2623
2761
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuTriggerDirective, decorators: [{
2624
2762
  type: Directive,
@@ -2627,11 +2765,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2627
2765
  standalone: true,
2628
2766
  exportAs: 'tnMenuTrigger',
2629
2767
  host: {
2630
- '(click)': 'onClick()'
2631
- }
2768
+ '(click)': 'onClick()',
2769
+ '(keydown.arrowDown)': 'onArrowDown($event)',
2770
+ },
2632
2771
  }]
2633
2772
  }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuTriggerFor", required: true }] }], tnMenuPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuPosition", required: false }] }] } });
2634
2773
 
2774
+ /**
2775
+ * Renders a single `TnMenuItem` (separator, leaf button, or submenu trigger).
2776
+ *
2777
+ * Used recursively by `<tn-menu>` so the same authoring covers root items and
2778
+ * arbitrarily-nested submenus — adding a new menu-item property (badge,
2779
+ * tooltip, etc.) only needs to be wired up here, not duplicated across every
2780
+ * nesting level.
2781
+ *
2782
+ * **Why a component, not an ng-template:** earlier prototypes used a recursive
2783
+ * `<ng-template #menuItem>` outletted via `[ngTemplateOutlet]`. That fails
2784
+ * because the embedded view's DI chain resolves through the template's
2785
+ * declaration site, not the outlet anchor — `CdkMenuItem` then can't find
2786
+ * `MENU_STACK`, which is provided per-`CdkMenu`. A real component's DI walks
2787
+ * up through its host element instead, so each renderer instance sees the
2788
+ * `CdkMenu` it's actually rendered under (root or any nested submenu).
2789
+ *
2790
+ * @internal Implementation detail of `<tn-menu>`. Not re-exported from the
2791
+ * menu barrel or `public-api.ts` because it depends on injecting
2792
+ * `TnMenuComponent` from the host DI scope and cannot be used standalone.
2793
+ */
2794
+ class TnMenuItemRendererComponent {
2795
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
2796
+ // The renderer is rendered inside a CDK overlay portal, but the portal is
2797
+ // created from a template defined inside `<tn-menu>` — so the view-DI chain
2798
+ // still walks back to the host `<tn-menu>` component, which means inject()
2799
+ // here resolves it correctly.
2800
+ menu = inject(TnMenuComponent);
2801
+ onClick() {
2802
+ this.menu.onMenuItemClick(this.item());
2803
+ }
2804
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2805
+ 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 });
2806
+ }
2807
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, decorators: [{
2808
+ type: Component,
2809
+ args: [{ selector: 'tn-menu-item-renderer', standalone: true, imports: [
2810
+ CommonModule,
2811
+ CdkMenu,
2812
+ CdkMenuItem,
2813
+ CdkMenuTrigger,
2814
+ TnIconComponent,
2815
+ TnTestIdDirective,
2816
+ forwardRef(() => TnMenuItemRendererComponent),
2817
+ ], 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" }]
2818
+ }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
2819
+
2635
2820
  /**
2636
2821
  * Projection-based menu item for use inside `<tn-menu>`.
2637
2822
  *
@@ -2643,17 +2828,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2643
2828
  *
2644
2829
  * Existing `<tn-menu [items]="...">` consumers don't need this component;
2645
2830
  * 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.
2831
+ * projected `<tn-menu-item>` children render together inside one `<tn-menu>`,
2832
+ * share keyboard navigation, and look identical.
2833
+ *
2834
+ * **How it works:** the component itself renders nothing visible — it acts as
2835
+ * a configuration declaration. The parent `<tn-menu>` collects projected items
2836
+ * via `contentChildren`, re-renders them inside the CDK overlay alongside
2837
+ * items-array entries (with `cdkMenuItem` for full arrow-key navigation), and
2838
+ * routes clicks back to each item's `itemClick` output.
2839
+ *
2840
+ * **Accessibility:** because items are re-rendered inside the parent's
2841
+ * `CdkMenu`, all keyboard semantics from `@angular/cdk/menu` apply uniformly:
2842
+ * Arrow Up/Down/Home/End to move focus, Enter/Space to activate, Esc to
2843
+ * close, type-ahead search. Disabled items are skipped.
2844
+ *
2845
+ * **Limitation — custom content & keyboard nav:** the projected
2846
+ * `<ng-content>` (custom-content mode) is rendered inside a `<button
2847
+ * cdkMenuItem>` wrapper that owns Enter/Space/Arrow handling. Interactive
2848
+ * elements inside the projection (a nested `<button>`, link, toggle, etc.)
2849
+ * receive clicks but **do not** participate in CDK arrow-key navigation —
2850
+ * the wrapper button is the only keyboard-reachable target. Prefer
2851
+ * display-only content (icons, badges, two-line text); for cases that need
2852
+ * an extra interactive control, build a custom menu host with `cdkMenu`
2853
+ * directly instead of `<tn-menu>`.
2657
2854
  *
2658
2855
  * @example
2659
2856
  * ```html
@@ -2675,20 +2872,16 @@ class TnMenuItemComponent {
2675
2872
  selected = input(false, ...(ngDevMode ? [{ debugName: "selected" }] : []));
2676
2873
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
2677
2874
  itemClick = output();
2875
+ /** Template capturing whatever the consumer projected as item content. */
2876
+ content = viewChild.required('content');
2678
2877
  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
2878
  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"] }] });
2879
+ 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
2880
  }
2688
2881
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemComponent, decorators: [{
2689
2882
  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"] }] } });
2883
+ args: [{ selector: 'tn-menu-item', standalone: true, imports: [CommonModule], host: { style: 'display: none;' }, template: "<ng-template #content><ng-content /></ng-template>\n" }]
2884
+ }], 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
2885
 
2693
2886
  /**
2694
2887
  * Activates CDK menu hover-to-open behavior for menus opened via custom overlays.
@@ -2704,12 +2897,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2704
2897
  */
2705
2898
  class TnMenuActivateHoverDirective {
2706
2899
  cdkMenu = inject(CdkMenu);
2707
- elementRef = inject(ElementRef);
2900
+ elementRef = inject((ElementRef));
2708
2901
  ngAfterContentInit() {
2709
2902
  const stack = this.cdkMenu.menuStack;
2710
2903
  if (stack.isEmpty()) {
2711
2904
  stack.push(this.cdkMenu);
2712
- this.elementRef.nativeElement.focus({ preventScroll: true });
2905
+ }
2906
+ const km = this.cdkMenu.keyManager;
2907
+ km?.skipPredicate?.((item) => item.disabled);
2908
+ // Prefer the marked "selected" item when one exists — matches user
2909
+ // expectation for option pickers (export format, sort key, etc.) where
2910
+ // reopening the menu should land focus on the current choice, not always
2911
+ // the first entry. Falls back to the first enabled item.
2912
+ const host = this.elementRef.nativeElement;
2913
+ const items = Array.from(host.querySelectorAll('[cdkMenuItem]'));
2914
+ const selectedIndex = items.findIndex((el) => el.classList.contains('tn-menu-item--selected') && !el.hasAttribute('disabled'));
2915
+ const selectedItem = selectedIndex >= 0 ? items[selectedIndex] : undefined;
2916
+ if (selectedItem && km?.setActiveItem) {
2917
+ km.setActiveItem(selectedIndex);
2918
+ selectedItem.focus();
2919
+ }
2920
+ else {
2921
+ // Set the active index to the first enabled item so the next ArrowDown
2922
+ // advances correctly (instead of being a no-op while the manager "catches
2923
+ // up" to where DOM focus already is).
2924
+ this.cdkMenu.focusFirstItem('keyboard');
2713
2925
  }
2714
2926
  }
2715
2927
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuActivateHoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -2731,6 +2943,7 @@ class TnMenuComponent {
2731
2943
  menuTemplate = viewChild.required('menuTemplate');
2732
2944
  contextMenuTemplate = viewChild.required('contextMenuTemplate');
2733
2945
  contextOverlayRef;
2946
+ contextBackdropSub;
2734
2947
  overlay = inject(Overlay);
2735
2948
  viewContainerRef = inject(ViewContainerRef);
2736
2949
  onMenuItemClick(item) {
@@ -2746,21 +2959,20 @@ class TnMenuComponent {
2746
2959
  }
2747
2960
  }
2748
2961
  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
- });
2962
+ /**
2963
+ * Click handler for projected `<tn-menu-item>` entries. Emits the item's own
2964
+ * `itemClick` output, re-emits a synthetic entry on `menuItemClick` so
2965
+ * trigger-driven menus close uniformly, and closes any open context menu.
2966
+ */
2967
+ onProjectedItemClick(item, event) {
2968
+ if (item.disabled()) {
2969
+ return;
2970
+ }
2971
+ item.itemClick.emit(event);
2972
+ this.menuItemClick.emit({ id: item.id() ?? '', label: item.label() ?? '' });
2973
+ if (this.contextOverlayRef) {
2974
+ this.closeContextMenu();
2975
+ }
2764
2976
  }
2765
2977
  hasChildren = computed(() => (item) => {
2766
2978
  return !!(item.children && item.children.length > 0);
@@ -2803,20 +3015,30 @@ class TnMenuComponent {
2803
3015
  // Create portal and attach to overlay
2804
3016
  const portal = new TemplatePortal(contextMenuTemplate, this.viewContainerRef);
2805
3017
  this.contextOverlayRef.attach(portal);
2806
- // Handle backdrop click to close menu
2807
- this.contextOverlayRef.backdropClick().subscribe(() => {
3018
+ // Handle backdrop click to close menu — keep the subscription so we can
3019
+ // unsubscribe explicitly on close/destroy rather than leaving it dangling.
3020
+ this.contextBackdropSub = this.contextOverlayRef.backdropClick().subscribe(() => {
2808
3021
  this.closeContextMenu();
2809
3022
  });
2810
3023
  this.onMenuOpen();
2811
3024
  }
2812
3025
  }
2813
3026
  closeContextMenu() {
3027
+ this.contextBackdropSub?.unsubscribe();
3028
+ this.contextBackdropSub = undefined;
2814
3029
  if (this.contextOverlayRef) {
2815
3030
  this.contextOverlayRef.dispose();
2816
3031
  this.contextOverlayRef = undefined;
2817
3032
  this.onMenuClose();
2818
3033
  }
2819
3034
  }
3035
+ ngOnDestroy() {
3036
+ // Component destroyed while context menu open → clean up without notifying.
3037
+ this.contextBackdropSub?.unsubscribe();
3038
+ this.contextBackdropSub = undefined;
3039
+ this.contextOverlayRef?.dispose();
3040
+ this.contextOverlayRef = undefined;
3041
+ }
2820
3042
  onContextMenu(event) {
2821
3043
  if (this.contextMenu()) {
2822
3044
  event.preventDefault();
@@ -2829,12 +3051,12 @@ class TnMenuComponent {
2829
3051
  return item.id;
2830
3052
  }
2831
3053
  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"] }] });
3054
+ 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
3055
  }
2834
3056
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuComponent, decorators: [{
2835
3057
  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 }] }] } });
3058
+ 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"] }]
3059
+ }], 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
3060
 
2839
3061
  class TnSlideToggleComponent {
2840
3062
  toggleEl = viewChild.required('toggleEl');
@@ -2998,6 +3220,9 @@ class TnCardComponent {
2998
3220
  hasHeader = computed(() => {
2999
3221
  return !!(this.projectedHeader() || this.title() || this.headerStatus() || this.headerControl() || this.headerMenu());
3000
3222
  }, ...(ngDevMode ? [{ debugName: "hasHeader" }] : []));
3223
+ hasHeaderRight = computed(() => {
3224
+ return !!(this.headerStatus() || this.headerControl() || this.headerMenu()?.length);
3225
+ }, ...(ngDevMode ? [{ debugName: "hasHeaderRight" }] : []));
3001
3226
  hasFooter = computed(() => {
3002
3227
  return !!(this.primaryAction() || this.secondaryAction() || this.footerLink());
3003
3228
  }, ...(ngDevMode ? [{ debugName: "hasFooter" }] : []));
@@ -3020,7 +3245,7 @@ class TnCardComponent {
3020
3245
  return type ? `tn-card__status--${type}` : 'tn-card__status--neutral';
3021
3246
  }
3022
3247
  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"] }] });
3248
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCardComponent, isStandalone: true, selector: "tn-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, titleLink: { classPropertyName: "titleLink", publicName: "titleLink", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, padContent: { classPropertyName: "padContent", publicName: "padContent", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, headerStatus: { classPropertyName: "headerStatus", publicName: "headerStatus", isSignal: true, isRequired: false, transformFunction: null }, headerControl: { classPropertyName: "headerControl", publicName: "headerControl", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, headerMenuTriggerTestId: { classPropertyName: "headerMenuTriggerTestId", publicName: "headerMenuTriggerTestId", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null }, footerLink: { classPropertyName: "footerLink", publicName: "footerLink", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedHeader", first: true, predicate: TnCardHeaderDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card:not(.tn-card--bordered) .tn-card__header{border-bottom-color:#0000001a}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card:not(.tn-card--bordered) .tn-card__footer{border-top-color:#0000001a}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "ariaLabel", "testId", "name", "size", "color", "tooltip", "library"], outputs: ["onClick"] }, { kind: "component", type: TnSlideToggleComponent, selector: "tn-slide-toggle", inputs: ["labelPosition", "label", "disabled", "required", "color", "testId", "ariaLabel", "ariaLabelledby", "checked"], outputs: ["change", "toggleChange"] }, { kind: "component", type: TnMenuComponent, selector: "tn-menu", inputs: ["items", "contextMenu"], outputs: ["menuItemClick", "menuOpen", "menuClose"] }, { kind: "directive", type: TnMenuTriggerDirective, selector: "[tnMenuTriggerFor]", inputs: ["tnMenuTriggerFor", "tnMenuPosition"], exportAs: ["tnMenuTrigger"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3024
3249
  }
3025
3250
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, decorators: [{
3026
3251
  type: Component,
@@ -3033,7 +3258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3033
3258
  TnMenuComponent,
3034
3259
  TnMenuTriggerDirective,
3035
3260
  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"] }]
3261
+ ], 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
3262
  }], 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
3263
 
3039
3264
  const expandCollapseAnimation = trigger('expandCollapse', [
@@ -5053,11 +5278,11 @@ class TnFormFieldComponent {
5053
5278
  return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
5054
5279
  }, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
5055
5280
  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"] }] });
5281
+ 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
5282
  }
5058
5283
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
5059
5284
  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"] }]
5285
+ 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
5286
  }], 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
5287
 
5063
5288
  /**
@@ -5278,6 +5503,11 @@ class TnSelectComponent {
5278
5503
  * fallback uses `JSON.stringify`, which is key-order dependent and can
5279
5504
  * produce false negatives for structurally equal objects. For primitives the
5280
5505
  * default identity check is fine.
5506
+ *
5507
+ * @example
5508
+ * ```ts
5509
+ * compareWith = (a, b) => a?.id === b?.id;
5510
+ * ```
5281
5511
  */
5282
5512
  compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
5283
5513
  selectionChange = output();
@@ -5291,110 +5521,73 @@ class TnSelectComponent {
5291
5521
  /** Index into `flatOptions` of the keyboard-focused row (-1 when none). */
5292
5522
  focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
5293
5523
  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" }] : []));
5524
+ // Per-instance fallback id namespace so aria-activedescendant ids stay
5525
+ // unique across selects when no `testId` is provided.
5526
+ static instanceCounter = 0;
5527
+ instanceId = `i${++TnSelectComponent.instanceCounter}`;
5528
+ /**
5529
+ * Id namespace used by all DOM ids the template emits (dropdown panel,
5530
+ * option rows, group labels). Prefers `testId` when set so tests can target
5531
+ * specific instances; otherwise falls back to a per-instance counter so two
5532
+ * `<tn-select>`s on the same page never collide on `aria-controls`/group ids.
5533
+ */
5534
+ idNamespace = computed(() => this.testId() || this.instanceId, ...(ngDevMode ? [{ debugName: "idNamespace" }] : []));
5309
5535
  // Computed disabled state (combines input and form state)
5310
5536
  isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
5311
5537
  /**
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.
5538
+ * Selectable, non-disabled options in display order (regular options first,
5539
+ * then groups). Used by keyboard navigation so we can skip disabled
5540
+ * entries and group headers without a separate filter pass.
5315
5541
  */
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 });
5542
+ navigableOptions = computed(() => {
5543
+ const result = [];
5544
+ const baseId = `tn-select-opt-${this.idNamespace()}`;
5545
+ let i = 0;
5546
+ for (const opt of this.options()) {
5547
+ if (!opt.disabled) {
5548
+ result.push({ option: opt, id: `${baseId}-${i}` });
5321
5549
  }
5550
+ i++;
5322
5551
  }
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
5552
  for (const group of this.optionGroups()) {
5333
- offsets.push(offset);
5334
- offset += group.options.length;
5553
+ for (const opt of group.options) {
5554
+ if (!opt.disabled && !group.disabled) {
5555
+ result.push({ option: opt, id: `${baseId}-${i}` });
5556
+ }
5557
+ i++;
5558
+ }
5335
5559
  }
5336
- return offsets;
5337
- }, ...(ngDevMode ? [{ debugName: "groupOffsets" }] : []));
5338
- /** `aria-activedescendant` id for the focused option (or null). */
5339
- activeOptionId = computed(() => {
5560
+ return result;
5561
+ }, ...(ngDevMode ? [{ debugName: "navigableOptions" }] : []));
5562
+ /** Stable DOM id of the currently-highlighted option, for aria-activedescendant. */
5563
+ focusedOptionId = computed(() => {
5340
5564
  const idx = this.focusedIndex();
5341
- if (idx < 0 || !this.isOpen()) {
5342
- return null;
5343
- }
5344
- return this.optionId(idx);
5345
- }, ...(ngDevMode ? [{ debugName: "activeOptionId" }] : []));
5565
+ const nav = this.navigableOptions();
5566
+ return idx >= 0 && idx < nav.length ? nav[idx].id : null;
5567
+ }, ...(ngDevMode ? [{ debugName: "focusedOptionId" }] : []));
5568
+ /** Stable DOM id for an option; matches what navigableOptions() assigns. */
5569
+ optionId(option) {
5570
+ const entry = this.navigableOptions().find((x) => x.option === option);
5571
+ return entry?.id ?? null;
5572
+ }
5573
+ /** Whether `option` is the keyboard-highlighted item. */
5574
+ isOptionFocused(option) {
5575
+ const idx = this.focusedIndex();
5576
+ const nav = this.navigableOptions();
5577
+ return idx >= 0 && idx < nav.length && nav[idx].option === option;
5578
+ }
5346
5579
  onChange = (_value) => { };
5347
5580
  onTouched = () => { };
5348
5581
  elementRef = inject(ElementRef);
5349
5582
  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
- }
5583
+ overlay = inject(Overlay);
5584
+ viewContainerRef = inject(ViewContainerRef);
5585
+ triggerEl = viewChild.required('triggerEl');
5586
+ dropdownTemplate = viewChild.required('dropdownTemplate');
5587
+ overlayRef;
5588
+ overlaySubs = [];
5589
+ // CDK Overlay handles outside-click detection and Escape; no constructor
5590
+ // wiring needed. See openDropdown/closeDropdown.
5398
5591
  // ControlValueAccessor implementation
5399
5592
  writeValue(value) {
5400
5593
  if (this.multiple()) {
@@ -5430,100 +5623,107 @@ class TnSelectComponent {
5430
5623
  this.openDropdown();
5431
5624
  }
5432
5625
  }
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
5626
  openDropdown() {
5438
- if (this.isDisabled()) {
5627
+ if (this.isDisabled() || this.isOpen()) {
5439
5628
  return;
5440
5629
  }
5441
- this.dropdownPosition.set(this.computeDropdownPosition());
5442
5630
  this.isOpen.set(true);
5443
- this.focusedIndex.set(this.initialFocusIndex());
5631
+ // Seed keyboard focus at the current selection when there is one;
5632
+ // otherwise leave it unset so the next ArrowDown lands on the first item.
5633
+ const selected = this.selectedValue();
5634
+ if (selected !== null && selected !== undefined) {
5635
+ const idx = this.navigableOptions().findIndex((x) => this.compareValues(x.option.value, selected));
5636
+ this.focusedIndex.set(idx);
5637
+ }
5638
+ else {
5639
+ this.focusedIndex.set(-1);
5640
+ }
5641
+ this.attachOverlay();
5444
5642
  }
5445
5643
  /**
5446
- * Close the dropdown.
5644
+ * Attach the dropdown panel as a CDK overlay anchored to the trigger.
5447
5645
  *
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.
5646
+ * Why an overlay (vs. an inline absolutely-positioned panel):
5647
+ * - Escapes parent `overflow: hidden`/clipping in surrounding layouts.
5648
+ * - `outsidePointerEvents()` notifies on outside pointerdown WITHOUT
5649
+ * intercepting the click (no backdrop) — so the user's click reaches
5650
+ * the underlying target while the select closes silently.
5651
+ * - Position is recomputed on scroll so the panel stays attached.
5652
+ * - Width is matched to the trigger so the panel doesn't jump in size.
5451
5653
  */
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
- }
5654
+ attachOverlay() {
5655
+ const trigger = this.triggerEl().nativeElement;
5656
+ const positionStrategy = this.overlay
5657
+ .position()
5658
+ .flexibleConnectedTo(trigger)
5659
+ .withPositions([
5660
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
5661
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
5662
+ ]);
5663
+ this.overlayRef = this.overlay.create({
5664
+ positionStrategy,
5665
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
5666
+ hasBackdrop: false,
5667
+ width: trigger.offsetWidth,
5668
+ });
5669
+ const portal = new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef);
5670
+ this.overlayRef.attach(portal);
5671
+ // Click-outside (non-intercepting). The pointer event still reaches the
5672
+ // element the user clicked; we just notice and close.
5673
+ //
5674
+ // Important: ignore events whose target is inside the select host. A
5675
+ // pointerdown on the trigger is "outside the overlay" from CDK's POV but
5676
+ // it's our own toggle target letting closeDropdown fire here races the
5677
+ // trigger's click handler and the dropdown immediately reopens.
5678
+ this.overlaySubs.push(this.overlayRef.outsidePointerEvents().subscribe((event) => {
5679
+ const target = event.target;
5680
+ if (target && this.elementRef.nativeElement.contains(target)) {
5681
+ return;
5478
5682
  }
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
- }
5683
+ this.closeDropdown(false);
5684
+ }));
5685
+ // Escape as a fallback (the trigger keydown handler covers the common case,
5686
+ // but if focus ever moves into the panel, this catches it too).
5687
+ this.overlaySubs.push(this.overlayRef.keydownEvents().subscribe((event) => {
5688
+ if (event.key === 'Escape' && !event.altKey && !event.ctrlKey && !event.metaKey) {
5689
+ event.preventDefault();
5690
+ this.closeDropdown(true);
5487
5691
  }
5488
- }
5489
- // Otherwise the first non-disabled option.
5490
- return flat.findIndex((opt) => !opt.disabled);
5692
+ }));
5491
5693
  }
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';
5694
+ detachOverlay() {
5695
+ this.overlaySubs.forEach((s) => s.unsubscribe());
5696
+ this.overlaySubs = [];
5697
+ this.overlayRef?.dispose();
5698
+ this.overlayRef = undefined;
5699
+ }
5700
+ ngOnDestroy() {
5701
+ // If the component is destroyed while the dropdown is open (e.g. router
5702
+ // navigates away), closeDropdown() never runs — clean up directly here.
5703
+ this.detachOverlay();
5513
5704
  }
5514
5705
  /**
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.
5706
+ * Closes the dropdown.
5707
+ *
5708
+ * @param restoreFocus When `true` (the default), returns focus to the
5709
+ * trigger. Used for explicit closes — Escape, Enter/Space activation,
5710
+ * option click — where the user is still interacting with the select.
5711
+ * Pass `false` for click-outside / blur paths so we don't steal focus
5712
+ * from the element the user actually navigated to.
5518
5713
  */
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;
5714
+ closeDropdown(restoreFocus = true) {
5715
+ this.isOpen.set(false);
5716
+ this.focusedIndex.set(-1);
5717
+ this.detachOverlay();
5718
+ this.onTouched();
5719
+ if (restoreFocus) {
5720
+ // `focusVisible: true` (Chrome/Firefox) keeps the :focus-visible outline
5721
+ // on the trigger after Escape / Enter / option-pick — without it,
5722
+ // programmatic .focus() is treated as non-keyboard and the focus ring
5723
+ // silently disappears, which users perceive as "focus lost". Safari
5724
+ // ignores the option and falls back to its heuristic.
5725
+ this.triggerEl().nativeElement.focus({ preventScroll: true, focusVisible: true });
5726
+ }
5527
5727
  }
5528
5728
  onOptionClick(option, groupDisabled = false) {
5529
5729
  if (option.disabled || groupDisabled) {
@@ -5567,10 +5767,6 @@ class TnSelectComponent {
5567
5767
  }
5568
5768
  return this.compareValues(this.selectedValue(), option.value);
5569
5769
  }
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
5770
  displayText = computed(() => {
5575
5771
  if (this.multiple()) {
5576
5772
  const values = this.selectedValues();
@@ -5604,14 +5800,26 @@ class TnSelectComponent {
5604
5800
  }
5605
5801
  return undefined;
5606
5802
  }
5607
- anyOptionsPresent = computed(() => {
5803
+ hasAnyOptions = computed(() => {
5608
5804
  return this.options().length > 0 || this.optionGroups().length > 0;
5609
- }, ...(ngDevMode ? [{ debugName: "anyOptionsPresent" }] : []));
5805
+ }, ...(ngDevMode ? [{ debugName: "hasAnyOptions" }] : []));
5806
+ /** One-shot guard so the object-compare warning fires at most once per instance. */
5807
+ warnedAboutObjectCompare = false;
5610
5808
  /**
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.
5809
+ * Compares two option values for equality.
5810
+ *
5811
+ * - Uses `compareWith` when provided (the supported path for object values).
5812
+ * - Falls back to strict identity (`===`) — adequate for primitives.
5813
+ * - For object values WITHOUT `compareWith` we return `false` (no
5814
+ * structural compare) and emit a one-time warning. The previous
5815
+ * `JSON.stringify` fallback was key-order sensitive and produced silent
5816
+ * false-negatives that were hard to diagnose; returning `false` makes the
5817
+ * misuse loud (selection won't match) and the warning points to the fix.
5818
+ *
5819
+ * The warning is **unconditional** (not gated on `isDevMode()`) so prod
5820
+ * monitoring picks it up — consumers relying on the old stringify fallback
5821
+ * would otherwise see selections silently stop matching after upgrade with
5822
+ * no signal in production logs.
5615
5823
  */
5616
5824
  compareValues(a, b) {
5617
5825
  const customCompare = this.compareWith();
@@ -5622,143 +5830,143 @@ class TnSelectComponent {
5622
5830
  return true;
5623
5831
  }
5624
5832
  if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
5625
- return JSON.stringify(a) === JSON.stringify(b);
5833
+ if (!this.warnedAboutObjectCompare) {
5834
+ this.warnedAboutObjectCompare = true;
5835
+ console.warn('[tn-select] Comparing object option values without a `compareWith` input. ' +
5836
+ 'Identity comparison will not match structurally-equal objects from different ' +
5837
+ 'references. Provide `[compareWith]="(a, b) => a?.id === b?.id"` (or similar).');
5838
+ }
5839
+ return false;
5626
5840
  }
5627
5841
  return false;
5628
5842
  }
5629
5843
  /**
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:
5844
+ * Keyboard navigation for the combobox trigger.
5845
+ *
5846
+ * - **ArrowDown / ArrowUp** opens the dropdown if closed; otherwise moves
5847
+ * the keyboard-focus highlight (via aria-activedescendant) up/down,
5848
+ * skipping disabled options and group headers.
5849
+ * - **Home / End** jump to the first / last enabled option.
5850
+ * - **Enter / Space** opens the dropdown if closed; if open and an option
5851
+ * is highlighted, selects that option (in single mode) or toggles it
5852
+ * (in multiple mode).
5853
+ * - **Escape** closes the dropdown without changing the selection.
5633
5854
  *
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.
5855
+ * All navigation keys call `event.preventDefault()` so the page does not
5856
+ * scroll while the user is moving through options.
5642
5857
  */
5643
5858
  onKeydown(event) {
5859
+ if (this.isDisabled()) {
5860
+ return;
5861
+ }
5644
5862
  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
5863
  case 'ArrowDown':
5864
+ event.preventDefault();
5662
5865
  if (!this.isOpen()) {
5663
5866
  this.openDropdown();
5867
+ if (this.focusedIndex() < 0) {
5868
+ this.moveFocus('first');
5869
+ }
5664
5870
  }
5665
5871
  else {
5666
5872
  this.moveFocus(1);
5667
5873
  }
5668
- event.preventDefault();
5669
5874
  break;
5670
5875
  case 'ArrowUp':
5876
+ event.preventDefault();
5671
5877
  if (!this.isOpen()) {
5672
5878
  this.openDropdown();
5879
+ this.moveFocus('last');
5673
5880
  }
5674
5881
  else {
5675
5882
  this.moveFocus(-1);
5676
5883
  }
5677
- event.preventDefault();
5678
5884
  break;
5679
5885
  case 'Home':
5680
5886
  if (this.isOpen()) {
5681
- this.moveFocusTo(0, 1);
5682
5887
  event.preventDefault();
5888
+ this.moveFocus('first');
5683
5889
  }
5684
5890
  break;
5685
5891
  case 'End':
5686
5892
  if (this.isOpen()) {
5687
- this.moveFocusTo(this.flatOptions().length - 1, -1);
5688
5893
  event.preventDefault();
5894
+ this.moveFocus('last');
5895
+ }
5896
+ break;
5897
+ case 'Enter':
5898
+ case ' ':
5899
+ event.preventDefault();
5900
+ if (!this.isOpen()) {
5901
+ this.openDropdown();
5902
+ }
5903
+ else {
5904
+ this.activateFocusedOption();
5905
+ }
5906
+ break;
5907
+ case 'Escape':
5908
+ if (this.isOpen()) {
5909
+ event.preventDefault();
5910
+ this.closeDropdown();
5689
5911
  }
5690
5912
  break;
5691
5913
  case 'Tab':
5692
- // Let the browser move focus to the next element; just close.
5914
+ // Standard combobox: Tab moves focus out of the select; close first
5915
+ // so the trigger stays a clean tab stop. Don't refocus — that would
5916
+ // fight the natural Tab advance to the next focusable element.
5693
5917
  if (this.isOpen()) {
5694
- this.closeDropdown({ restoreFocus: false });
5918
+ this.closeDropdown(false);
5695
5919
  }
5696
5920
  break;
5697
5921
  }
5698
5922
  }
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) {
5923
+ moveFocus(target) {
5924
+ const count = this.navigableOptions().length;
5925
+ if (count === 0) {
5703
5926
  return;
5704
5927
  }
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
- }
5928
+ let next;
5929
+ if (target === 'first') {
5930
+ next = 0;
5713
5931
  }
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;
5932
+ else if (target === 'last') {
5933
+ next = count - 1;
5720
5934
  }
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;
5935
+ else {
5936
+ const current = this.focusedIndex();
5937
+ const start = current < 0 ? (target === 1 ? -1 : count) : current;
5938
+ next = (start + target + count) % count;
5729
5939
  }
5940
+ this.focusedIndex.set(next);
5941
+ this.scrollFocusedIntoView();
5730
5942
  }
5731
- /** Select (or toggle, in multi-mode) the currently keyboard-focused row. */
5732
- selectFocused() {
5943
+ activateFocusedOption() {
5733
5944
  const idx = this.focusedIndex();
5734
- const flat = this.flatOptions();
5735
- if (idx < 0 || idx >= flat.length) {
5945
+ const nav = this.navigableOptions();
5946
+ if (idx < 0 || idx >= nav.length) {
5736
5947
  return;
5737
5948
  }
5738
- const opt = flat[idx];
5739
- if (opt.disabled) {
5740
- return;
5741
- }
5742
- if (this.multiple()) {
5743
- this.toggleOption(opt);
5744
- }
5745
- else {
5746
- this.selectOption(opt);
5747
- }
5949
+ this.onOptionClick(nav[idx].option);
5748
5950
  }
5749
- /** Scrolls the keyboard-focused option into view if it's outside the dropdown's viewport. */
5750
5951
  scrollFocusedIntoView() {
5751
- const idx = this.focusedIndex();
5752
- if (idx < 0) {
5952
+ const id = this.focusedOptionId();
5953
+ if (!id) {
5753
5954
  return;
5754
5955
  }
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' });
5956
+ const overlayEl = this.overlayRef?.overlayElement;
5957
+ if (!overlayEl) {
5958
+ return;
5959
+ }
5960
+ // Defer to next tick so the DOM has updated with the new .focused class.
5961
+ // The dropdown panel is rendered in a CDK overlay outside the host, so we
5962
+ // scope the query to the overlay element rather than the host (which
5963
+ // would silently miss the option) or `document` (which would pick the
5964
+ // wrong option if two tn-select instances with the same testId are open
5965
+ // simultaneously, and couples the component to a global).
5966
+ queueMicrotask(() => {
5967
+ const el = overlayEl.querySelector(`#${CSS.escape(id)}`);
5968
+ el?.scrollIntoView({ block: 'nearest' });
5969
+ });
5762
5970
  }
5763
5971
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5764
5972
  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 +5975,7 @@ class TnSelectComponent {
5767
5975
  useExisting: forwardRef(() => TnSelectComponent),
5768
5976
  multi: true
5769
5977
  }
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 });
5978
+ ], 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
5979
  }
5772
5980
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
5773
5981
  type: Component,
@@ -5777,8 +5985,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
5777
5985
  useExisting: forwardRef(() => TnSelectComponent),
5778
5986
  multi: true
5779
5987
  }
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 }] }] } });
5988
+ ], 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"] }]
5989
+ }], 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
5990
 
5783
5991
  /**
5784
5992
  * Harness for interacting with tn-select in tests.
@@ -5934,7 +6142,9 @@ class TnSelectHarness extends ComponentHarness {
5934
6142
  */
5935
6143
  async selectOption(filter) {
5936
6144
  await this.open();
5937
- const options = await this.locatorForAll('.tn-select-option')();
6145
+ // Dropdown panel is rendered in a CDK overlay (outside the host element),
6146
+ // so we search the document root rather than the harness-local subtree.
6147
+ const options = await this.documentRootLocatorFactory().locatorForAll('.tn-select-option')();
5938
6148
  for (const option of options) {
5939
6149
  const text = (await option.text()).trim();
5940
6150
  const matches = filter instanceof RegExp
@@ -5961,7 +6171,9 @@ class TnSelectHarness extends ComponentHarness {
5961
6171
  */
5962
6172
  async getOptions() {
5963
6173
  await this.open();
5964
- const options = await this.locatorForAll('.tn-select-option')();
6174
+ // Dropdown panel is rendered in a CDK overlay (outside the host element),
6175
+ // so we search the document root rather than the harness-local subtree.
6176
+ const options = await this.documentRootLocatorFactory().locatorForAll('.tn-select-option')();
5965
6177
  const labels = [];
5966
6178
  for (const option of options) {
5967
6179
  labels.push((await option.text()).trim());
@@ -6368,7 +6580,7 @@ class TnListItemComponent {
6368
6580
  }
6369
6581
  }
6370
6582
  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"] });
6583
+ 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
6584
  }
6373
6585
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, decorators: [{
6374
6586
  type: Component,
@@ -6380,7 +6592,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6380
6592
  '[class.tn-list-item--three-line]': 'hasThirdText()',
6381
6593
  'role': 'listitem',
6382
6594
  '(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"] }]
6595
+ }, 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
6596
  }], 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
6597
 
6386
6598
  class TnListSubheaderComponent {
@@ -6602,7 +6814,7 @@ class TnListOptionComponent {
6602
6814
  }
6603
6815
  }
6604
6816
  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"] }] });
6817
+ 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
6818
  }
6607
6819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListOptionComponent, decorators: [{
6608
6820
  type: Component,
@@ -6613,7 +6825,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6613
6825
  'role': 'option',
6614
6826
  '[attr.aria-selected]': 'effectiveSelected()',
6615
6827
  '[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"] }]
6828
+ }, 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
6829
  }], 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
6830
  type: HostListener,
6619
6831
  args: ['click', ['$event']]
@@ -6732,7 +6944,7 @@ class TnEmptyComponent {
6732
6944
  return this.size() === 'compact' ? 'lg' : 'xl';
6733
6945
  }, ...(ngDevMode ? [{ debugName: "iconSizePreset" }] : []));
6734
6946
  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"] }] });
6947
+ 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
6948
  }
6737
6949
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnEmptyComponent, decorators: [{
6738
6950
  type: Component,
@@ -6741,9 +6953,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6741
6953
  '[class.tn-empty--compact]': 'size() === "compact"',
6742
6954
  '[class.tn-empty--bordered]': 'bordered()',
6743
6955
  '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"] }]
6956
+ }, 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
6957
  }], 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
6958
 
6959
+ class TnSpinnerComponent {
6960
+ mode = input('indeterminate', ...(ngDevMode ? [{ debugName: "mode" }] : []));
6961
+ value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
6962
+ diameter = input(40, ...(ngDevMode ? [{ debugName: "diameter" }] : []));
6963
+ strokeWidth = input(4, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : []));
6964
+ ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
6965
+ ariaLabelledby = input(null, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
6966
+ radius = computed(() => {
6967
+ return (this.diameter() - this.strokeWidth()) / 2;
6968
+ }, ...(ngDevMode ? [{ debugName: "radius" }] : []));
6969
+ circumference = computed(() => {
6970
+ return 2 * Math.PI * this.radius();
6971
+ }, ...(ngDevMode ? [{ debugName: "circumference" }] : []));
6972
+ strokeDasharray = computed(() => {
6973
+ return `${this.circumference()} ${this.circumference()}`;
6974
+ }, ...(ngDevMode ? [{ debugName: "strokeDasharray" }] : []));
6975
+ strokeDashoffset = computed(() => {
6976
+ if (this.mode() === 'indeterminate') {
6977
+ return 0;
6978
+ }
6979
+ const progress = Math.max(0, Math.min(100, this.value()));
6980
+ return this.circumference() - (progress / 100) * this.circumference();
6981
+ }, ...(ngDevMode ? [{ debugName: "strokeDashoffset" }] : []));
6982
+ viewBox = computed(() => {
6983
+ const size = this.diameter();
6984
+ return `0 0 ${size} ${size}`;
6985
+ }, ...(ngDevMode ? [{ debugName: "viewBox" }] : []));
6986
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6987
+ 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 });
6988
+ }
6989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSpinnerComponent, decorators: [{
6990
+ type: Component,
6991
+ args: [{ selector: 'tn-spinner', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
6992
+ 'class': 'tn-spinner',
6993
+ '[class.tn-spinner-indeterminate]': 'mode() === "indeterminate"',
6994
+ '[class.tn-spinner-determinate]': 'mode() === "determinate"',
6995
+ '[attr.aria-valuenow]': 'mode() === "determinate" ? value() : null',
6996
+ '[attr.aria-valuemin]': 'mode() === "determinate" ? 0 : null',
6997
+ '[attr.aria-valuemax]': 'mode() === "determinate" ? 100 : null',
6998
+ 'role': 'progressbar',
6999
+ '[attr.aria-label]': 'ariaLabel() || null',
7000
+ '[attr.aria-labelledby]': 'ariaLabelledby() || null'
7001
+ }, 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"] }]
7002
+ }], 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 }] }] } });
7003
+
6747
7004
  class TnHeaderCellDefDirective {
6748
7005
  template = inject((TemplateRef));
6749
7006
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnHeaderCellDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -6814,14 +7071,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6814
7071
  }]
6815
7072
  }] });
6816
7073
 
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');
7074
+ // Material icons render via the `material-icons` CSS font (see icon.component.ts),
7075
+ // so they don't need sprite scanning — the literal `mat-` prefix matches what
7076
+ // the runtime icon resolver would produce from a Material library name.
7077
+ const SORT_ICON_ASC = 'mat-arrow_upward';
7078
+ const SORT_ICON_DESC = 'mat-arrow_downward';
7079
+ const SORT_ICON_NONE = 'mat-unfold_more';
7080
+ const EXPAND_ICON_DOWN = 'mat-keyboard_arrow_down';
7081
+ const EXPAND_ICON_UP = 'mat-keyboard_arrow_up';
6822
7082
  /**
6823
- * Determines the animation duration for detail row expand/collapse.
6824
- * Respects prefers-reduced-motion by using 0ms when the user prefers reduced motion.
7083
+ * Animation duration for detail row expand/collapse.
7084
+ *
7085
+ * Evaluated once when the `@Component` decorator runs (at module load), so the
7086
+ * value is frozen for the lifetime of the app: if the user toggles their OS
7087
+ * "reduce motion" preference at runtime, this duration will NOT update for
7088
+ * already-loaded components. Angular animations don't expose a per-trigger
7089
+ * dynamic duration, so live updates would require switching from
7090
+ * `@detailExpand` to plain CSS transitions (already used elsewhere in the SCSS,
7091
+ * which respects the live preference via `@media (prefers-reduced-motion)`).
7092
+ * Acceptable tradeoff: the OS preference rarely flips mid-session, and the
7093
+ * surrounding CSS transitions continue to react live.
6825
7094
  */
6826
7095
  function getExpandDuration() {
6827
7096
  if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
@@ -6841,9 +7110,50 @@ class TnTableComponent {
6841
7110
  selectable = input(false, ...(ngDevMode ? [{ debugName: "selectable" }] : []));
6842
7111
  expandable = input(false, ...(ngDevMode ? [{ debugName: "expandable" }] : []));
6843
7112
  bordered = input(false, ...(ngDevMode ? [{ debugName: "bordered" }] : []));
7113
+ /**
7114
+ * Marks a single row as "active" — adds the `tn-table__row--active` class
7115
+ * and a left-side indicator bar. Set to `null` (default) to clear.
7116
+ *
7117
+ * **Matched by object identity (`===`)** against the row references in
7118
+ * `dataSource`. Pass the exact reference you got from the data source (e.g.
7119
+ * via the `rowClick` event or a lookup into `dataSource()`), not a
7120
+ * structurally-equal copy — `{ id: 1 } !== { id: 1 }` and the row will not
7121
+ * highlight. This differs from `tn-select`, which supports a `compareWith`
7122
+ * input for object values; the table intentionally does not, because the
7123
+ * common use case (clicking a row to mark it active) already gives the
7124
+ * caller the original reference. If you need structural equality, look up
7125
+ * the row by id in your data source before assigning here.
7126
+ */
7127
+ activeRow = input(null, ...(ngDevMode ? [{ debugName: "activeRow" }] : []));
7128
+ /**
7129
+ * Overrides the active-row background color. Accepts any CSS color value
7130
+ * (`#hex`, `rgb()`, `var(--token)`). Defaults to `--tn-bg3` when null.
7131
+ */
7132
+ activeBg = input(null, ...(ngDevMode ? [{ debugName: "activeBg" }] : []));
7133
+ /**
7134
+ * Overrides the left-side active-row indicator color. Defaults to
7135
+ * `--tn-primary` when null.
7136
+ */
7137
+ activeIndicator = input(null, ...(ngDevMode ? [{ debugName: "activeIndicator" }] : []));
7138
+ /**
7139
+ * When true, shows a spinner overlay over the table. Existing rows remain
7140
+ * visible (dimmed) so reloads don't cause layout jumps; if there are no rows
7141
+ * yet, the spinner replaces the empty state.
7142
+ */
7143
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
7144
+ /** Accessible label announced while loading. */
7145
+ loadingMessage = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingMessage" }] : []));
7146
+ /**
7147
+ * When true, rows become keyboard-focusable (tabindex=0) and clicking or
7148
+ * pressing Enter/Space emits `rowClick`. Use this for "click row to view
7149
+ * details" patterns. Independent of `selectable` (checkbox) and `expandable`.
7150
+ */
7151
+ clickable = input(false, ...(ngDevMode ? [{ debugName: "clickable" }] : []));
6844
7152
  // --- Outputs ---
6845
7153
  sortChange = output();
6846
7154
  selectionChange = output();
7155
+ /** Emits the row when a clickable row is activated (click or Enter/Space). */
7156
+ rowClick = output();
6847
7157
  // --- Content queries ---
6848
7158
  columnDefs = contentChildren(TnTableColumnDirective, ...(ngDevMode ? [{ debugName: "columnDefs" }] : []));
6849
7159
  detailRowDef = contentChild(TnDetailRowDefDirective, ...(ngDevMode ? [{ debugName: "detailRowDef" }] : []));
@@ -6985,6 +7295,27 @@ class TnTableComponent {
6985
7295
  isRowExpanded(row) {
6986
7296
  return this.expandedRows().has(row);
6987
7297
  }
7298
+ // --- Active row ---
7299
+ isRowActive(row) {
7300
+ const active = this.activeRow();
7301
+ return active !== null && active === row;
7302
+ }
7303
+ // --- Row click ---
7304
+ onRowClick(row) {
7305
+ if (!this.clickable()) {
7306
+ return;
7307
+ }
7308
+ this.rowClick.emit(row);
7309
+ }
7310
+ onRowKeydown(event, row) {
7311
+ if (!this.clickable()) {
7312
+ return;
7313
+ }
7314
+ if (event.key === 'Enter' || event.key === ' ') {
7315
+ event.preventDefault();
7316
+ this.rowClick.emit(row);
7317
+ }
7318
+ }
6988
7319
  // --- Selection methods ---
6989
7320
  toggleSelectAll() {
6990
7321
  if (this.isAllSelected()) {
@@ -7012,7 +7343,7 @@ class TnTableComponent {
7012
7343
  return row[column];
7013
7344
  }
7014
7345
  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: [
7346
+ 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
7347
  trigger('detailExpand', [
7017
7348
  state('collapsed,void', style({ height: '0px', minHeight: '0', overflow: 'hidden' })),
7018
7349
  state('expanded', style({ height: '*' })),
@@ -7022,7 +7353,7 @@ class TnTableComponent {
7022
7353
  }
7023
7354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTableComponent, decorators: [{
7024
7355
  type: Component,
7025
- args: [{ selector: 'tn-table', standalone: true, imports: [CommonModule, TnCheckboxComponent, TnEmptyComponent, TnIconComponent], animations: [
7356
+ args: [{ selector: 'tn-table', standalone: true, imports: [CommonModule, TnCheckboxComponent, TnEmptyComponent, TnIconComponent, TnSpinnerComponent], animations: [
7026
7357
  trigger('detailExpand', [
7027
7358
  state('collapsed,void', style({ height: '0px', minHeight: '0', overflow: 'hidden' })),
7028
7359
  state('expanded', style({ height: '*' })),
@@ -7031,8 +7362,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
7031
7362
  ], hostDirectives: [{ directive: TnTestIdDirective, inputs: ['tnTestId: testId'] }], host: {
7032
7363
  class: 'tn-table',
7033
7364
  '[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 }] }] } });
7365
+ '[class.tn-table--loading]': 'loading()',
7366
+ '[style.--tn-table-active-bg]': 'activeBg()',
7367
+ '[style.--tn-table-active-indicator]': 'activeIndicator()',
7368
+ }, 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"] }]
7369
+ }], 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
7370
 
7037
7371
  /**
7038
7372
  * Harness for interacting with `tn-table` in tests.
@@ -7223,6 +7557,79 @@ class TnTableHarness extends ComponentHarness {
7223
7557
  const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7224
7558
  return row.hasClass('tn-table__row--expanded');
7225
7559
  }
7560
+ // --- Clickable rows ---
7561
+ /**
7562
+ * Clicks a row (for tables with `clickable` enabled).
7563
+ *
7564
+ * @param rowIndex Zero-based index of the data row.
7565
+ */
7566
+ async clickRow(rowIndex) {
7567
+ await this.assertRowExists(rowIndex);
7568
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7569
+ await row.click();
7570
+ }
7571
+ /**
7572
+ * Sends a keyboard event to a row (Enter/Space activate clickable rows).
7573
+ *
7574
+ * @param rowIndex Zero-based index of the data row.
7575
+ * @param key Which key to press — Enter or Space.
7576
+ */
7577
+ async pressKeyOnRow(rowIndex, key) {
7578
+ await this.assertRowExists(rowIndex);
7579
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7580
+ await row.focus();
7581
+ if (key === 'enter') {
7582
+ await row.sendKeys(TestKey.ENTER);
7583
+ }
7584
+ else {
7585
+ await row.sendKeys(' ');
7586
+ }
7587
+ }
7588
+ /**
7589
+ * Checks if a row is keyboard-focusable (tabindex=0).
7590
+ *
7591
+ * @param rowIndex Zero-based index of the data row.
7592
+ */
7593
+ async isRowFocusable(rowIndex) {
7594
+ await this.assertRowExists(rowIndex);
7595
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7596
+ return (await row.getAttribute('tabindex')) === '0';
7597
+ }
7598
+ // --- Loading ---
7599
+ /**
7600
+ * Checks whether the table is currently in the loading state.
7601
+ *
7602
+ * @returns Promise resolving to true if the loading overlay is visible.
7603
+ */
7604
+ async isLoading() {
7605
+ const overlay = await this.locatorForOptional('.tn-table__loading-overlay')();
7606
+ return overlay !== null;
7607
+ }
7608
+ // --- Active row ---
7609
+ /**
7610
+ * Checks if a data row is currently marked active.
7611
+ *
7612
+ * @param rowIndex Zero-based index of the data row.
7613
+ * @returns Promise resolving to true if the row has the active class.
7614
+ */
7615
+ async isRowActive(rowIndex) {
7616
+ await this.assertRowExists(rowIndex);
7617
+ const row = await this.locatorFor(`.tn-table__row[data-row-index="${rowIndex}"]`)();
7618
+ return row.hasClass('tn-table__row--active');
7619
+ }
7620
+ /**
7621
+ * Gets the index of the currently active row, or null if none is active.
7622
+ *
7623
+ * @returns Promise resolving to the active row index or null.
7624
+ */
7625
+ async getActiveRowIndex() {
7626
+ const row = await this.locatorForOptional('.tn-table__row--active')();
7627
+ if (!row) {
7628
+ return null;
7629
+ }
7630
+ const attr = await row.getAttribute('data-row-index');
7631
+ return attr === null ? null : Number(attr);
7632
+ }
7226
7633
  /**
7227
7634
  * Gets the text content of an expanded detail row.
7228
7635
  *
@@ -7795,7 +8202,7 @@ class TnNestedTreeNodeComponent extends CdkNestedTreeNode {
7795
8202
  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
8203
  { provide: CdkNestedTreeNode, useExisting: TnNestedTreeNodeComponent },
7797
8204
  { 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 });
8205
+ ], 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
8206
  }
7800
8207
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnNestedTreeNodeComponent, decorators: [{
7801
8208
  type: Component,
@@ -7807,7 +8214,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
7807
8214
  '[attr.aria-level]': 'level + 1',
7808
8215
  '[attr.aria-expanded]': 'isExpandable ? isExpanded : null',
7809
8216
  '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"] }]
8217
+ }, 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
8218
  }], ctorParameters: () => [] });
7812
8219
 
7813
8220
  var ModifierKeys;
@@ -8361,51 +8768,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
8361
8768
  }]
8362
8769
  }] });
8363
8770
 
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 }] }] } });
8408
-
8409
8771
  class TnBrandedSpinnerComponent {
8410
8772
  ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
8411
8773
  paths = [];
@@ -8811,11 +9173,11 @@ class TnCalendarHeaderComponent {
8811
9173
  this.nextClicked.emit();
8812
9174
  }
8813
9175
  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"] });
9176
+ 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
9177
  }
8816
9178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCalendarHeaderComponent, decorators: [{
8817
9179
  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"] }]
9180
+ 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
9181
  }], 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
9182
 
8821
9183
  class TnMonthViewComponent {
@@ -8981,11 +9343,11 @@ class TnMonthViewComponent {
8981
9343
  }
8982
9344
  }
8983
9345
  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"] });
9346
+ 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
9347
  }
8986
9348
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMonthViewComponent, decorators: [{
8987
9349
  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"] }]
9350
+ 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
9351
  }], 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
9352
 
8991
9353
  class TnMultiYearViewComponent {
@@ -9550,7 +9912,7 @@ class TnDateInputComponent {
9550
9912
  useExisting: forwardRef(() => TnDateInputComponent),
9551
9913
  multi: true
9552
9914
  }
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"] }] });
9915
+ ], 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
9916
  }
9555
9917
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDateInputComponent, decorators: [{
9556
9918
  type: Component,
@@ -9562,7 +9924,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
9562
9924
  }
9563
9925
  ], host: {
9564
9926
  '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"] }]
9927
+ }, 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
9928
  }], 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
9929
 
9568
9930
  /**
@@ -10115,7 +10477,7 @@ class TnDateRangeInputComponent {
10115
10477
  useExisting: forwardRef(() => TnDateRangeInputComponent),
10116
10478
  multi: true
10117
10479
  }
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"] }] });
10480
+ ], 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
10481
  }
10120
10482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDateRangeInputComponent, decorators: [{
10121
10483
  type: Component,
@@ -10127,7 +10489,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
10127
10489
  }
10128
10490
  ], host: {
10129
10491
  '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"] }]
10492
+ }, 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
10493
  }], 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
10494
 
10133
10495
  /**
@@ -10870,7 +11232,7 @@ class TnButtonToggleComponent {
10870
11232
  useExisting: forwardRef(() => TnButtonToggleComponent),
10871
11233
  multi: true
10872
11234
  }
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 });
11235
+ ], 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
11236
  }
10875
11237
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnButtonToggleComponent, decorators: [{
10876
11238
  type: Component,
@@ -10887,7 +11249,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
10887
11249
  '[class.tn-button-toggle--disabled]': 'isDisabled()',
10888
11250
  '[class.tn-button-toggle--standalone]': '!buttonToggleGroup',
10889
11251
  '(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"] }]
11252
+ }, 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
11253
  }], 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
11254
 
10893
11255
  class TnButtonToggleGroupComponent {
@@ -10897,6 +11259,22 @@ class TnButtonToggleGroupComponent {
10897
11259
  name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
10898
11260
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
10899
11261
  ariaLabelledby = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
11262
+ /**
11263
+ * Overrides the background color of checked toggles in this group. Accepts
11264
+ * any CSS color value (`#hex`, `rgb()`, `var(--token)`, etc.). When null
11265
+ * (default), falls back to `--tn-alt-bg2`.
11266
+ */
11267
+ checkedBg = input(null, ...(ngDevMode ? [{ debugName: "checkedBg" }] : []));
11268
+ /**
11269
+ * Overrides the text color of checked toggles in this group. Defaults to
11270
+ * `--tn-fg1` when null.
11271
+ */
11272
+ checkedColor = input(null, ...(ngDevMode ? [{ debugName: "checkedColor" }] : []));
11273
+ /**
11274
+ * Overrides the border color of checked toggles in this group. Defaults to
11275
+ * `--tn-lines` when null.
11276
+ */
11277
+ checkedBorder = input(null, ...(ngDevMode ? [{ debugName: "checkedBorder" }] : []));
10900
11278
  /**
10901
11279
  * Test-id applied to the group root. Rendered under whichever attribute name
10902
11280
  * is configured via `TN_TEST_ATTR` (default `data-testid`).
@@ -10916,6 +11294,10 @@ class TnButtonToggleGroupComponent {
10916
11294
  const toggles = this.buttonToggles();
10917
11295
  toggles.forEach(toggle => {
10918
11296
  toggle.buttonToggleGroup = this;
11297
+ // Track each toggle's value signal so the effect re-runs after @for-driven
11298
+ // bindings resolve. Without this, an initial value written before child
11299
+ // value signals have propagated would never reach the toggles.
11300
+ toggle.value();
10919
11301
  });
10920
11302
  // Re-apply stored value to toggles that weren't available during writeValue
10921
11303
  if (toggles.length > 0) {
@@ -11030,7 +11412,7 @@ class TnButtonToggleGroupComponent {
11030
11412
  });
11031
11413
  }
11032
11414
  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: [
11415
+ 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
11416
  {
11035
11417
  provide: NG_VALUE_ACCESSOR,
11036
11418
  useExisting: forwardRef(() => TnButtonToggleGroupComponent),
@@ -11047,9 +11429,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
11047
11429
  multi: true
11048
11430
  }
11049
11431
  ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
11050
- 'class': 'tn-button-toggle-group'
11432
+ 'class': 'tn-button-toggle-group',
11433
+ '[style.--tn-button-toggle-checked-bg]': 'checkedBg()',
11434
+ '[style.--tn-button-toggle-checked-color]': 'checkedColor()',
11435
+ '[style.--tn-button-toggle-checked-border]': 'checkedBorder()'
11051
11436
  }, 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"] }] } });
11437
+ }], 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
11438
 
11054
11439
  /**
11055
11440
  * Harness for interacting with `tn-button-toggle` in tests.
@@ -12129,7 +12514,7 @@ class TnStepperComponent {
12129
12514
  return index;
12130
12515
  }
12131
12516
  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: [
12517
+ 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
12518
  trigger('stepTransition', [
12134
12519
  transition(':enter', [
12135
12520
  style({ opacity: 0, transform: 'translateX(50px)' }),
@@ -12149,7 +12534,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
12149
12534
  ])
12150
12535
  ], host: {
12151
12536
  '(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"] }]
12537
+ }, 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
12538
  }], 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
12539
 
12155
12540
  /**
@@ -12505,7 +12890,7 @@ class TnFilePickerPopupComponent {
12505
12890
  return `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}/${date.getFullYear()} ${timePart}`;
12506
12891
  }
12507
12892
  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" }] });
12893
+ 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
12894
  }
12510
12895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFilePickerPopupComponent, decorators: [{
12511
12896
  type: Component,
@@ -12522,7 +12907,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
12522
12907
  TruncatePathPipe
12523
12908
  ], host: {
12524
12909
  '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"] }]
12910
+ }, 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
12911
  }], 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
12912
 
12528
12913
  class TnFilePickerComponent {
@@ -12982,7 +13367,7 @@ class TnFilePickerComponent {
12982
13367
  useExisting: forwardRef(() => TnFilePickerComponent),
12983
13368
  multi: true
12984
13369
  }
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" }] });
13370
+ ], 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
13371
  }
12987
13372
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFilePickerComponent, decorators: [{
12988
13373
  type: Component,
@@ -13004,7 +13389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
13004
13389
  ], host: {
13005
13390
  'class': 'tn-file-picker',
13006
13391
  '[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"] }]
13392
+ }, 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
13393
  }], 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
13394
 
13010
13395
  /**
@@ -13141,14 +13526,14 @@ class TnToastComponent {
13141
13526
  onAction = () => { };
13142
13527
  onDismiss = () => { };
13143
13528
  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 });
13529
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnToastComponent, isStandalone: true, selector: "tn-toast", host: { properties: { "class.tn-toast--top": "position() === \"top\"", "class.tn-toast--bottom": "position() === \"bottom\"" } }, ngImport: i0, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
13145
13530
  }
13146
13531
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, decorators: [{
13147
13532
  type: Component,
13148
13533
  args: [{ selector: 'tn-toast', standalone: true, imports: [TnIconComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
13149
13534
  '[class.tn-toast--top]': 'position() === "top"',
13150
13535
  '[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"] }]
13536
+ }, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:1rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"] }]
13152
13537
  }] });
13153
13538
 
13154
13539
  class TnToastRef {