@truenas/ui-components 0.1.77 → 0.2.1
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.
|
@@ -3,7 +3,7 @@ import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/port
|
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { InjectionToken, inject, Renderer2, ElementRef, input, effect, Directive, ViewContainerRef, output, viewChild, computed, signal, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, isDevMode, ViewEncapsulation, contentChildren, HostListener, contentChild, ChangeDetectorRef, DestroyRef, TemplateRef, isSignal, untracked, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
|
|
5
5
|
import * as i1$3 from '@angular/forms';
|
|
6
|
-
import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
|
|
6
|
+
import { NG_VALUE_ACCESSOR, FormsModule, NgControl, 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';
|
|
@@ -2628,6 +2628,36 @@ class TnInputComponent {
|
|
|
2628
2628
|
* the a11y tree.
|
|
2629
2629
|
*/
|
|
2630
2630
|
ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
2631
|
+
/**
|
|
2632
|
+
* Native `autocomplete` hint rendered on the input/textarea. Pass the standard
|
|
2633
|
+
* autofill tokens (`'username'`, `'current-password'`, `'new-password'`,
|
|
2634
|
+
* `'one-time-code'`, ...) so browsers and password managers can identify and
|
|
2635
|
+
* fill the field.
|
|
2636
|
+
*/
|
|
2637
|
+
autocomplete = input(undefined, ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
|
|
2638
|
+
/**
|
|
2639
|
+
* Native `name` attribute rendered on the input/textarea. Browsers and password
|
|
2640
|
+
* managers use it to identify the field; typically mirrors the form control name.
|
|
2641
|
+
*/
|
|
2642
|
+
name = input(undefined, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
2643
|
+
/**
|
|
2644
|
+
* Renders the native `readonly` attribute: the value is visible, focusable and
|
|
2645
|
+
* selectable but not editable. Unlike `disabled`, a readonly field stays in the
|
|
2646
|
+
* tab order and its form control stays enabled.
|
|
2647
|
+
*/
|
|
2648
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
2649
|
+
/**
|
|
2650
|
+
* Renders the native `required` attribute so browsers and assistive technology
|
|
2651
|
+
* expose the field as required. Validation itself stays with the consumer's form
|
|
2652
|
+
* validators (e.g. `Validators.required`); forms that fully own validation UX
|
|
2653
|
+
* should also set `novalidate` to suppress native submit blocking.
|
|
2654
|
+
*
|
|
2655
|
+
* Note: this drives only the native/a11y semantics. When wrapped in a
|
|
2656
|
+
* `tn-form-field`, the visual `*` indicator is inferred automatically from the
|
|
2657
|
+
* control's `Validators.required`; only validator-less setups need the form
|
|
2658
|
+
* field's own `required` input alongside this one.
|
|
2659
|
+
*/
|
|
2660
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
2631
2661
|
/**
|
|
2632
2662
|
* Integer/decimal switch — only meaningful when `inputType` is `InputType.Number`.
|
|
2633
2663
|
*
|
|
@@ -2663,6 +2693,13 @@ class TnInputComponent {
|
|
|
2663
2693
|
suffixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIcon" }] : []));
|
|
2664
2694
|
suffixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconLibrary" }] : []));
|
|
2665
2695
|
suffixIconAriaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconAriaLabel" }] : []));
|
|
2696
|
+
/**
|
|
2697
|
+
* Semantic test-id base for the suffix-action button. The library prepends the
|
|
2698
|
+
* element type (`button`) and renders the result under whichever attribute name
|
|
2699
|
+
* is configured via `TN_TEST_ATTR` (default `data-testid`) — e.g.
|
|
2700
|
+
* `suffixActionTestId="toggle-password"` → `button-toggle-password`.
|
|
2701
|
+
*/
|
|
2702
|
+
suffixActionTestId = input(undefined, ...(ngDevMode ? [{ debugName: "suffixActionTestId" }] : []));
|
|
2666
2703
|
onSuffixAction = output();
|
|
2667
2704
|
hasPrefixIcon = computed(() => !!this.prefixIcon(), ...(ngDevMode ? [{ debugName: "hasPrefixIcon" }] : []));
|
|
2668
2705
|
hasSuffixIcon = computed(() => !!this.suffixIcon(), ...(ngDevMode ? [{ debugName: "hasSuffixIcon" }] : []));
|
|
@@ -2818,13 +2855,13 @@ class TnInputComponent {
|
|
|
2818
2855
|
return Number.isNaN(parsed) ? null : parsed;
|
|
2819
2856
|
}
|
|
2820
2857
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2821
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnInputComponent, isStandalone: true, selector: "tn-input", inputs: { inputType: { classPropertyName: "inputType", publicName: "inputType", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, allowDecimals: { classPropertyName: "allowDecimals", publicName: "allowDecimals", isSignal: true, isRequired: false, transformFunction: null }, sizeStandard: { classPropertyName: "sizeStandard", publicName: "sizeStandard", isSignal: true, isRequired: false, transformFunction: null }, sizeDefaultUnit: { classPropertyName: "sizeDefaultUnit", publicName: "sizeDefaultUnit", isSignal: true, isRequired: false, transformFunction: null }, sizeRound: { classPropertyName: "sizeRound", publicName: "sizeRound", isSignal: true, isRequired: false, transformFunction: null }, prefixIcon: { classPropertyName: "prefixIcon", publicName: "prefixIcon", isSignal: true, isRequired: false, transformFunction: null }, prefixIconLibrary: { classPropertyName: "prefixIconLibrary", publicName: "prefixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIcon: { classPropertyName: "suffixIcon", publicName: "suffixIcon", isSignal: true, isRequired: false, transformFunction: null }, suffixIconLibrary: { classPropertyName: "suffixIconLibrary", publicName: "suffixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIconAriaLabel: { classPropertyName: "suffixIconAriaLabel", publicName: "suffixIconAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSuffixAction: "onSuffixAction" }, providers: [
|
|
2858
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnInputComponent, isStandalone: true, selector: "tn-input", inputs: { inputType: { classPropertyName: "inputType", publicName: "inputType", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, allowDecimals: { classPropertyName: "allowDecimals", publicName: "allowDecimals", isSignal: true, isRequired: false, transformFunction: null }, sizeStandard: { classPropertyName: "sizeStandard", publicName: "sizeStandard", isSignal: true, isRequired: false, transformFunction: null }, sizeDefaultUnit: { classPropertyName: "sizeDefaultUnit", publicName: "sizeDefaultUnit", isSignal: true, isRequired: false, transformFunction: null }, sizeRound: { classPropertyName: "sizeRound", publicName: "sizeRound", isSignal: true, isRequired: false, transformFunction: null }, prefixIcon: { classPropertyName: "prefixIcon", publicName: "prefixIcon", isSignal: true, isRequired: false, transformFunction: null }, prefixIconLibrary: { classPropertyName: "prefixIconLibrary", publicName: "prefixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIcon: { classPropertyName: "suffixIcon", publicName: "suffixIcon", isSignal: true, isRequired: false, transformFunction: null }, suffixIconLibrary: { classPropertyName: "suffixIconLibrary", publicName: "suffixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIconAriaLabel: { classPropertyName: "suffixIconAriaLabel", publicName: "suffixIconAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, suffixActionTestId: { classPropertyName: "suffixActionTestId", publicName: "suffixActionTestId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSuffixAction: "onSuffixAction" }, providers: [
|
|
2822
2859
|
{
|
|
2823
2860
|
provide: NG_VALUE_ACCESSOR,
|
|
2824
2861
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2825
2862
|
multi: true
|
|
2826
2863
|
}
|
|
2827
|
-
], 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()\"\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 [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\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 [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\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 [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</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{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){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action: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{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"] }] });
|
|
2864
|
+
], 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()\"\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</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{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){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action: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{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"] }] });
|
|
2828
2865
|
}
|
|
2829
2866
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, decorators: [{
|
|
2830
2867
|
type: Component,
|
|
@@ -2834,8 +2871,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2834
2871
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2835
2872
|
multi: true
|
|
2836
2873
|
}
|
|
2837
|
-
], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon()\"\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 [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\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 [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\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 [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</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{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){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action: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{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
|
|
2838
|
-
}], 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 }] }], 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 }] }], 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 }] }], onSuffixAction: [{ type: i0.Output, args: ["onSuffixAction"] }] } });
|
|
2874
|
+
], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon()\"\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</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{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){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action: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{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
|
|
2875
|
+
}], 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 }] }], 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"] }] } });
|
|
2839
2876
|
|
|
2840
2877
|
/**
|
|
2841
2878
|
* Harness for interacting with tn-icon in tests.
|
|
@@ -3106,6 +3143,9 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3106
3143
|
return new HarnessPredicate(TnInputHarness, options)
|
|
3107
3144
|
.addOption('placeholder', options.placeholder, async (harness, placeholder) => {
|
|
3108
3145
|
return (await harness.getPlaceholder()) === placeholder;
|
|
3146
|
+
})
|
|
3147
|
+
.addOption('name', options.name, async (harness, name) => {
|
|
3148
|
+
return (await harness.getName()) === name;
|
|
3109
3149
|
});
|
|
3110
3150
|
}
|
|
3111
3151
|
/**
|
|
@@ -3206,6 +3246,58 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3206
3246
|
const input = await this.inputEl();
|
|
3207
3247
|
return input.getAttribute('aria-label');
|
|
3208
3248
|
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Gets the native `name` attribute (set via the `name` input).
|
|
3251
|
+
*
|
|
3252
|
+
* @returns Promise resolving to the name string, or null if unset.
|
|
3253
|
+
*/
|
|
3254
|
+
async getName() {
|
|
3255
|
+
if (await this.isMultiline()) {
|
|
3256
|
+
const textarea = await this.textareaEl();
|
|
3257
|
+
return textarea.getAttribute('name');
|
|
3258
|
+
}
|
|
3259
|
+
const input = await this.inputEl();
|
|
3260
|
+
return input.getAttribute('name');
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Gets the native `autocomplete` attribute (set via the `autocomplete` input).
|
|
3264
|
+
*
|
|
3265
|
+
* @returns Promise resolving to the autocomplete token, or null if unset.
|
|
3266
|
+
*/
|
|
3267
|
+
async getAutocomplete() {
|
|
3268
|
+
if (await this.isMultiline()) {
|
|
3269
|
+
const textarea = await this.textareaEl();
|
|
3270
|
+
return textarea.getAttribute('autocomplete');
|
|
3271
|
+
}
|
|
3272
|
+
const input = await this.inputEl();
|
|
3273
|
+
return input.getAttribute('autocomplete');
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Checks whether the input is readonly (set via the `readonly` input).
|
|
3277
|
+
*
|
|
3278
|
+
* @returns Promise resolving to true if the input is readonly.
|
|
3279
|
+
*/
|
|
3280
|
+
async isReadonly() {
|
|
3281
|
+
if (await this.isMultiline()) {
|
|
3282
|
+
const textarea = await this.textareaEl();
|
|
3283
|
+
return (await textarea.getProperty('readOnly')) ?? false;
|
|
3284
|
+
}
|
|
3285
|
+
const input = await this.inputEl();
|
|
3286
|
+
return (await input.getProperty('readOnly')) ?? false;
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Checks whether the input is marked required (set via the `required` input).
|
|
3290
|
+
*
|
|
3291
|
+
* @returns Promise resolving to true if the input is required.
|
|
3292
|
+
*/
|
|
3293
|
+
async isRequired() {
|
|
3294
|
+
if (await this.isMultiline()) {
|
|
3295
|
+
const textarea = await this.textareaEl();
|
|
3296
|
+
return (await textarea.getProperty('required')) ?? false;
|
|
3297
|
+
}
|
|
3298
|
+
const input = await this.inputEl();
|
|
3299
|
+
return (await input.getProperty('required')) ?? false;
|
|
3300
|
+
}
|
|
3209
3301
|
/**
|
|
3210
3302
|
* Gets the placeholder text.
|
|
3211
3303
|
*
|
|
@@ -6196,6 +6288,17 @@ function activeErrorKey(errors) {
|
|
|
6196
6288
|
class TnFormFieldComponent {
|
|
6197
6289
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
6198
6290
|
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
6291
|
+
/**
|
|
6292
|
+
* Forces the visual `*` required indicator next to the label. Usually
|
|
6293
|
+
* unnecessary: the indicator is inferred automatically when the projected
|
|
6294
|
+
* control carries `Validators.required`. Set this only when inference can't
|
|
6295
|
+
* see the requirement — e.g. a validator wrapped in `Validators.compose(...)`
|
|
6296
|
+
* or a custom validator that emits a `required`-style error.
|
|
6297
|
+
*
|
|
6298
|
+
* The indicator is purely visual — for native/a11y semantics pair it with the
|
|
6299
|
+
* projected control's own `required` input (e.g. `tn-input`'s, which renders
|
|
6300
|
+
* the native attribute).
|
|
6301
|
+
*/
|
|
6199
6302
|
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
6200
6303
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
6201
6304
|
subscriptSizing = input('dynamic', ...(ngDevMode ? [{ debugName: "subscriptSizing" }] : []));
|
|
@@ -6227,7 +6330,15 @@ class TnFormFieldComponent {
|
|
|
6227
6330
|
invalid: false,
|
|
6228
6331
|
interacted: false,
|
|
6229
6332
|
errors: null,
|
|
6333
|
+
required: false,
|
|
6230
6334
|
}, ...(ngDevMode ? [{ debugName: "controlState" }] : []));
|
|
6335
|
+
/**
|
|
6336
|
+
* Whether the required indicator renders: forced via the `required` input, or
|
|
6337
|
+
* inferred from the projected control's validators (mirrors Angular Material's
|
|
6338
|
+
* `hasValidator(Validators.required)` approach — reference equality, so composed
|
|
6339
|
+
* or custom required-like validators need the explicit input).
|
|
6340
|
+
*/
|
|
6341
|
+
showRequired = computed(() => this.required() || this.controlState().required, ...(ngDevMode ? [{ debugName: "showRequired" }] : []));
|
|
6231
6342
|
hasError = computed(() => {
|
|
6232
6343
|
const state = this.controlState();
|
|
6233
6344
|
return state.invalid && state.interacted;
|
|
@@ -6260,6 +6371,7 @@ class TnFormFieldComponent {
|
|
|
6260
6371
|
invalid: !!control.invalid,
|
|
6261
6372
|
interacted: !!(control.dirty || control.touched),
|
|
6262
6373
|
errors: control.errors ?? null,
|
|
6374
|
+
required: !!control.control?.hasValidator(Validators.required),
|
|
6263
6375
|
});
|
|
6264
6376
|
}
|
|
6265
6377
|
}
|
|
@@ -6331,11 +6443,11 @@ class TnFormFieldComponent {
|
|
|
6331
6443
|
return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
|
|
6332
6444
|
}, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
|
|
6333
6445
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6334
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" tnTestIdType=\"form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label() || tooltip()) {\n <div class=\"tn-form-field-label-row\">\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"
|
|
6446
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" tnTestIdType=\"form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label() || tooltip()) {\n <div class=\"tn-form-field-label-row\">\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"showRequired()\">\n {{ label() }}\n @if (showRequired()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n @if (tooltip()) {\n <button\n type=\"button\"\n class=\"tn-form-field-tooltip\"\n [attr.aria-label]=\"tooltip()\"\n [tnTooltip]=\"tooltip()\"\n [tnTooltipPosition]=\"tooltipPosition()\">\n <tn-icon name=\"help-circle\" library=\"mdi\" size=\"sm\" />\n </button>\n }\n </div>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label-row{display:flex;align-items:center;gap:.375rem;margin-bottom:.5rem}.tn-form-field-label{display:block;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-tooltip{display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;background:transparent;color:var(--tn-fg2, #6c757d);cursor:help;line-height:0}.tn-form-field-tooltip:hover,.tn-form-field-tooltip:focus-visible{color:var(--tn-primary, #007bff)}.tn-form-field-tooltip:focus-visible{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;border-radius:50%}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"], dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }, { kind: "directive", type: TnTooltipDirective, selector: "[tnTooltip]", inputs: ["tnTooltip", "tnTooltipPosition", "tnTooltipDisabled", "tnTooltipShowDelay", "tnTooltipHideDelay", "tnTooltipClass"] }] });
|
|
6335
6447
|
}
|
|
6336
6448
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
|
|
6337
6449
|
type: Component,
|
|
6338
|
-
args: [{ selector: 'tn-form-field', standalone: true, imports: [TnTestIdDirective, TnIconComponent, TnTooltipDirective], template: "<div class=\"tn-form-field\" tnTestIdType=\"form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label() || tooltip()) {\n <div class=\"tn-form-field-label-row\">\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"
|
|
6450
|
+
args: [{ selector: 'tn-form-field', standalone: true, imports: [TnTestIdDirective, TnIconComponent, TnTooltipDirective], template: "<div class=\"tn-form-field\" tnTestIdType=\"form-field\" [tnTestId]=\"testId()\">\n <!-- Label -->\n @if (label() || tooltip()) {\n <div class=\"tn-form-field-label-row\">\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"showRequired()\">\n {{ label() }}\n @if (showRequired()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n @if (tooltip()) {\n <button\n type=\"button\"\n class=\"tn-form-field-tooltip\"\n [attr.aria-label]=\"tooltip()\"\n [tnTooltip]=\"tooltip()\"\n [tnTooltipPosition]=\"tooltipPosition()\">\n <tn-icon name=\"help-circle\" library=\"mdi\" size=\"sm\" />\n </button>\n }\n </div>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label-row{display:flex;align-items:center;gap:.375rem;margin-bottom:.5rem}.tn-form-field-label{display:block;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-tooltip{display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;background:transparent;color:var(--tn-fg2, #6c757d);cursor:help;line-height:0}.tn-form-field-tooltip:hover,.tn-form-field-tooltip:focus-visible{color:var(--tn-primary, #007bff)}.tn-form-field-tooltip:focus-visible{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px;border-radius:50%}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"] }]
|
|
6339
6451
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], subscriptSizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscriptSizing", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { isSignal: true }] }] } });
|
|
6340
6452
|
|
|
6341
6453
|
/**
|
|
@@ -6577,6 +6689,17 @@ class TnSelectComponent {
|
|
|
6577
6689
|
* with i18n requirements can pass a translated string.
|
|
6578
6690
|
*/
|
|
6579
6691
|
noOptionsLabel = input('No options available', ...(ngDevMode ? [{ debugName: "noOptionsLabel" }] : []));
|
|
6692
|
+
/**
|
|
6693
|
+
* When `true` (single-select mode only), prepends a synthetic "empty"
|
|
6694
|
+
* option to the dropdown so users can unset a chosen value: picking it
|
|
6695
|
+
* resets the selection to `null`, shows the placeholder again, and emits
|
|
6696
|
+
* `null` via `selectionChange` (and to any bound form control). Mirrors
|
|
6697
|
+
* webui ix-select's `--` option. Ignored when `multiple` is set — there,
|
|
6698
|
+
* values are cleared by toggling them off individually.
|
|
6699
|
+
*/
|
|
6700
|
+
allowEmpty = input(false, ...(ngDevMode ? [{ debugName: "allowEmpty" }] : []));
|
|
6701
|
+
/** Label of the empty option rendered when `allowEmpty` is set. */
|
|
6702
|
+
emptyLabel = input('--', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : []));
|
|
6580
6703
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
6581
6704
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
6582
6705
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
@@ -6606,6 +6729,10 @@ class TnSelectComponent {
|
|
|
6606
6729
|
* ```
|
|
6607
6730
|
*/
|
|
6608
6731
|
compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
|
|
6732
|
+
/**
|
|
6733
|
+
* Emits the picked value on each selection in single mode. Emits `null`
|
|
6734
|
+
* when the user picks the `allowEmpty` empty option to clear the field.
|
|
6735
|
+
*/
|
|
6609
6736
|
selectionChange = output();
|
|
6610
6737
|
/** Emits the full array of selected values after each toggle in multiple mode. */
|
|
6611
6738
|
multiSelectionChange = output();
|
|
@@ -6630,6 +6757,27 @@ class TnSelectComponent {
|
|
|
6630
6757
|
idNamespace = computed(() => composeTestId(undefined, this.testId()) || this.instanceId, ...(ngDevMode ? [{ debugName: "idNamespace" }] : []));
|
|
6631
6758
|
// Computed disabled state (combines input and form state)
|
|
6632
6759
|
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
6760
|
+
/**
|
|
6761
|
+
* The synthetic clear-selection option (`allowEmpty`, single mode only).
|
|
6762
|
+
* Its value is `null` cast to `T` so it flows through the same selection
|
|
6763
|
+
* path as real options — `selectedValue`/`writeValue` already model "no
|
|
6764
|
+
* selection" as `null`, so picking it clears the field for free.
|
|
6765
|
+
*/
|
|
6766
|
+
emptyOption = computed(() => {
|
|
6767
|
+
if (!this.allowEmpty() || this.multiple()) {
|
|
6768
|
+
return null;
|
|
6769
|
+
}
|
|
6770
|
+
return { value: null, label: this.emptyLabel() };
|
|
6771
|
+
}, ...(ngDevMode ? [{ debugName: "emptyOption" }] : []));
|
|
6772
|
+
/** Ungrouped options as rendered: the empty option (when enabled) first. */
|
|
6773
|
+
displayOptions = computed(() => {
|
|
6774
|
+
const empty = this.emptyOption();
|
|
6775
|
+
return empty ? [empty, ...this.options()] : this.options();
|
|
6776
|
+
}, ...(ngDevMode ? [{ debugName: "displayOptions" }] : []));
|
|
6777
|
+
/** Whether `option` is the synthetic `allowEmpty` clear option. */
|
|
6778
|
+
isEmptyOption(option) {
|
|
6779
|
+
return option === this.emptyOption();
|
|
6780
|
+
}
|
|
6633
6781
|
/**
|
|
6634
6782
|
* Selectable, non-disabled options in display order (regular options first,
|
|
6635
6783
|
* then groups). Used by keyboard navigation so we can skip disabled
|
|
@@ -6639,7 +6787,7 @@ class TnSelectComponent {
|
|
|
6639
6787
|
const result = [];
|
|
6640
6788
|
const baseId = `tn-select-opt-${this.idNamespace()}`;
|
|
6641
6789
|
let i = 0;
|
|
6642
|
-
for (const opt of this.
|
|
6790
|
+
for (const opt of this.displayOptions()) {
|
|
6643
6791
|
if (!opt.disabled) {
|
|
6644
6792
|
result.push({ option: opt, id: `${baseId}-${i}` });
|
|
6645
6793
|
}
|
|
@@ -6681,6 +6829,12 @@ class TnSelectComponent {
|
|
|
6681
6829
|
* `value`, else its `label`.
|
|
6682
6830
|
*/
|
|
6683
6831
|
optionTestIdParts(option) {
|
|
6832
|
+
// The synthetic empty option gets a fixed `empty` discriminator — its
|
|
6833
|
+
// label (`--` by default) would be stripped entirely by kebab
|
|
6834
|
+
// normalization, leaving a non-unique id.
|
|
6835
|
+
if (this.isEmptyOption(option)) {
|
|
6836
|
+
return scopeTestId(this.testId(), 'empty');
|
|
6837
|
+
}
|
|
6684
6838
|
const extractor = this.optionTestIdKey();
|
|
6685
6839
|
let key;
|
|
6686
6840
|
if (extractor) {
|
|
@@ -6753,6 +6907,11 @@ class TnSelectComponent {
|
|
|
6753
6907
|
const idx = this.navigableOptions().findIndex((x) => this.compareValues(x.option.value, selected));
|
|
6754
6908
|
this.focusedIndex.set(idx);
|
|
6755
6909
|
}
|
|
6910
|
+
else if (this.emptyOption()) {
|
|
6911
|
+
// A cleared value means the empty option (always first) is the current
|
|
6912
|
+
// selection — seed keyboard focus there.
|
|
6913
|
+
this.focusedIndex.set(0);
|
|
6914
|
+
}
|
|
6756
6915
|
else {
|
|
6757
6916
|
this.focusedIndex.set(-1);
|
|
6758
6917
|
}
|
|
@@ -7087,13 +7246,13 @@ class TnSelectComponent {
|
|
|
7087
7246
|
});
|
|
7088
7247
|
}
|
|
7089
7248
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7090
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, noOptionsLabel: { classPropertyName: "noOptionsLabel", publicName: "noOptionsLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, optionTestIdKey: { classPropertyName: "optionTestIdKey", publicName: "optionTestIdKey", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", multiSelectionChange: "multiSelectionChange" }, providers: [
|
|
7249
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, noOptionsLabel: { classPropertyName: "noOptionsLabel", publicName: "noOptionsLabel", isSignal: true, isRequired: false, transformFunction: null }, allowEmpty: { classPropertyName: "allowEmpty", publicName: "allowEmpty", isSignal: true, isRequired: false, transformFunction: null }, emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, optionTestIdKey: { classPropertyName: "optionTestIdKey", publicName: "optionTestIdKey", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", multiSelectionChange: "multiSelectionChange" }, providers: [
|
|
7091
7250
|
{
|
|
7092
7251
|
provide: NG_VALUE_ACCESSOR,
|
|
7093
7252
|
useExisting: forwardRef(() => TnSelectComponent),
|
|
7094
7253
|
multi: true
|
|
7095
7254
|
}
|
|
7096
|
-
], 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 -->\n @for (option of
|
|
7255
|
+
], 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-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-error, #dc3545) 25%,transparent)}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg2, #e8f4fd);box-shadow:inset 2px 0 0 var(--tn-primary, #007bff)}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-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 });
|
|
7097
7256
|
}
|
|
7098
7257
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
|
|
7099
7258
|
type: Component,
|
|
@@ -7103,8 +7262,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
7103
7262
|
useExisting: forwardRef(() => TnSelectComponent),
|
|
7104
7263
|
multi: true
|
|
7105
7264
|
}
|
|
7106
|
-
], 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 -->\n @for (option of
|
|
7107
|
-
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], noOptionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noOptionsLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], 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 }] }] } });
|
|
7265
|
+
], 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-dropdown-max-height: 200px}.tn-select-label{display:block;margin-bottom:.5rem;font-size:1rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-error, #dc3545) 25%,transparent)}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:var(--tn-select-dropdown-max-height);overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:var(--tn-select-dropdown-max-height)}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg1, #212529)}.tn-select-option:hover:not(.disabled):not(.focused){background-color:var(--tn-alt-bg2, #f8f9fa)}.tn-select-option.focused{background-color:var(--tn-alt-bg2, #e8f4fd);box-shadow:inset 2px 0 0 var(--tn-primary, #007bff)}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-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"] }]
|
|
7266
|
+
}], 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 }] }] } });
|
|
7108
7267
|
|
|
7109
7268
|
/**
|
|
7110
7269
|
* Harness for interacting with tn-select in tests.
|
|
@@ -7275,6 +7434,37 @@ class TnSelectHarness extends ComponentHarness {
|
|
|
7275
7434
|
}
|
|
7276
7435
|
throw new Error(`Could not find option matching "${filter}"`);
|
|
7277
7436
|
}
|
|
7437
|
+
/**
|
|
7438
|
+
* Clears the selection by picking the empty option. Only available when
|
|
7439
|
+
* the select has `allowEmpty` set (single mode); throws otherwise.
|
|
7440
|
+
* Opens the dropdown if needed.
|
|
7441
|
+
*
|
|
7442
|
+
* @returns Promise that resolves when the selection has been cleared.
|
|
7443
|
+
*
|
|
7444
|
+
* @example
|
|
7445
|
+
* ```typescript
|
|
7446
|
+
* const select = await loader.getHarness(TnSelectHarness);
|
|
7447
|
+
* await select.selectOption('Banana');
|
|
7448
|
+
* await select.clear();
|
|
7449
|
+
* expect(await select.getDisplayText()).toBe('Select a fruit'); // placeholder
|
|
7450
|
+
* ```
|
|
7451
|
+
*/
|
|
7452
|
+
async clear() {
|
|
7453
|
+
// Options only render into the overlay once the dropdown is open, so the
|
|
7454
|
+
// empty option can't be checked for from the closed state.
|
|
7455
|
+
await this.open();
|
|
7456
|
+
// Dropdown panel is rendered in a CDK overlay (outside the host element),
|
|
7457
|
+
// so we search the document root rather than the harness-local subtree.
|
|
7458
|
+
const emptyOption = await this.documentRootLocatorFactory()
|
|
7459
|
+
.locatorForOptional('.tn-select-empty-option')();
|
|
7460
|
+
if (!emptyOption) {
|
|
7461
|
+
// Close again so the misuse error doesn't leave the dropdown open as a
|
|
7462
|
+
// side effect for tests that catch it and keep asserting.
|
|
7463
|
+
await this.close();
|
|
7464
|
+
throw new Error('Select has no empty option — set `allowEmpty` to make it clearable.');
|
|
7465
|
+
}
|
|
7466
|
+
await emptyOption.click();
|
|
7467
|
+
}
|
|
7278
7468
|
/**
|
|
7279
7469
|
* Gets the labels of all available options. Opens the dropdown if needed.
|
|
7280
7470
|
*
|
|
@@ -9080,7 +9270,7 @@ class TnTablePagerComponent {
|
|
|
9080
9270
|
provider.setPagination(pagination);
|
|
9081
9271
|
}
|
|
9082
9272
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9083
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTablePagerComponent, isStandalone: true, selector: "tn-table-pager", inputs: { testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, dataProvider: { classPropertyName: "dataProvider", publicName: "dataProvider", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, tablePaginationLabel: { classPropertyName: "tablePaginationLabel", publicName: "tablePaginationLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "resolvedTablePaginationLabel()" }, classAttribute: "tn-table-pager" }, ngImport: i0, template: "<div class=\"tn-table-pager__page-size\">\n <span class=\"tn-table-pager__label\">{{ resolvedItemsPerPageLabel() }}:</span>\n <tn-select\n class=\"tn-table-pager__size-select\"\n [testId]=\"childTestId('page-size')\"\n [options]=\"pageSizeSelectOptions()\"\n [ariaLabel]=\"resolvedItemsPerPageLabel()\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\" />\n</div>\n\n<span class=\"tn-table-pager__range\">\n @if (effectiveTotalItems() === 0) {\n 0 {{ resolvedOfLabel() }} 0\n } @else if (lastItemOnPage() > firstItemOnPage()) {\n {{ firstItemOnPage() }} \u2013 {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n } @else {\n {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n }\n</span>\n\n<div class=\"tn-table-pager__buttons\">\n <tn-icon-button\n name=\"page-first\"\n library=\"mdi\"\n [testId]=\"childTestId('first-page')\"\n [ariaLabel]=\"resolvedFirstPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"goToPage(1)\" />\n <tn-icon-button\n name=\"chevron-left\"\n library=\"mdi\"\n [testId]=\"childTestId('previous-page')\"\n [ariaLabel]=\"resolvedPreviousPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"previousPage()\" />\n <tn-icon-button\n name=\"chevron-right\"\n library=\"mdi\"\n [testId]=\"childTestId('next-page')\"\n [ariaLabel]=\"resolvedNextPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"nextPage()\" />\n <tn-icon-button\n name=\"page-last\"\n library=\"mdi\"\n [testId]=\"childTestId('last-page')\"\n [ariaLabel]=\"resolvedLastPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"goToPage(totalPages())\" />\n</div>\n", styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:16px;padding:10px;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);color:var(--tn-fg2)}.tn-table-pager__page-size{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.tn-table-pager__label{white-space:nowrap}.tn-table-pager__size-select{min-width:84px}.tn-table-pager__range{white-space:nowrap}.tn-table-pager__buttons{display:flex;align-items:center;gap:4px}@media(max-width:600px){:host{gap:8px}.tn-table-pager__page-size{gap:4px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "disabled", "testId", "multiple", "optionTestIdKey", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9273
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTablePagerComponent, isStandalone: true, selector: "tn-table-pager", inputs: { testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, dataProvider: { classPropertyName: "dataProvider", publicName: "dataProvider", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, tablePaginationLabel: { classPropertyName: "tablePaginationLabel", publicName: "tablePaginationLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "resolvedTablePaginationLabel()" }, classAttribute: "tn-table-pager" }, ngImport: i0, template: "<div class=\"tn-table-pager__page-size\">\n <span class=\"tn-table-pager__label\">{{ resolvedItemsPerPageLabel() }}:</span>\n <tn-select\n class=\"tn-table-pager__size-select\"\n [testId]=\"childTestId('page-size')\"\n [options]=\"pageSizeSelectOptions()\"\n [ariaLabel]=\"resolvedItemsPerPageLabel()\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\" />\n</div>\n\n<span class=\"tn-table-pager__range\">\n @if (effectiveTotalItems() === 0) {\n 0 {{ resolvedOfLabel() }} 0\n } @else if (lastItemOnPage() > firstItemOnPage()) {\n {{ firstItemOnPage() }} \u2013 {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n } @else {\n {{ lastItemOnPage() }} {{ resolvedOfLabel() }} {{ effectiveTotalItems() }}\n }\n</span>\n\n<div class=\"tn-table-pager__buttons\">\n <tn-icon-button\n name=\"page-first\"\n library=\"mdi\"\n [testId]=\"childTestId('first-page')\"\n [ariaLabel]=\"resolvedFirstPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"goToPage(1)\" />\n <tn-icon-button\n name=\"chevron-left\"\n library=\"mdi\"\n [testId]=\"childTestId('previous-page')\"\n [ariaLabel]=\"resolvedPreviousPageLabel()\"\n [disabled]=\"isFirstPageDisabled()\"\n (onClick)=\"previousPage()\" />\n <tn-icon-button\n name=\"chevron-right\"\n library=\"mdi\"\n [testId]=\"childTestId('next-page')\"\n [ariaLabel]=\"resolvedNextPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"nextPage()\" />\n <tn-icon-button\n name=\"page-last\"\n library=\"mdi\"\n [testId]=\"childTestId('last-page')\"\n [ariaLabel]=\"resolvedLastPageLabel()\"\n [disabled]=\"isLastPageDisabled()\"\n (onClick)=\"goToPage(totalPages())\" />\n</div>\n", styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:16px;padding:10px;background-color:var(--tn-bg2);border:1px solid var(--tn-lines);color:var(--tn-fg2)}.tn-table-pager__page-size{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.tn-table-pager__label{white-space:nowrap}.tn-table-pager__size-select{min-width:84px}.tn-table-pager__range{white-space:nowrap}.tn-table-pager__buttons{display:flex;align-items:center;gap:4px}@media(max-width:600px){:host{gap:8px}.tn-table-pager__page-size{gap:4px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "allowEmpty", "emptyLabel", "disabled", "testId", "multiple", "optionTestIdKey", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9084
9274
|
}
|
|
9085
9275
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTablePagerComponent, decorators: [{
|
|
9086
9276
|
type: Component,
|
|
@@ -11844,8 +12034,10 @@ class TnTimeInputComponent {
|
|
|
11844
12034
|
}
|
|
11845
12035
|
// Event handlers
|
|
11846
12036
|
onSelectionChange(value) {
|
|
12037
|
+
// The inner select never sets allowEmpty, so `null` cannot occur here —
|
|
12038
|
+
// the widened type only satisfies tn-select's `selectionChange` contract.
|
|
11847
12039
|
this._value = value;
|
|
11848
|
-
this.onChange(value);
|
|
12040
|
+
this.onChange(value ?? '');
|
|
11849
12041
|
this.onTouched();
|
|
11850
12042
|
}
|
|
11851
12043
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -11855,7 +12047,7 @@ class TnTimeInputComponent {
|
|
|
11855
12047
|
useExisting: forwardRef(() => TnTimeInputComponent),
|
|
11856
12048
|
multi: true
|
|
11857
12049
|
}
|
|
11858
|
-
], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "disabled", "testId", "multiple", "optionTestIdKey", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }] });
|
|
12050
|
+
], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "ariaLabel", "noOptionsLabel", "allowEmpty", "emptyLabel", "disabled", "testId", "multiple", "optionTestIdKey", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }] });
|
|
11859
12051
|
}
|
|
11860
12052
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTimeInputComponent, decorators: [{
|
|
11861
12053
|
type: Component,
|