@truenas/ui-components 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
2
2
  import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/portal';
3
3
  import * as i0 from '@angular/core';
4
- import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, inject, Renderer2, ElementRef, effect, Directive, ViewContainerRef, output, viewChild, signal, untracked, isDevMode, forwardRef, model, afterNextRender, Injectable, contentChildren, Pipe, HostListener, linkedSignal, contentChild, TemplateRef, ChangeDetectorRef, DestroyRef, isSignal, IterableDiffers, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
4
+ import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, inject, Injector, InjectionToken, Renderer2, ElementRef, effect, Directive, ViewContainerRef, output, viewChild, signal, untracked, isDevMode, forwardRef, model, afterNextRender, Injectable, contentChildren, Pipe, HostListener, linkedSignal, contentChild, TemplateRef, ChangeDetectorRef, DestroyRef, isSignal, IterableDiffers, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
5
5
  import * as i1$3 from '@angular/forms';
6
- import { NG_VALUE_ACCESSOR, FormsModule, NgControl, Validators } from '@angular/forms';
6
+ import { NgControl, NG_VALUE_ACCESSOR, FormsModule, Validators } from '@angular/forms';
7
7
  import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
8
8
  import * as i1 from '@angular/cdk/a11y';
9
9
  import { A11yModule, FocusMonitor } from '@angular/cdk/a11y';
@@ -169,6 +169,59 @@ function composeTestId(type, value) {
169
169
  return `${prefix}-${body}`;
170
170
  }
171
171
 
172
+ /**
173
+ * Resolve a form control's test-id base, falling back to the bound control's
174
+ * name (`formControlName`, a named `[formControl]`, or `ngModel`) when the
175
+ * consumer provides no explicit `testId`.
176
+ *
177
+ * Call from a form control's field initializer and bind the result on the
178
+ * element that represents the control:
179
+ *
180
+ * ```ts
181
+ * protected resolvedTestId = controlTestId(this.testId);
182
+ * ```
183
+ * ```html
184
+ * <input tnTestIdType="input" [tnTestId]="resolvedTestId()" />
185
+ * ```
186
+ *
187
+ * The element-type prefix (`tnTestIdType`) still applies, so a
188
+ * `<tn-input formControlName="sshPort">` with no `testId` emits
189
+ * `input-ssh-port` — exactly what a consumer would otherwise hand-write as
190
+ * `testId="ssh-port"`. An explicit `testId` always wins.
191
+ *
192
+ * Two deliberate constraints make this safe inside a `ControlValueAccessor`:
193
+ *
194
+ * - **`self`-scoped**: the lookup reads only the `NgControl` on the component's
195
+ * OWN host element, never an ancestor's. A composite control whose template
196
+ * embeds child controls therefore can't leak its own name onto those children
197
+ * (which would make their ids collide).
198
+ * - **lazy**: the `NgControl` is resolved on first read, not in the injection
199
+ * context. Eagerly injecting `NgControl` into a component that provides
200
+ * `NG_VALUE_ACCESSOR` is circular (the control needs the value accessor, the
201
+ * accessor would need the control); reading it after construction is not.
202
+ *
203
+ * Must be called within an injection context (a field initializer or
204
+ * constructor).
205
+ */
206
+ function controlTestId(testId) {
207
+ const injector = inject(Injector);
208
+ // `undefined` = not yet resolved; `null` = resolved, no bound control.
209
+ let ngControl;
210
+ return computed(() => {
211
+ const explicit = testId();
212
+ // An explicit base (anything that survives normalization) always wins.
213
+ if (composeTestId(undefined, explicit) !== '') {
214
+ return explicit;
215
+ }
216
+ if (ngControl === undefined) {
217
+ ngControl = injector.get(NgControl, null, { self: true, optional: true });
218
+ }
219
+ // `?? explicit` preserves the falsy passthrough — with no control name the
220
+ // base stays empty and the directive writes no attribute.
221
+ return ngControl?.name ?? explicit;
222
+ });
223
+ }
224
+
172
225
  /**
173
226
  * Controls which attribute name the library renders `testId` values to.
174
227
  *
@@ -219,6 +272,10 @@ function writeTestId(renderer, element, attrName, composed) {
219
272
  * {@link composeTestId}). When no `tnTestIdType` is set the value is written
220
273
  * verbatim, so existing call sites are unaffected.
221
274
  *
275
+ * Form controls derive an unset base from the bound control's name via
276
+ * {@link controlTestId} before passing it here, so a control stays targetable by
277
+ * automation without the consumer repeating the name as a `testId`.
278
+ *
222
279
  * @example
223
280
  * ```html
224
281
  * <!-- component-owned prefix: emits data-testid="button-save" -->
@@ -2022,8 +2079,14 @@ class TnBannerComponent {
2022
2079
  actionContent = contentChildren(TnBannerActionDirective, ...(ngDevMode ? [{ debugName: "actionContent" }] : []));
2023
2080
  /** Signal indicating whether action content has been projected */
2024
2081
  hasAction = computed(() => this.actionContent().length > 0, ...(ngDevMode ? [{ debugName: "hasAction" }] : []));
2082
+ /**
2083
+ * Signal indicating whether the built-in structured layout (icon + heading/message)
2084
+ * should be rendered. When neither heading nor message is provided, the banner
2085
+ * instead renders arbitrary projected content from the default slot.
2086
+ */
2087
+ hasStructuredContent = computed(() => !!this.heading() || !!this.message(), ...(ngDevMode ? [{ debugName: "hasStructuredContent" }] : []));
2025
2088
  // Signal-based inputs (modern Angular 19+)
2026
- heading = input.required(...(ngDevMode ? [{ debugName: "heading" }] : []));
2089
+ heading = input(undefined, ...(ngDevMode ? [{ debugName: "heading" }] : []));
2027
2090
  message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
2028
2091
  type = input('info', ...(ngDevMode ? [{ debugName: "type" }] : []));
2029
2092
  bordered = input(false, ...(ngDevMode ? [{ debugName: "bordered" }] : []));
@@ -2082,12 +2145,12 @@ class TnBannerComponent {
2082
2145
  return result;
2083
2146
  }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
2084
2147
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2085
- 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", "testId", "fullSize", "customSize"] }] });
2148
+ 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: false, 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 @if (hasStructuredContent()) {\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 @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\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", "testId", "fullSize", "customSize"] }] });
2086
2149
  }
2087
2150
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, decorators: [{
2088
2151
  type: Component,
2089
- 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"] }]
2090
- }], 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 }] }] } });
2152
+ args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n @if (hasStructuredContent()) {\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 @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\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"] }]
2153
+ }], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], 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 }] }] } });
2091
2154
 
