@truenas/ui-components 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
|
|
2
2
|
import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { 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';
|
|
4
|
+
import { InjectionToken, inject, Renderer2, ElementRef, input, effect, Directive, ViewContainerRef, output, viewChild, computed, signal, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, isDevMode, ViewEncapsulation, contentChildren, HostListener, linkedSignal, 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
6
|
import { NG_VALUE_ACCESSOR, FormsModule, NgControl, Validators } from '@angular/forms';
|
|
7
7
|
import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
|
|
@@ -2514,6 +2514,81 @@ var InputType;
|
|
|
2514
2514
|
InputType["Size"] = "size";
|
|
2515
2515
|
})(InputType || (InputType = {}));
|
|
2516
2516
|
|
|
2517
|
+
/**
|
|
2518
|
+
* Marks an icon name for inclusion in the TrueNAS UI sprite generation.
|
|
2519
|
+
*
|
|
2520
|
+
* This function serves two purposes:
|
|
2521
|
+
* 1. At runtime: Applies library-specific prefixes (e.g., mdi-, app-)
|
|
2522
|
+
* 2. At build time: Marker for the scanner to detect icons for sprite inclusion
|
|
2523
|
+
*
|
|
2524
|
+
* Use this when icon names are computed dynamically or come from variables,
|
|
2525
|
+
* to ensure they're included in the sprite at build time.
|
|
2526
|
+
*
|
|
2527
|
+
* @example
|
|
2528
|
+
* // Static icon name - automatically detected from template
|
|
2529
|
+
* <tn-icon name="folder"></tn-icon>
|
|
2530
|
+
*
|
|
2531
|
+
* @example
|
|
2532
|
+
* // Dynamic MDI icon
|
|
2533
|
+
* const iconName = condition ? tnIconMarker("pencil", "mdi") : tnIconMarker("delete", "mdi");
|
|
2534
|
+
* <tn-icon [name]="iconName"></tn-icon>
|
|
2535
|
+
*
|
|
2536
|
+
* @example
|
|
2537
|
+
* // Dynamic custom icon (consumer's own icon)
|
|
2538
|
+
* const logo = tnIconMarker("your-logo-name", "custom");
|
|
2539
|
+
* <tn-icon [name]="logo"></tn-icon>
|
|
2540
|
+
*
|
|
2541
|
+
* @example
|
|
2542
|
+
* // Array of dynamic icons
|
|
2543
|
+
* const actions = [
|
|
2544
|
+
* { name: "Save", icon: tnIconMarker("content-save", "mdi") },
|
|
2545
|
+
* { name: "Cancel", icon: tnIconMarker("close", "mdi") }
|
|
2546
|
+
* ];
|
|
2547
|
+
*
|
|
2548
|
+
* @param iconName - The icon name to mark for sprite inclusion
|
|
2549
|
+
* @param library - Optional library type: 'mdi', 'material', or 'custom'
|
|
2550
|
+
* @returns The icon name with appropriate prefix applied
|
|
2551
|
+
* @public
|
|
2552
|
+
*/
|
|
2553
|
+
function tnIconMarker(iconName, library) {
|
|
2554
|
+
// Apply library-specific prefixes
|
|
2555
|
+
if (library === 'mdi' && !iconName.startsWith('mdi-')) {
|
|
2556
|
+
return `mdi-${iconName}`;
|
|
2557
|
+
}
|
|
2558
|
+
if (library === 'custom' && !iconName.startsWith('app-')) {
|
|
2559
|
+
return `app-${iconName}`;
|
|
2560
|
+
}
|
|
2561
|
+
if (library === 'material' && !iconName.startsWith('mat-')) {
|
|
2562
|
+
return `mat-${iconName}`;
|
|
2563
|
+
}
|
|
2564
|
+
return iconName;
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* INTERNAL LIBRARY USE ONLY
|
|
2568
|
+
*
|
|
2569
|
+
* Marks an icon name for inclusion in the sprite generation with library namespace.
|
|
2570
|
+
* This function MUST be used by library component code for custom icons.
|
|
2571
|
+
*
|
|
2572
|
+
* The TypeScript type enforces that the icon name starts with 'tn-' prefix,
|
|
2573
|
+
* which reserves this namespace exclusively for library-provided custom icons.
|
|
2574
|
+
*
|
|
2575
|
+
* @example
|
|
2576
|
+
* ```typescript
|
|
2577
|
+
* // ✅ Correct - Library component code
|
|
2578
|
+
* const icon = libIconMarker('tn-dataset');
|
|
2579
|
+
*
|
|
2580
|
+
* // ❌ Wrong - Will cause TypeScript error
|
|
2581
|
+
* const icon = libIconMarker('dataset');
|
|
2582
|
+
* ```
|
|
2583
|
+
*
|
|
2584
|
+
* @param iconName - The icon name with 'tn-' prefix (enforced by TypeScript)
|
|
2585
|
+
* @returns The same icon name (identity function)
|
|
2586
|
+
* @internal
|
|
2587
|
+
*/
|
|
2588
|
+
function libIconMarker(iconName) {
|
|
2589
|
+
return iconName;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2517
2592
|
// Ordered binary/decimal prefixes; the array index doubles as the power applied
|
|
2518
2593
|
// to the base (B = base^0, K = base^1, M = base^2, ...).
|
|
2519
2594
|
const UNIT_PREFIXES = ['B', 'K', 'M', 'G', 'T', 'P', 'E'];
|
|
@@ -2687,6 +2762,14 @@ class TnInputComponent {
|
|
|
2687
2762
|
* (e.g. `1.5 GiB`). Ignored unless `inputType` is `Size`.
|
|
2688
2763
|
*/
|
|
2689
2764
|
sizeRound = input(2, ...(ngDevMode ? [{ debugName: "sizeRound" }] : []));
|
|
2765
|
+
/**
|
|
2766
|
+
* Whether a `Password` field renders the built-in visibility toggle — the eye
|
|
2767
|
+
* button that switches the field between masked and plain-text display.
|
|
2768
|
+
* Defaults to true; set false for secrets that must never be revealed.
|
|
2769
|
+
* Setting `suffixIcon` suppresses the toggle automatically (a custom suffix
|
|
2770
|
+
* control replaces it). Ignored unless `inputType` is `Password`.
|
|
2771
|
+
*/
|
|
2772
|
+
showPasswordToggle = input(true, ...(ngDevMode ? [{ debugName: "showPasswordToggle" }] : []));
|
|
2690
2773
|
// Icon inputs
|
|
2691
2774
|
prefixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIcon" }] : []));
|
|
2692
2775
|
prefixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIconLibrary" }] : []));
|
|
@@ -2709,8 +2792,28 @@ class TnInputComponent {
|
|
|
2709
2792
|
isSize = computed(() => this.inputType() === InputType.Size, ...(ngDevMode ? [{ debugName: "isSize" }] : []));
|
|
2710
2793
|
/** True when the field only accepts whole numbers (i.e. `allowDecimals` is false). */
|
|
2711
2794
|
integerOnly = computed(() => !this.allowDecimals(), ...(ngDevMode ? [{ debugName: "integerOnly" }] : []));
|
|
2712
|
-
/**
|
|
2713
|
-
|
|
2795
|
+
/** True when the field is a password field. */
|
|
2796
|
+
isPassword = computed(() => this.inputType() === InputType.Password, ...(ngDevMode ? [{ debugName: "isPassword" }] : []));
|
|
2797
|
+
/**
|
|
2798
|
+
* Whether the password is currently revealed. Not exposed as an input so a
|
|
2799
|
+
* reveal is always an explicit user gesture, and linked to the toggle's own
|
|
2800
|
+
* availability so a revealed value never outlives the control that revealed
|
|
2801
|
+
* it: whenever the toggle goes away (the field leaves password mode,
|
|
2802
|
+
* `showPasswordToggle` flips off, a `suffixIcon` replaces it) or the field
|
|
2803
|
+
* is disabled, the field snaps back to masked.
|
|
2804
|
+
*/
|
|
2805
|
+
passwordVisible = linkedSignal({ ...(ngDevMode ? { debugName: "passwordVisible" } : {}), source: () => this.hasPasswordToggle() && !this.isDisabled(),
|
|
2806
|
+
computation: () => false });
|
|
2807
|
+
/** The `type` attribute actually rendered. Number and size modes use a text input: number avoids the native type="number" footguns, size must accept unit letters. A revealed password renders as text — flipping the type attribute is the standard reveal mechanism and preserves value and caret. */
|
|
2808
|
+
resolvedType = computed(() => {
|
|
2809
|
+
if (this.isNumeric() || this.isSize()) {
|
|
2810
|
+
return InputType.PlainText;
|
|
2811
|
+
}
|
|
2812
|
+
if (this.isPassword() && this.passwordVisible()) {
|
|
2813
|
+
return InputType.PlainText;
|
|
2814
|
+
}
|
|
2815
|
+
return this.inputType();
|
|
2816
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedType" }] : []));
|
|
2714
2817
|
/** `inputmode` hint: numeric keypad for integers, decimal keypad otherwise. Null (omitted) outside number mode — size fields accept unit letters, so they keep the full keyboard. */
|
|
2715
2818
|
numericInputMode = computed(() => {
|
|
2716
2819
|
if (!this.isNumeric()) {
|
|
@@ -2720,6 +2823,30 @@ class TnInputComponent {
|
|
|
2720
2823
|
}, ...(ngDevMode ? [{ debugName: "numericInputMode" }] : []));
|
|
2721
2824
|
/** Number and size modes are always single-line; they win over `multiline` if both are set. */
|
|
2722
2825
|
showTextarea = computed(() => this.multiline() && !this.isNumeric() && !this.isSize(), ...(ngDevMode ? [{ debugName: "showTextarea" }] : []));
|
|
2826
|
+
/**
|
|
2827
|
+
* The visibility toggle renders only on single-line password fields (a
|
|
2828
|
+
* password+multiline combo renders a textarea, which has no `type` to flip),
|
|
2829
|
+
* and yields to a consumer-provided `suffixIcon` — a custom suffix control
|
|
2830
|
+
* replaces the built-in toggle rather than stacking a second button.
|
|
2831
|
+
*/
|
|
2832
|
+
hasPasswordToggle = computed(() => this.isPassword() && this.showPasswordToggle() && !this.showTextarea() && !this.hasSuffixIcon(), ...(ngDevMode ? [{ debugName: "hasPasswordToggle" }] : []));
|
|
2833
|
+
/**
|
|
2834
|
+
* Icon for the visibility toggle, mirroring the field's current state: a
|
|
2835
|
+
* slashed eye while masked, an open eye while revealed. The literal
|
|
2836
|
+
* `tnIconMarker` calls double as build-time markers that pull both icons
|
|
2837
|
+
* into the sprite.
|
|
2838
|
+
*/
|
|
2839
|
+
passwordToggleIcon = computed(() => this.passwordVisible() ? tnIconMarker('eye', 'mdi') : tnIconMarker('eye-off', 'mdi'), ...(ngDevMode ? [{ debugName: "passwordToggleIcon" }] : []));
|
|
2840
|
+
/**
|
|
2841
|
+
* Role-first test-id segments for the visibility toggle:
|
|
2842
|
+
* `button-toggle-password[-<testId>]`. The toggle is fixed field chrome, so
|
|
2843
|
+
* the role leads (matching the dialog-shell close/fullscreen convention) and
|
|
2844
|
+
* automation can target every password toggle with one prefix selector.
|
|
2845
|
+
*/
|
|
2846
|
+
passwordToggleTestId = computed(() => {
|
|
2847
|
+
const base = this.testId();
|
|
2848
|
+
return ['toggle-password', ...(Array.isArray(base) ? base : [base])];
|
|
2849
|
+
}, ...(ngDevMode ? [{ debugName: "passwordToggleTestId" }] : []));
|
|
2723
2850
|
// Unique per instance: a hard-coded id produced duplicate ids when multiple
|
|
2724
2851
|
// tn-inputs share a page (invalid HTML, and it breaks any future label `for=`,
|
|
2725
2852
|
// aria-describedby, or getElementById targeting this input).
|
|
@@ -2827,6 +2954,9 @@ class TnInputComponent {
|
|
|
2827
2954
|
}
|
|
2828
2955
|
this.onTouched();
|
|
2829
2956
|
}
|
|
2957
|
+
togglePasswordVisibility() {
|
|
2958
|
+
this.passwordVisible.update((visible) => !visible);
|
|
2959
|
+
}
|
|
2830
2960
|
/** Strips any character that can't appear in the current numeric mode (single leading '-', single '.' for decimals). */
|
|
2831
2961
|
sanitizeNumeric(raw) {
|
|
2832
2962
|
const allowDecimal = !this.integerOnly();
|
|
@@ -2855,13 +2985,13 @@ class TnInputComponent {
|
|
|
2855
2985
|
return Number.isNaN(parsed) ? null : parsed;
|
|
2856
2986
|
}
|
|
2857
2987
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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: [
|
|
2988
|
+
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 }, showPasswordToggle: { classPropertyName: "showPasswordToggle", publicName: "showPasswordToggle", 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: [
|
|
2859
2989
|
{
|
|
2860
2990
|
provide: NG_VALUE_ACCESSOR,
|
|
2861
2991
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2862
2992
|
multi: true
|
|
2863
2993
|
}
|
|
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"] }] });
|
|
2994
|
+
], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
2865
2995
|
}
|
|
2866
2996
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, decorators: [{
|
|
2867
2997
|
type: Component,
|
|
@@ -2871,8 +3001,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2871
3001
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2872
3002
|
multi: true
|
|
2873
3003
|
}
|
|
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"] }] } });
|
|
3004
|
+
], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon() || hasPasswordToggle()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name()\"\n [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n tnTestIdType=\"button\"\n [tnTestId]=\"suffixActionTestId()\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n\n <!-- Password visibility toggle. State is conveyed via the swapping\n aria-label alone \u2014 no aria-pressed, which would contradict a label that\n already names the action (\"Hide password, pressed\" is ambiguous). -->\n @if (hasPasswordToggle()) {\n <button\n type=\"button\"\n class=\"tn-input__visibility-toggle\"\n tnTestIdType=\"button\"\n [tnTestId]=\"passwordToggleTestId()\"\n [attr.aria-label]=\"passwordVisible() ? 'Hide password' : 'Show password'\"\n [disabled]=\"isDisabled()\"\n (click)=\"togglePasswordVisibility()\"\n >\n <tn-icon size=\"sm\" aria-hidden=\"true\" [name]=\"passwordToggleIcon()\" />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action,.tn-input__visibility-toggle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled),.tn-input__visibility-toggle:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible,.tn-input__visibility-toggle:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled,.tn-input__visibility-toggle:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action,.tn-input__visibility-toggle{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
|
|
3005
|
+
}], propDecorators: { inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], inputType: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputType", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], allowDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDecimals", required: false }] }], sizeStandard: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeStandard", required: false }] }], sizeDefaultUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeDefaultUnit", required: false }] }], sizeRound: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeRound", required: false }] }], showPasswordToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPasswordToggle", required: false }] }], prefixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIcon", required: false }] }], prefixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIconLibrary", required: false }] }], suffixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIcon", required: false }] }], suffixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconLibrary", required: false }] }], suffixIconAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconAriaLabel", required: false }] }], suffixActionTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixActionTestId", required: false }] }], onSuffixAction: [{ type: i0.Output, args: ["onSuffixAction"] }] } });
|
|
2876
3006
|
|
|
2877
3007
|
/**
|
|
2878
3008
|
* Harness for interacting with tn-icon in tests.
|
|
@@ -3132,6 +3262,7 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3132
3262
|
prefixIconEl = this.locatorForOptional('tn-icon.tn-input__prefix-icon');
|
|
3133
3263
|
suffixButton = this.locatorForOptional('.tn-input__suffix-action');
|
|
3134
3264
|
suffixIconEl = this.locatorForOptional(TnIconHarness.with({ ancestor: '.tn-input__suffix-action' }));
|
|
3265
|
+
visibilityToggle = this.locatorForOptional('.tn-input__visibility-toggle');
|
|
3135
3266
|
/**
|
|
3136
3267
|
* Gets a `HarnessPredicate` that can be used to search for an input
|
|
3137
3268
|
* with specific attributes.
|
|
@@ -3390,6 +3521,43 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3390
3521
|
}
|
|
3391
3522
|
return button.click();
|
|
3392
3523
|
}
|
|
3524
|
+
/**
|
|
3525
|
+
* Checks whether the password visibility toggle is present.
|
|
3526
|
+
*
|
|
3527
|
+
* @returns Promise resolving to true if the toggle button exists.
|
|
3528
|
+
*/
|
|
3529
|
+
async hasPasswordToggle() {
|
|
3530
|
+
const toggle = await this.visibilityToggle();
|
|
3531
|
+
return toggle !== null;
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Whether the password is currently revealed (the field renders as plain text).
|
|
3535
|
+
*
|
|
3536
|
+
* Only meaningful on password-type inputs; resolves false while masked.
|
|
3537
|
+
*
|
|
3538
|
+
* @returns Promise resolving to true when the value is shown in plain text.
|
|
3539
|
+
*/
|
|
3540
|
+
async isPasswordRevealed() {
|
|
3541
|
+
const toggle = await this.visibilityToggle();
|
|
3542
|
+
if (!toggle) {
|
|
3543
|
+
return false;
|
|
3544
|
+
}
|
|
3545
|
+
const input = await this.inputEl();
|
|
3546
|
+
return (await input.getProperty('type')) === 'text';
|
|
3547
|
+
}
|
|
3548
|
+
/**
|
|
3549
|
+
* Clicks the password visibility toggle, switching between masked and
|
|
3550
|
+
* plain-text display.
|
|
3551
|
+
*
|
|
3552
|
+
* @returns Promise that resolves when the click action is complete.
|
|
3553
|
+
*/
|
|
3554
|
+
async togglePasswordVisibility() {
|
|
3555
|
+
const toggle = await this.visibilityToggle();
|
|
3556
|
+
if (!toggle) {
|
|
3557
|
+
throw new Error('No password visibility toggle found on this input.');
|
|
3558
|
+
}
|
|
3559
|
+
return toggle.click();
|
|
3560
|
+
}
|
|
3393
3561
|
/**
|
|
3394
3562
|
* Focuses the input element.
|
|
3395
3563
|
*
|
|
@@ -7490,81 +7658,6 @@ class TnSelectHarness extends ComponentHarness {
|
|
|
7490
7658
|
}
|
|
7491
7659
|
}
|
|
7492
7660
|
|
|
7493
|
-
/**
|
|
7494
|
-
* Marks an icon name for inclusion in the TrueNAS UI sprite generation.
|
|
7495
|
-
*
|
|
7496
|
-
* This function serves two purposes:
|
|
7497
|
-
* 1. At runtime: Applies library-specific prefixes (e.g., mdi-, app-)
|
|
7498
|
-
* 2. At build time: Marker for the scanner to detect icons for sprite inclusion
|
|
7499
|
-
*
|
|
7500
|
-
* Use this when icon names are computed dynamically or come from variables,
|
|
7501
|
-
* to ensure they're included in the sprite at build time.
|
|
7502
|
-
*
|
|
7503
|
-
* @example
|
|
7504
|
-
* // Static icon name - automatically detected from template
|
|
7505
|
-
* <tn-icon name="folder"></tn-icon>
|
|
7506
|
-
*
|
|
7507
|
-
* @example
|
|
7508
|
-
* // Dynamic MDI icon
|
|
7509
|
-
* const iconName = condition ? tnIconMarker("pencil", "mdi") : tnIconMarker("delete", "mdi");
|
|
7510
|
-
* <tn-icon [name]="iconName"></tn-icon>
|
|
7511
|
-
*
|
|
7512
|
-
* @example
|
|
7513
|
-
* // Dynamic custom icon (consumer's own icon)
|
|
7514
|
-
* const logo = tnIconMarker("your-logo-name", "custom");
|
|
7515
|
-
* <tn-icon [name]="logo"></tn-icon>
|
|
7516
|
-
*
|
|
7517
|
-
* @example
|
|
7518
|
-
* // Array of dynamic icons
|
|
7519
|
-
* const actions = [
|
|
7520
|
-
* { name: "Save", icon: tnIconMarker("content-save", "mdi") },
|
|
7521
|
-
* { name: "Cancel", icon: tnIconMarker("close", "mdi") }
|
|
7522
|
-
* ];
|
|
7523
|
-
*
|
|
7524
|
-
* @param iconName - The icon name to mark for sprite inclusion
|
|
7525
|
-
* @param library - Optional library type: 'mdi', 'material', or 'custom'
|
|
7526
|
-
* @returns The icon name with appropriate prefix applied
|
|
7527
|
-
* @public
|
|
7528
|
-
*/
|
|
7529
|
-
function tnIconMarker(iconName, library) {
|
|
7530
|
-
// Apply library-specific prefixes
|
|
7531
|
-
if (library === 'mdi' && !iconName.startsWith('mdi-')) {
|
|
7532
|
-
return `mdi-${iconName}`;
|
|
7533
|
-
}
|
|
7534
|
-
if (library === 'custom' && !iconName.startsWith('app-')) {
|
|
7535
|
-
return `app-${iconName}`;
|
|
7536
|
-
}
|
|
7537
|
-
if (library === 'material' && !iconName.startsWith('mat-')) {
|
|
7538
|
-
return `mat-${iconName}`;
|
|
7539
|
-
}
|
|
7540
|
-
return iconName;
|
|
7541
|
-
}
|
|
7542
|
-
/**
|
|
7543
|
-
* INTERNAL LIBRARY USE ONLY
|
|
7544
|
-
*
|
|
7545
|
-
* Marks an icon name for inclusion in the sprite generation with library namespace.
|
|
7546
|
-
* This function MUST be used by library component code for custom icons.
|
|
7547
|
-
*
|
|
7548
|
-
* The TypeScript type enforces that the icon name starts with 'tn-' prefix,
|
|
7549
|
-
* which reserves this namespace exclusively for library-provided custom icons.
|
|
7550
|
-
*
|
|
7551
|
-
* @example
|
|
7552
|
-
* ```typescript
|
|
7553
|
-
* // ✅ Correct - Library component code
|
|
7554
|
-
* const icon = libIconMarker('tn-dataset');
|
|
7555
|
-
*
|
|
7556
|
-
* // ❌ Wrong - Will cause TypeScript error
|
|
7557
|
-
* const icon = libIconMarker('dataset');
|
|
7558
|
-
* ```
|
|
7559
|
-
*
|
|
7560
|
-
* @param iconName - The icon name with 'tn-' prefix (enforced by TypeScript)
|
|
7561
|
-
* @returns The same icon name (identity function)
|
|
7562
|
-
* @internal
|
|
7563
|
-
*/
|
|
7564
|
-
function libIconMarker(iconName) {
|
|
7565
|
-
return iconName;
|
|
7566
|
-
}
|
|
7567
|
-
|
|
7568
7661
|
/// <reference types="jest" />
|
|
7569
7662
|
/**
|
|
7570
7663
|
* Creates default mock implementation of TnSpriteLoaderService.
|