2092
2155
  /**
2093
2156
  * Harness for interacting with tn-banner in tests.
@@ -3286,6 +3349,8 @@ class TnInputComponent {
3286
3349
  inputType = input(InputType.PlainText, ...(ngDevMode ? [{ debugName: "inputType" }] : []));
3287
3350
  placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
3288
3351
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
3352
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
3353
+ resolvedTestId = controlTestId(this.testId);
3289
3354
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
3290
3355
  multiline = input(false, ...(ngDevMode ? [{ debugName: "multiline" }] : []));
3291
3356
  rows = input(3, ...(ngDevMode ? [{ debugName: "rows" }] : []));
@@ -3588,7 +3653,7 @@ class TnInputComponent {
3588
3653
  useExisting: forwardRef(() => TnInputComponent),
3589
3654
  multi: true
3590
3655
  }
3591
- ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value()\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;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}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
3656
+ ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value()\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"resolvedTestId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"resolvedTestId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;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}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
3592
3657
  }
3593
3658
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, decorators: [{
3594
3659
  type: Component,
@@ -3598,7 +3663,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3598
3663
  useExisting: forwardRef(() => TnInputComponent),
3599
3664
  multi: true
3600
3665
  }
3601
- ], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value()\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;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}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
3666
+ ], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value()\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"resolvedTestId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"resolvedTestId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;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}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
3602
3667
  }], propDecorators: { inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], inputType: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputType", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], allowDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDecimals", required: false }] }], sizeStandard: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeStandard", required: false }] }], sizeDefaultUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeDefaultUnit", required: false }] }], sizeRound: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeRound", required: false }] }], showPasswordToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPasswordToggle", required: false }] }], prefixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIcon", required: false }] }], prefixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIconLibrary", required: false }] }], suffixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIcon", required: false }] }], suffixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconLibrary", required: false }] }], suffixIconAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconAriaLabel", required: false }] }], suffixActionTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixActionTestId", required: false }] }], onSuffixAction: [{ type: i0.Output, args: ["onSuffixAction"] }] } });
3603
3668
 
3604
3669
  /**
@@ -5871,6 +5936,8 @@ class TnSlideToggleComponent {
5871
5936
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
5872
5937
  color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
5873
5938
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
5939
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
5940
+ resolvedTestId = controlTestId(this.testId);
5874
5941
  ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
5875
5942
  ariaLabelledby = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
5876
5943
  checked = input(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
@@ -5953,7 +6020,7 @@ class TnSlideToggleComponent {
5953
6020
  useExisting: forwardRef(() => TnSlideToggleComponent),
5954
6021
  multi: true
5955
6022
  }
5956
- ], viewQueries: [{ propertyName: "toggleEl", first: true, predicate: ["toggleEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-slide-toggle__label\" tnTestIdType=\"toggle\" [for]=\"id\" [tnTestId]=\"testId()\">\n\n <!-- Label before toggle -->\n @if (label() && labelPosition() === 'before') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--before\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n <!-- Toggle track and thumb -->\n <div class=\"tn-slide-toggle__bar\">\n <input\n #toggleEl\n type=\"checkbox\"\n class=\"tn-slide-toggle__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n (change)=\"onToggleChange($event)\"\n />\n\n <div class=\"tn-slide-toggle__track\">\n <div class=\"tn-slide-toggle__track-fill\"></div>\n </div>\n\n <div class=\"tn-slide-toggle__thumb-container\">\n <div class=\"tn-slide-toggle__thumb\">\n <div class=\"tn-slide-toggle__ripple\"></div>\n <!-- State icon -->\n <div class=\"tn-slide-toggle__icon\">\n @if (effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__check-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z\" fill=\"currentColor\"/>\n </svg>\n }\n @if (!effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__minus-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M19 13H5v-2h14v2z\" fill=\"currentColor\"/>\n </svg>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Label after toggle -->\n @if (label() && labelPosition() === 'after') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--after\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n </label>\n</div>", styles: [".tn-slide-toggle{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;line-height:1;font-family:inherit}.tn-slide-toggle__label{display:flex;align-items:center;cursor:inherit}.tn-slide-toggle__label-text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-slide-toggle__label-text{font-size:14px;line-height:1.4;color:var(--tn-fg1);transition:color .2s ease}.tn-slide-toggle__label-text--before{margin-right:8px}.tn-slide-toggle__label-text--after{margin-left:8px}.tn-slide-toggle__input{position:absolute;opacity:0;width:0;height:0;margin:0;pointer-events:none}.tn-slide-toggle__input:focus+.tn-slide-toggle__track{box-shadow:0 0 0 2px rgba(var(--tn-primary-rgb, 0, 123, 255),.2)}.tn-slide-toggle__bar{position:relative;display:flex;align-items:center;flex-shrink:0}.tn-slide-toggle__track{position:relative;width:52px;height:32px;border-radius:16px;background-color:var(--tn-lines);border:1px solid transparent;transition:background-color .2s ease,border-color .2s ease;overflow:hidden}.tn-slide-toggle__track-fill{position:absolute;inset:0;border-radius:inherit;background-color:var(--tn-primary, #007cba);opacity:0;transform:scaleX(0);transform-origin:left center;transition:opacity .2s ease,transform .2s ease}.tn-slide-toggle__thumb-container{position:absolute;top:50%;left:4px;transform:translateY(-50%);width:24px;height:24px;transition:transform .2s ease;z-index:1}.tn-slide-toggle__thumb{position:relative;width:24px;height:24px;border-radius:50%;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);box-shadow:0 2px 4px #0003;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease;display:flex;align-items:center;justify-content:center}.tn-slide-toggle__ripple{position:absolute;width:40px;height:40px;border-radius:50%;background-color:transparent;top:50%;left:50%;transform:translate(-50%,-50%);opacity:0;transition:opacity .2s ease,background-color .2s ease}.tn-slide-toggle__icon{position:relative;display:flex;align-items:center;justify-content:center;z-index:2;pointer-events:none}.tn-slide-toggle__check-icon,.tn-slide-toggle__minus-icon{transition:opacity .2s ease,transform .2s ease;color:var(--tn-fg2)}.tn-slide-toggle:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.08}.tn-slide-toggle--checked .tn-slide-toggle__track{background-color:var(--tn-primary);opacity:.5}.tn-slide-toggle--checked .tn-slide-toggle__track-fill{opacity:1;transform:scaleX(1)}.tn-slide-toggle--checked .tn-slide-toggle__thumb-container{transform:translateY(-50%) translate(24px)}.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-bg2);border-color:var(--tn-primary)}.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-primary)}.tn-slide-toggle--checked:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.12}.tn-slide-toggle--disabled{cursor:not-allowed;opacity:.6}.tn-slide-toggle--disabled .tn-slide-toggle__label-text{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled .tn-slide-toggle__track{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1);box-shadow:none}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track,.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track-fill{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled:hover .tn-slide-toggle__ripple{opacity:0}.tn-slide-toggle--accent{--tn-primary: var(--tn-accent)}.tn-slide-toggle--warn{--tn-primary: var(--tn-red)}.tn-slide-toggle .tn-slide-toggle__input:focus-visible+.tn-slide-toggle__track{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-slide-toggle:active:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.16;transform:translate(-50%,-50%) scale(1.1)}@media(prefers-contrast:high){.tn-slide-toggle .tn-slide-toggle__track{border-color:var(--tn-fg1)}.tn-slide-toggle .tn-slide-toggle__thumb{border-width:2px}.tn-slide-toggle--disabled .tn-slide-toggle__track,.tn-slide-toggle--disabled .tn-slide-toggle__thumb{border-color:var(--tn-alt-fg1)}}@media(prefers-reduced-motion:reduce){.tn-slide-toggle .tn-slide-toggle__track,.tn-slide-toggle .tn-slide-toggle__track-fill,.tn-slide-toggle .tn-slide-toggle__thumb-container,.tn-slide-toggle .tn-slide-toggle__thumb,.tn-slide-toggle .tn-slide-toggle__ripple{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
6023
+ ], viewQueries: [{ propertyName: "toggleEl", first: true, predicate: ["toggleEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-slide-toggle__label\" tnTestIdType=\"toggle\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n\n <!-- Label before toggle -->\n @if (label() && labelPosition() === 'before') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--before\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n <!-- Toggle track and thumb -->\n <div class=\"tn-slide-toggle__bar\">\n <input\n #toggleEl\n type=\"checkbox\"\n class=\"tn-slide-toggle__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n (change)=\"onToggleChange($event)\"\n />\n\n <div class=\"tn-slide-toggle__track\">\n <div class=\"tn-slide-toggle__track-fill\"></div>\n </div>\n\n <div class=\"tn-slide-toggle__thumb-container\">\n <div class=\"tn-slide-toggle__thumb\">\n <div class=\"tn-slide-toggle__ripple\"></div>\n <!-- State icon -->\n <div class=\"tn-slide-toggle__icon\">\n @if (effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__check-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z\" fill=\"currentColor\"/>\n </svg>\n }\n @if (!effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__minus-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M19 13H5v-2h14v2z\" fill=\"currentColor\"/>\n </svg>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Label after toggle -->\n @if (label() && labelPosition() === 'after') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--after\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n </label>\n</div>", styles: [".tn-slide-toggle{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;line-height:1;font-family:inherit}.tn-slide-toggle__label{display:flex;align-items:center;cursor:inherit}.tn-slide-toggle__label-text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-slide-toggle__label-text{font-size:14px;line-height:1.4;color:var(--tn-fg1);transition:color .2s ease}.tn-slide-toggle__label-text--before{margin-right:8px}.tn-slide-toggle__label-text--after{margin-left:8px}.tn-slide-toggle__input{position:absolute;opacity:0;width:0;height:0;margin:0;pointer-events:none}.tn-slide-toggle__input:focus+.tn-slide-toggle__track{box-shadow:0 0 0 2px rgba(var(--tn-primary-rgb, 0, 123, 255),.2)}.tn-slide-toggle__bar{position:relative;display:flex;align-items:center;flex-shrink:0}.tn-slide-toggle__track{position:relative;width:52px;height:32px;border-radius:16px;background-color:var(--tn-lines);border:1px solid transparent;transition:background-color .2s ease,border-color .2s ease;overflow:hidden}.tn-slide-toggle__track-fill{position:absolute;inset:0;border-radius:inherit;background-color:var(--tn-primary, #007cba);opacity:0;transform:scaleX(0);transform-origin:left center;transition:opacity .2s ease,transform .2s ease}.tn-slide-toggle__thumb-container{position:absolute;top:50%;left:4px;transform:translateY(-50%);width:24px;height:24px;transition:transform .2s ease;z-index:1}.tn-slide-toggle__thumb{position:relative;width:24px;height:24px;border-radius:50%;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);box-shadow:0 2px 4px #0003;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease;display:flex;align-items:center;justify-content:center}.tn-slide-toggle__ripple{position:absolute;width:40px;height:40px;border-radius:50%;background-color:transparent;top:50%;left:50%;transform:translate(-50%,-50%);opacity:0;transition:opacity .2s ease,background-color .2s ease}.tn-slide-toggle__icon{position:relative;display:flex;align-items:center;justify-content:center;z-index:2;pointer-events:none}.tn-slide-toggle__check-icon,.tn-slide-toggle__minus-icon{transition:opacity .2s ease,transform .2s ease;color:var(--tn-fg2)}.tn-slide-toggle:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.08}.tn-slide-toggle--checked .tn-slide-toggle__track{background-color:var(--tn-primary);opacity:.5}.tn-slide-toggle--checked .tn-slide-toggle__track-fill{opacity:1;transform:scaleX(1)}.tn-slide-toggle--checked .tn-slide-toggle__thumb-container{transform:translateY(-50%) translate(24px)}.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-bg2);border-color:var(--tn-primary)}.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-primary)}.tn-slide-toggle--checked:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.12}.tn-slide-toggle--disabled{cursor:not-allowed;opacity:.6}.tn-slide-toggle--disabled .tn-slide-toggle__label-text{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled .tn-slide-toggle__track{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1);box-shadow:none}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track,.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track-fill{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled:hover .tn-slide-toggle__ripple{opacity:0}.tn-slide-toggle--accent{--tn-primary: var(--tn-accent)}.tn-slide-toggle--warn{--tn-primary: var(--tn-red)}.tn-slide-toggle .tn-slide-toggle__input:focus-visible+.tn-slide-toggle__track{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-slide-toggle:active:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.16;transform:translate(-50%,-50%) scale(1.1)}@media(prefers-contrast:high){.tn-slide-toggle .tn-slide-toggle__track{border-color:var(--tn-fg1)}.tn-slide-toggle .tn-slide-toggle__thumb{border-width:2px}.tn-slide-toggle--disabled .tn-slide-toggle__track,.tn-slide-toggle--disabled .tn-slide-toggle__thumb{border-color:var(--tn-alt-fg1)}}@media(prefers-reduced-motion:reduce){.tn-slide-toggle .tn-slide-toggle__track,.tn-slide-toggle .tn-slide-toggle__track-fill,.tn-slide-toggle .tn-slide-toggle__thumb-container,.tn-slide-toggle .tn-slide-toggle__thumb,.tn-slide-toggle .tn-slide-toggle__ripple{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
5957
6024
  }
5958
6025
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSlideToggleComponent, decorators: [{
5959
6026
  type: Component,
@@ -5963,7 +6030,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
5963
6030
  useExisting: forwardRef(() => TnSlideToggleComponent),
5964
6031
  multi: true
5965
6032
  }
5966
- ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-slide-toggle__label\" tnTestIdType=\"toggle\" [for]=\"id\" [tnTestId]=\"testId()\">\n\n <!-- Label before toggle -->\n @if (label() && labelPosition() === 'before') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--before\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n <!-- Toggle track and thumb -->\n <div class=\"tn-slide-toggle__bar\">\n <input\n #toggleEl\n type=\"checkbox\"\n class=\"tn-slide-toggle__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n (change)=\"onToggleChange($event)\"\n />\n\n <div class=\"tn-slide-toggle__track\">\n <div class=\"tn-slide-toggle__track-fill\"></div>\n </div>\n\n <div class=\"tn-slide-toggle__thumb-container\">\n <div class=\"tn-slide-toggle__thumb\">\n <div class=\"tn-slide-toggle__ripple\"></div>\n <!-- State icon -->\n <div class=\"tn-slide-toggle__icon\">\n @if (effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__check-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z\" fill=\"currentColor\"/>\n </svg>\n }\n @if (!effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__minus-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M19 13H5v-2h14v2z\" fill=\"currentColor\"/>\n </svg>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Label after toggle -->\n @if (label() && labelPosition() === 'after') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--after\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n </label>\n</div>", styles: [".tn-slide-toggle{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;line-height:1;font-family:inherit}.tn-slide-toggle__label{display:flex;align-items:center;cursor:inherit}.tn-slide-toggle__label-text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-slide-toggle__label-text{font-size:14px;line-height:1.4;color:var(--tn-fg1);transition:color .2s ease}.tn-slide-toggle__label-text--before{margin-right:8px}.tn-slide-toggle__label-text--after{margin-left:8px}.tn-slide-toggle__input{position:absolute;opacity:0;width:0;height:0;margin:0;pointer-events:none}.tn-slide-toggle__input:focus+.tn-slide-toggle__track{box-shadow:0 0 0 2px rgba(var(--tn-primary-rgb, 0, 123, 255),.2)}.tn-slide-toggle__bar{position:relative;display:flex;align-items:center;flex-shrink:0}.tn-slide-toggle__track{position:relative;width:52px;height:32px;border-radius:16px;background-color:var(--tn-lines);border:1px solid transparent;transition:background-color .2s ease,border-color .2s ease;overflow:hidden}.tn-slide-toggle__track-fill{position:absolute;inset:0;border-radius:inherit;background-color:var(--tn-primary, #007cba);opacity:0;transform:scaleX(0);transform-origin:left center;transition:opacity .2s ease,transform .2s ease}.tn-slide-toggle__thumb-container{position:absolute;top:50%;left:4px;transform:translateY(-50%);width:24px;height:24px;transition:transform .2s ease;z-index:1}.tn-slide-toggle__thumb{position:relative;width:24px;height:24px;border-radius:50%;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);box-shadow:0 2px 4px #0003;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease;display:flex;align-items:center;justify-content:center}.tn-slide-toggle__ripple{position:absolute;width:40px;height:40px;border-radius:50%;background-color:transparent;top:50%;left:50%;transform:translate(-50%,-50%);opacity:0;transition:opacity .2s ease,background-color .2s ease}.tn-slide-toggle__icon{position:relative;display:flex;align-items:center;justify-content:center;z-index:2;pointer-events:none}.tn-slide-toggle__check-icon,.tn-slide-toggle__minus-icon{transition:opacity .2s ease,transform .2s ease;color:var(--tn-fg2)}.tn-slide-toggle:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.08}.tn-slide-toggle--checked .tn-slide-toggle__track{background-color:var(--tn-primary);opacity:.5}.tn-slide-toggle--checked .tn-slide-toggle__track-fill{opacity:1;transform:scaleX(1)}.tn-slide-toggle--checked .tn-slide-toggle__thumb-container{transform:translateY(-50%) translate(24px)}.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-bg2);border-color:var(--tn-primary)}.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-primary)}.tn-slide-toggle--checked:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.12}.tn-slide-toggle--disabled{cursor:not-allowed;opacity:.6}.tn-slide-toggle--disabled .tn-slide-toggle__label-text{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled .tn-slide-toggle__track{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1);box-shadow:none}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track,.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track-fill{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled:hover .tn-slide-toggle__ripple{opacity:0}.tn-slide-toggle--accent{--tn-primary: var(--tn-accent)}.tn-slide-toggle--warn{--tn-primary: var(--tn-red)}.tn-slide-toggle .tn-slide-toggle__input:focus-visible+.tn-slide-toggle__track{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-slide-toggle:active:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.16;transform:translate(-50%,-50%) scale(1.1)}@media(prefers-contrast:high){.tn-slide-toggle .tn-slide-toggle__track{border-color:var(--tn-fg1)}.tn-slide-toggle .tn-slide-toggle__thumb{border-width:2px}.tn-slide-toggle--disabled .tn-slide-toggle__track,.tn-slide-toggle--disabled .tn-slide-toggle__thumb{border-color:var(--tn-alt-fg1)}}@media(prefers-reduced-motion:reduce){.tn-slide-toggle .tn-slide-toggle__track,.tn-slide-toggle .tn-slide-toggle__track-fill,.tn-slide-toggle .tn-slide-toggle__thumb-container,.tn-slide-toggle .tn-slide-toggle__thumb,.tn-slide-toggle .tn-slide-toggle__ripple{transition:none}}\n"] }]
6033
+ ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-slide-toggle__label\" tnTestIdType=\"toggle\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n\n <!-- Label before toggle -->\n @if (label() && labelPosition() === 'before') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--before\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n <!-- Toggle track and thumb -->\n <div class=\"tn-slide-toggle__bar\">\n <input\n #toggleEl\n type=\"checkbox\"\n class=\"tn-slide-toggle__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n (change)=\"onToggleChange($event)\"\n />\n\n <div class=\"tn-slide-toggle__track\">\n <div class=\"tn-slide-toggle__track-fill\"></div>\n </div>\n\n <div class=\"tn-slide-toggle__thumb-container\">\n <div class=\"tn-slide-toggle__thumb\">\n <div class=\"tn-slide-toggle__ripple\"></div>\n <!-- State icon -->\n <div class=\"tn-slide-toggle__icon\">\n @if (effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__check-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z\" fill=\"currentColor\"/>\n </svg>\n }\n @if (!effectiveChecked()) {\n <svg\n class=\"tn-slide-toggle__minus-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\">\n <path d=\"M19 13H5v-2h14v2z\" fill=\"currentColor\"/>\n </svg>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Label after toggle -->\n @if (label() && labelPosition() === 'after') {\n <span\n class=\"tn-slide-toggle__label-text tn-slide-toggle__label-text--after\"\n tabindex=\"0\"\n role=\"button\"\n [innerHTML]=\"label() | tnLabelMarkup\"\n (click)=\"onLabelClick()\"\n (keydown.enter)=\"onLabelClick()\"\n (keydown.space)=\"onLabelClick()\">\n </span>\n }\n\n </label>\n</div>", styles: [".tn-slide-toggle{display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;line-height:1;font-family:inherit}.tn-slide-toggle__label{display:flex;align-items:center;cursor:inherit}.tn-slide-toggle__label-text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-slide-toggle__label-text{font-size:14px;line-height:1.4;color:var(--tn-fg1);transition:color .2s ease}.tn-slide-toggle__label-text--before{margin-right:8px}.tn-slide-toggle__label-text--after{margin-left:8px}.tn-slide-toggle__input{position:absolute;opacity:0;width:0;height:0;margin:0;pointer-events:none}.tn-slide-toggle__input:focus+.tn-slide-toggle__track{box-shadow:0 0 0 2px rgba(var(--tn-primary-rgb, 0, 123, 255),.2)}.tn-slide-toggle__bar{position:relative;display:flex;align-items:center;flex-shrink:0}.tn-slide-toggle__track{position:relative;width:52px;height:32px;border-radius:16px;background-color:var(--tn-lines);border:1px solid transparent;transition:background-color .2s ease,border-color .2s ease;overflow:hidden}.tn-slide-toggle__track-fill{position:absolute;inset:0;border-radius:inherit;background-color:var(--tn-primary, #007cba);opacity:0;transform:scaleX(0);transform-origin:left center;transition:opacity .2s ease,transform .2s ease}.tn-slide-toggle__thumb-container{position:absolute;top:50%;left:4px;transform:translateY(-50%);width:24px;height:24px;transition:transform .2s ease;z-index:1}.tn-slide-toggle__thumb{position:relative;width:24px;height:24px;border-radius:50%;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);box-shadow:0 2px 4px #0003;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease;display:flex;align-items:center;justify-content:center}.tn-slide-toggle__ripple{position:absolute;width:40px;height:40px;border-radius:50%;background-color:transparent;top:50%;left:50%;transform:translate(-50%,-50%);opacity:0;transition:opacity .2s ease,background-color .2s ease}.tn-slide-toggle__icon{position:relative;display:flex;align-items:center;justify-content:center;z-index:2;pointer-events:none}.tn-slide-toggle__check-icon,.tn-slide-toggle__minus-icon{transition:opacity .2s ease,transform .2s ease;color:var(--tn-fg2)}.tn-slide-toggle:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.08}.tn-slide-toggle--checked .tn-slide-toggle__track{background-color:var(--tn-primary);opacity:.5}.tn-slide-toggle--checked .tn-slide-toggle__track-fill{opacity:1;transform:scaleX(1)}.tn-slide-toggle--checked .tn-slide-toggle__thumb-container{transform:translateY(-50%) translate(24px)}.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-bg2);border-color:var(--tn-primary)}.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-primary)}.tn-slide-toggle--checked:hover:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.12}.tn-slide-toggle--disabled{cursor:not-allowed;opacity:.6}.tn-slide-toggle--disabled .tn-slide-toggle__label-text{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled .tn-slide-toggle__track{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1);box-shadow:none}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track,.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__track-fill{background-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__thumb{background-color:var(--tn-alt-bg2);border-color:var(--tn-alt-bg1)}.tn-slide-toggle--disabled.tn-slide-toggle--checked .tn-slide-toggle__check-icon{color:var(--tn-alt-fg1)}.tn-slide-toggle--disabled:hover .tn-slide-toggle__ripple{opacity:0}.tn-slide-toggle--accent{--tn-primary: var(--tn-accent)}.tn-slide-toggle--warn{--tn-primary: var(--tn-red)}.tn-slide-toggle .tn-slide-toggle__input:focus-visible+.tn-slide-toggle__track{outline:2px solid var(--tn-primary);outline-offset:2px}.tn-slide-toggle:active:not(.tn-slide-toggle--disabled) .tn-slide-toggle__ripple{background-color:var(--tn-primary);opacity:.16;transform:translate(-50%,-50%) scale(1.1)}@media(prefers-contrast:high){.tn-slide-toggle .tn-slide-toggle__track{border-color:var(--tn-fg1)}.tn-slide-toggle .tn-slide-toggle__thumb{border-width:2px}.tn-slide-toggle--disabled .tn-slide-toggle__track,.tn-slide-toggle--disabled .tn-slide-toggle__thumb{border-color:var(--tn-alt-fg1)}}@media(prefers-reduced-motion:reduce){.tn-slide-toggle .tn-slide-toggle__track,.tn-slide-toggle .tn-slide-toggle__track-fill,.tn-slide-toggle .tn-slide-toggle__thumb-container,.tn-slide-toggle .tn-slide-toggle__thumb,.tn-slide-toggle .tn-slide-toggle__ripple{transition:none}}\n"] }]
5967
6034
  }], propDecorators: { toggleEl: [{ type: i0.ViewChild, args: ['toggleEl', { isSignal: true }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], toggleChange: [{ type: i0.Output, args: ["toggleChange"] }] } });
5968
6035
 
5969
6036
  class TnCardComponent {
@@ -6318,6 +6385,8 @@ class TnCheckboxComponent {
6318
6385
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
6319
6386
  indeterminate = input(false, ...(ngDevMode ? [{ debugName: "indeterminate" }] : []));
6320
6387
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
6388
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
6389
+ resolvedTestId = controlTestId(this.testId);
6321
6390
  error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
6322
6391
  checked = input(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
6323
6392
  change = output();
@@ -6392,7 +6461,7 @@ class TnCheckboxComponent {
6392
6461
  useExisting: forwardRef(() => TnCheckboxComponent),
6393
6462
  multi: true
6394
6463
  }
6395
- ], queries: [{ propertyName: "labelContent", predicate: TnCheckboxLabelDirective, isSignal: true }], viewQueries: [{ propertyName: "checkboxEl", first: true, predicate: ["checkboxEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-checkbox__label\" tnTestIdType=\"checkbox\" [for]=\"id\" [tnTestId]=\"testId()\">\n <input\n #checkboxEl\n type=\"checkbox\"\n class=\"tn-checkbox__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [indeterminate]=\"indeterminate()\"\n [attr.aria-label]=\"hasProjectedLabel() ? (label() | tnLabelText) : null\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onCheckboxChange($event)\"\n />\n <span class=\"tn-checkbox__checkmark\"></span>\n @if (!hideLabel()) {\n <span class=\"tn-checkbox__text\">\n @if (hasProjectedLabel()) {\n <ng-content select=\"[tnCheckboxLabel]\" />\n } @else {\n <span [innerHTML]=\"label() | tnLabelMarkup\"></span>\n }\n </span>\n }\n </label>\n\n @if (error()) {\n <div\n class=\"tn-checkbox__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-checkbox{display:flex;flex-direction:column;gap:4px}.tn-checkbox__label{display:flex;align-items:center;gap:8px;cursor:pointer;-webkit-user-select:none;user-select:none;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);font-size:14px;line-height:1.5;color:var(--tn-fg1, #333)}.tn-checkbox__label:hover:not(.tn-checkbox--disabled) .tn-checkbox__checkmark{border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-checkbox__input:checked~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:checked~.tn-checkbox__checkmark:after{display:block}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark:after{display:block;content:\"\";left:4px;top:8px;width:8px;height:2px;background:var(--tn-primary-txt, #fff);border-radius:1px;transform:none}.tn-checkbox__input:focus~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}.tn-checkbox__input:disabled~.tn-checkbox__checkmark{background-color:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #e0e0e0);cursor:not-allowed}.tn-checkbox__input:disabled:checked~.tn-checkbox__checkmark{background-color:var(--tn-lines, #e0e0e0);border-color:var(--tn-lines, #e0e0e0)}.tn-checkbox__checkmark{position:relative;height:16px;width:16px;background-color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #ccc);border-radius:2px;transition:all .2s ease;flex-shrink:0}.tn-checkbox__checkmark:after{content:\"\";position:absolute;display:none;left:5px;top:2px;width:4px;height:8px;border:solid var(--tn-primary-txt, #fff);border-width:0 2px 2px 0;transform:rotate(45deg)}.tn-checkbox__text{flex:1;min-width:0}.tn-checkbox__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-checkbox__error{color:var(--tn-red, #dc2626);font-size:12px;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);margin-top:4px;display:flex;align-items:center;gap:4px}.tn-checkbox--disabled .tn-checkbox__label{cursor:not-allowed;color:var(--tn-fg2, #666);opacity:.6}.tn-checkbox--disabled .tn-checkbox__text{color:var(--tn-fg2, #666)}.tn-checkbox--error .tn-checkbox__checkmark{border-color:var(--tn-red, #dc2626)}.tn-checkbox--indeterminate .tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:focus-visible~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}@media(prefers-contrast:high){.tn-checkbox__checkmark{border-width:2px}}@media(prefers-reduced-motion:reduce){.tn-checkbox__checkmark{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }, { kind: "pipe", type: LabelTextPipe, name: "tnLabelText" }] });
6464
+ ], queries: [{ propertyName: "labelContent", predicate: TnCheckboxLabelDirective, isSignal: true }], viewQueries: [{ propertyName: "checkboxEl", first: true, predicate: ["checkboxEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-checkbox__label\" tnTestIdType=\"checkbox\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #checkboxEl\n type=\"checkbox\"\n class=\"tn-checkbox__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [indeterminate]=\"indeterminate()\"\n [attr.aria-label]=\"hasProjectedLabel() ? (label() | tnLabelText) : null\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onCheckboxChange($event)\"\n />\n <span class=\"tn-checkbox__checkmark\"></span>\n @if (!hideLabel()) {\n <span class=\"tn-checkbox__text\">\n @if (hasProjectedLabel()) {\n <ng-content select=\"[tnCheckboxLabel]\" />\n } @else {\n <span [innerHTML]=\"label() | tnLabelMarkup\"></span>\n }\n </span>\n }\n </label>\n\n @if (error()) {\n <div\n class=\"tn-checkbox__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-checkbox{display:flex;flex-direction:column;gap:4px}.tn-checkbox__label{display:flex;align-items:center;gap:8px;cursor:pointer;-webkit-user-select:none;user-select:none;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);font-size:14px;line-height:1.5;color:var(--tn-fg1, #333)}.tn-checkbox__label:hover:not(.tn-checkbox--disabled) .tn-checkbox__checkmark{border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-checkbox__input:checked~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:checked~.tn-checkbox__checkmark:after{display:block}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark:after{display:block;content:\"\";left:4px;top:8px;width:8px;height:2px;background:var(--tn-primary-txt, #fff);border-radius:1px;transform:none}.tn-checkbox__input:focus~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}.tn-checkbox__input:disabled~.tn-checkbox__checkmark{background-color:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #e0e0e0);cursor:not-allowed}.tn-checkbox__input:disabled:checked~.tn-checkbox__checkmark{background-color:var(--tn-lines, #e0e0e0);border-color:var(--tn-lines, #e0e0e0)}.tn-checkbox__checkmark{position:relative;height:16px;width:16px;background-color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #ccc);border-radius:2px;transition:all .2s ease;flex-shrink:0}.tn-checkbox__checkmark:after{content:\"\";position:absolute;display:none;left:5px;top:2px;width:4px;height:8px;border:solid var(--tn-primary-txt, #fff);border-width:0 2px 2px 0;transform:rotate(45deg)}.tn-checkbox__text{flex:1;min-width:0}.tn-checkbox__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-checkbox__error{color:var(--tn-red, #dc2626);font-size:12px;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);margin-top:4px;display:flex;align-items:center;gap:4px}.tn-checkbox--disabled .tn-checkbox__label{cursor:not-allowed;color:var(--tn-fg2, #666);opacity:.6}.tn-checkbox--disabled .tn-checkbox__text{color:var(--tn-fg2, #666)}.tn-checkbox--error .tn-checkbox__checkmark{border-color:var(--tn-red, #dc2626)}.tn-checkbox--indeterminate .tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:focus-visible~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}@media(prefers-contrast:high){.tn-checkbox__checkmark{border-width:2px}}@media(prefers-reduced-motion:reduce){.tn-checkbox__checkmark{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }, { kind: "pipe", type: LabelTextPipe, name: "tnLabelText" }] });
6396
6465
  }
6397
6466
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCheckboxComponent, decorators: [{
6398
6467
  type: Component,
@@ -6402,7 +6471,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6402
6471
  useExisting: forwardRef(() => TnCheckboxComponent),
6403
6472
  multi: true
6404
6473
  }
6405
- ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-checkbox__label\" tnTestIdType=\"checkbox\" [for]=\"id\" [tnTestId]=\"testId()\">\n <input\n #checkboxEl\n type=\"checkbox\"\n class=\"tn-checkbox__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [indeterminate]=\"indeterminate()\"\n [attr.aria-label]=\"hasProjectedLabel() ? (label() | tnLabelText) : null\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onCheckboxChange($event)\"\n />\n <span class=\"tn-checkbox__checkmark\"></span>\n @if (!hideLabel()) {\n <span class=\"tn-checkbox__text\">\n @if (hasProjectedLabel()) {\n <ng-content select=\"[tnCheckboxLabel]\" />\n } @else {\n <span [innerHTML]=\"label() | tnLabelMarkup\"></span>\n }\n </span>\n }\n </label>\n\n @if (error()) {\n <div\n class=\"tn-checkbox__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-checkbox{display:flex;flex-direction:column;gap:4px}.tn-checkbox__label{display:flex;align-items:center;gap:8px;cursor:pointer;-webkit-user-select:none;user-select:none;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);font-size:14px;line-height:1.5;color:var(--tn-fg1, #333)}.tn-checkbox__label:hover:not(.tn-checkbox--disabled) .tn-checkbox__checkmark{border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-checkbox__input:checked~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:checked~.tn-checkbox__checkmark:after{display:block}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark:after{display:block;content:\"\";left:4px;top:8px;width:8px;height:2px;background:var(--tn-primary-txt, #fff);border-radius:1px;transform:none}.tn-checkbox__input:focus~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}.tn-checkbox__input:disabled~.tn-checkbox__checkmark{background-color:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #e0e0e0);cursor:not-allowed}.tn-checkbox__input:disabled:checked~.tn-checkbox__checkmark{background-color:var(--tn-lines, #e0e0e0);border-color:var(--tn-lines, #e0e0e0)}.tn-checkbox__checkmark{position:relative;height:16px;width:16px;background-color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #ccc);border-radius:2px;transition:all .2s ease;flex-shrink:0}.tn-checkbox__checkmark:after{content:\"\";position:absolute;display:none;left:5px;top:2px;width:4px;height:8px;border:solid var(--tn-primary-txt, #fff);border-width:0 2px 2px 0;transform:rotate(45deg)}.tn-checkbox__text{flex:1;min-width:0}.tn-checkbox__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-checkbox__error{color:var(--tn-red, #dc2626);font-size:12px;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);margin-top:4px;display:flex;align-items:center;gap:4px}.tn-checkbox--disabled .tn-checkbox__label{cursor:not-allowed;color:var(--tn-fg2, #666);opacity:.6}.tn-checkbox--disabled .tn-checkbox__text{color:var(--tn-fg2, #666)}.tn-checkbox--error .tn-checkbox__checkmark{border-color:var(--tn-red, #dc2626)}.tn-checkbox--indeterminate .tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:focus-visible~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}@media(prefers-contrast:high){.tn-checkbox__checkmark{border-width:2px}}@media(prefers-reduced-motion:reduce){.tn-checkbox__checkmark{transition:none}}\n"] }]
6474
+ ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-checkbox__label\" tnTestIdType=\"checkbox\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #checkboxEl\n type=\"checkbox\"\n class=\"tn-checkbox__input\"\n [id]=\"id\"\n [checked]=\"effectiveChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [indeterminate]=\"indeterminate()\"\n [attr.aria-label]=\"hasProjectedLabel() ? (label() | tnLabelText) : null\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onCheckboxChange($event)\"\n />\n <span class=\"tn-checkbox__checkmark\"></span>\n @if (!hideLabel()) {\n <span class=\"tn-checkbox__text\">\n @if (hasProjectedLabel()) {\n <ng-content select=\"[tnCheckboxLabel]\" />\n } @else {\n <span [innerHTML]=\"label() | tnLabelMarkup\"></span>\n }\n </span>\n }\n </label>\n\n @if (error()) {\n <div\n class=\"tn-checkbox__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-checkbox{display:flex;flex-direction:column;gap:4px}.tn-checkbox__label{display:flex;align-items:center;gap:8px;cursor:pointer;-webkit-user-select:none;user-select:none;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);font-size:14px;line-height:1.5;color:var(--tn-fg1, #333)}.tn-checkbox__label:hover:not(.tn-checkbox--disabled) .tn-checkbox__checkmark{border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-checkbox__input:checked~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:checked~.tn-checkbox__checkmark:after{display:block}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:indeterminate~.tn-checkbox__checkmark:after{display:block;content:\"\";left:4px;top:8px;width:8px;height:2px;background:var(--tn-primary-txt, #fff);border-radius:1px;transform:none}.tn-checkbox__input:focus~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}.tn-checkbox__input:disabled~.tn-checkbox__checkmark{background-color:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #e0e0e0);cursor:not-allowed}.tn-checkbox__input:disabled:checked~.tn-checkbox__checkmark{background-color:var(--tn-lines, #e0e0e0);border-color:var(--tn-lines, #e0e0e0)}.tn-checkbox__checkmark{position:relative;height:16px;width:16px;background-color:var(--tn-bg1, #fff);border:1px solid var(--tn-lines, #ccc);border-radius:2px;transition:all .2s ease;flex-shrink:0}.tn-checkbox__checkmark:after{content:\"\";position:absolute;display:none;left:5px;top:2px;width:4px;height:8px;border:solid var(--tn-primary-txt, #fff);border-width:0 2px 2px 0;transform:rotate(45deg)}.tn-checkbox__text{flex:1;min-width:0}.tn-checkbox__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-checkbox__error{color:var(--tn-red, #dc2626);font-size:12px;font-family:var(--tn-font-family-body, \"Inter\", sans-serif);margin-top:4px;display:flex;align-items:center;gap:4px}.tn-checkbox--disabled .tn-checkbox__label{cursor:not-allowed;color:var(--tn-fg2, #666);opacity:.6}.tn-checkbox--disabled .tn-checkbox__text{color:var(--tn-fg2, #666)}.tn-checkbox--error .tn-checkbox__checkmark{border-color:var(--tn-red, #dc2626)}.tn-checkbox--indeterminate .tn-checkbox__checkmark{background-color:var(--tn-primary, #0095d5);border-color:var(--tn-primary, #0095d5)}.tn-checkbox__input:focus-visible~.tn-checkbox__checkmark{outline:2px solid var(--tn-primary, #0095d5);outline-offset:2px}@media(prefers-contrast:high){.tn-checkbox__checkmark{border-width:2px}}@media(prefers-reduced-motion:reduce){.tn-checkbox__checkmark{transition:none}}\n"] }]
6406
6475
  }], propDecorators: { checkboxEl: [{ type: i0.ViewChild, args: ['checkboxEl', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hideLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], labelContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnCheckboxLabelDirective), { isSignal: true }] }] } });
6407
6476
 
6408
6477
  /**
@@ -6617,6 +6686,8 @@ class TnRadioComponent {
6617
6686
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
6618
6687
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
6619
6688
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
6689
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
6690
+ resolvedTestId = controlTestId(this.testId);
6620
6691
  error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
6621
6692
  change = output();
6622
6693
  id = `tn-radio-${Math.random().toString(36).substr(2, 9)}`;
@@ -6681,7 +6752,7 @@ class TnRadioComponent {
6681
6752
  useExisting: forwardRef(() => TnRadioComponent),
6682
6753
  multi: true
6683
6754
  }
6684
- ], viewQueries: [{ propertyName: "radioEl", first: true, predicate: ["radioEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"testId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"name()\"\n [value]=\"value()\"\n [checked]=\"checked\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-red, #dc3545)}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
6755
+ ], viewQueries: [{ propertyName: "radioEl", first: true, predicate: ["radioEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"name()\"\n [value]=\"value()\"\n [checked]=\"checked\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-red, #dc3545)}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
6685
6756
  }
6686
6757
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnRadioComponent, decorators: [{
6687
6758
  type: Component,
@@ -6691,7 +6762,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
6691
6762
  useExisting: forwardRef(() => TnRadioComponent),
6692
6763
  multi: true
6693
6764
  }
6694
- ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"testId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"name()\"\n [value]=\"value()\"\n [checked]=\"checked\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-red, #dc3545)}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"] }]
6765
+ ], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"name()\"\n [value]=\"value()\"\n [checked]=\"checked\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n aria-live=\"polite\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-red, #dc3545)}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"] }]
6695
6766
  }], propDecorators: { radioEl: [{ type: i0.ViewChild, args: ['radioEl', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
6696
6767
 
6697
6768
  /**
@@ -8643,6 +8714,8 @@ class TnSelectComponent {
8643
8714
  emptyLabel = input('--', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : []));
8644
8715
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
8645
8716
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
8717
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
8718
+ resolvedTestId = controlTestId(this.testId);
8646
8719
  multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
8647
8720
  /**
8648
8721
  * Optional extractor for the per-option test-id discriminator. Defaults to
@@ -8691,11 +8764,12 @@ class TnSelectComponent {
8691
8764
  instanceId = `i${++TnSelectComponent.instanceCounter}`;
8692
8765
  /**
8693
8766
  * Id namespace used by all DOM ids the template emits (dropdown panel,
8694
- * option rows, group labels). Prefers `testId` when set so tests can target
8695
- * specific instances; otherwise falls back to a per-instance counter so two
8767
+ * option rows, group labels). Prefers the resolved test-id base (explicit
8768
+ * `testId`, else the bound control name) so tests can target specific
8769
+ * instances; otherwise falls back to a per-instance counter so two
8696
8770
  * `<tn-select>`s on the same page never collide on `aria-controls`/group ids.
8697
8771
  */
8698
- idNamespace = computed(() => composeTestId(undefined, this.testId()) || this.instanceId, ...(ngDevMode ? [{ debugName: "idNamespace" }] : []));
8772
+ idNamespace = computed(() => composeTestId(undefined, this.resolvedTestId()) || this.instanceId, ...(ngDevMode ? [{ debugName: "idNamespace" }] : []));
8699
8773
  // Computed disabled state (combines input and form state)
8700
8774
  isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
8701
8775
  /**
@@ -8763,18 +8837,18 @@ class TnSelectComponent {
8763
8837
  }
8764
8838
  /**
8765
8839
  * Test-id segments for an option row, consumed by `[tnTestId]` with
8766
- * `tnTestIdType="option"`. The select's `testId` scopes each option so ids
8767
- * stay unique across selects: base `quick-filters` + option value `ssd`
8768
- * `option-quick-filters-ssd`; with no base → `option-ssd`. The discriminator
8769
- * comes from `optionTestIdKey` when provided, else the option's primitive
8770
- * `value`, else its `label`.
8840
+ * `tnTestIdType="option"`. The select's resolved base (explicit `testId`, else
8841
+ * the bound control name) scopes each option so ids stay unique across selects:
8842
+ * base `quick-filters` + option value `ssd` → `option-quick-filters-ssd`; with
8843
+ * no base → `option-ssd`. The discriminator comes from `optionTestIdKey` when
8844
+ * provided, else the option's primitive `value`, else its `label`.
8771
8845
  */
8772
8846
  optionTestIdParts(option) {
8773
8847
  // The synthetic empty option gets a fixed `empty` discriminator — its
8774
8848
  // label (`--` by default) would be stripped entirely by kebab
8775
8849
  // normalization, leaving a non-unique id.
8776
8850
  if (this.isEmptyOption(option)) {
8777
- return scopeTestId(this.testId(), 'empty');
8851
+ return scopeTestId(this.resolvedTestId(), 'empty');
8778
8852
  }
8779
8853
  const extractor = this.optionTestIdKey();
8780
8854
  let key;
@@ -8787,7 +8861,7 @@ class TnSelectComponent {
8787
8861
  else {
8788
8862
  key = option.label;
8789
8863
  }
8790
- return scopeTestId(this.testId(), key);
8864
+ return scopeTestId(this.resolvedTestId(), key);
8791
8865
  }
8792
8866
  onChange = (_value) => { };
8793
8867
  onTouched = () => { };
@@ -9193,7 +9267,7 @@ class TnSelectComponent {
9193
9267
  useExisting: forwardRef(() => TnSelectComponent),
9194
9268
  multi: true
9195
9269
  }
9196
- ], 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\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tnTestIdType=\"select\"\n [tnTestId]=\"testId()\"\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 (preceded by the synthetic empty option when allowEmpty is set) -->\n @for (option of displayOptions(); 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 tnTestIdType=\"option\"\n [class.tn-select-empty-option]=\"isEmptyOption(option)\"\n [tnTestId]=\"optionTestIdParts(option)\"\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 || displayOptions().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 tnTestIdType=\"option\"\n [tnTestId]=\"optionTestIdParts(option)\"\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-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{--tn-select-dropdown-max-height: 350px;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)}.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-option.tn-select-empty-option{color:var(--tn-fg2, #6c757d)}.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", "tnTestIdType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9270
+ ], 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\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tnTestIdType=\"select\"\n [tnTestId]=\"resolvedTestId()\"\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 (preceded by the synthetic empty option when allowEmpty is set) -->\n @for (option of displayOptions(); 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 tnTestIdType=\"option\"\n [class.tn-select-empty-option]=\"isEmptyOption(option)\"\n [tnTestId]=\"optionTestIdParts(option)\"\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 || displayOptions().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 tnTestIdType=\"option\"\n [tnTestId]=\"optionTestIdParts(option)\"\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-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{--tn-select-dropdown-max-height: 350px;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)}.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-option.tn-select-empty-option{color:var(--tn-fg2, #6c757d)}.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", "tnTestIdType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9197
9271
  }
9198
9272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
9199
9273
  type: Component,
@@ -9203,7 +9277,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
9203
9277
  useExisting: forwardRef(() => TnSelectComponent),
9204
9278
  multi: true
9205
9279
  }
9206
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-select-container\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tnTestIdType=\"select\"\n [tnTestId]=\"testId()\"\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 (preceded by the synthetic empty option when allowEmpty is set) -->\n @for (option of displayOptions(); 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 tnTestIdType=\"option\"\n [class.tn-select-empty-option]=\"isEmptyOption(option)\"\n [tnTestId]=\"optionTestIdParts(option)\"\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 || displayOptions().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 tnTestIdType=\"option\"\n [tnTestId]=\"optionTestIdParts(option)\"\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-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{--tn-select-dropdown-max-height: 350px;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)}.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-option.tn-select-empty-option{color:var(--tn-fg2, #6c757d)}.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"] }]
9280
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-select-container\">\n <!-- Select Trigger -->\n <div\n #triggerEl\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tnTestIdType=\"select\"\n [tnTestId]=\"resolvedTestId()\"\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 (preceded by the synthetic empty option when allowEmpty is set) -->\n @for (option of displayOptions(); 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 tnTestIdType=\"option\"\n [class.tn-select-empty-option]=\"isEmptyOption(option)\"\n [tnTestId]=\"optionTestIdParts(option)\"\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 || displayOptions().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 tnTestIdType=\"option\"\n [tnTestId]=\"optionTestIdParts(option)\"\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-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{--tn-select-dropdown-max-height: 350px;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)}.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-option.tn-select-empty-option{color:var(--tn-fg2, #6c757d)}.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"] }]
9207
9281
  }], 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 }] }], allowEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowEmpty", required: false }] }], emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", 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 }] }], optionTestIdKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTestIdKey", 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 }] }] } });
9208
9282
 
9209
9283
  /**
@@ -17601,5 +17675,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
17601
17675
  * Generated bundle index. Do not edit.
17602
17676
  */
17603
17677
 
17604
- export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnFormSectionComponent, TnFormSectionHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, formatSize, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, parseLabelMarkup, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
17678
+ export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnFormSectionComponent, TnFormSectionHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, controlTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, formatSize, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, parseLabelMarkup, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
17605
17679
  //# sourceMappingURL=truenas-ui-components.mjs.map