@truenas/ui-components 0.2.0 → 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,9 +1,9 @@
|
|
|
1
1
|
import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
|
|
2
2
|
import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { 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
|
-
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';
|
|
@@ -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'];
|
|
@@ -2628,6 +2703,36 @@ class TnInputComponent {
|
|
|
2628
2703
|
* the a11y tree.
|
|
2629
2704
|
*/
|
|
2630
2705
|
ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
2706
|
+
/**
|
|
2707
|
+
* Native `autocomplete` hint rendered on the input/textarea. Pass the standard
|
|
2708
|
+
* autofill tokens (`'username'`, `'current-password'`, `'new-password'`,
|
|
2709
|
+
* `'one-time-code'`, ...) so browsers and password managers can identify and
|
|
2710
|
+
* fill the field.
|
|
2711
|
+
*/
|
|
2712
|
+
autocomplete = input(undefined, ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
|
|
2713
|
+
/**
|
|
2714
|
+
* Native `name` attribute rendered on the input/textarea. Browsers and password
|
|
2715
|
+
* managers use it to identify the field; typically mirrors the form control name.
|
|
2716
|
+
*/
|
|
2717
|
+
name = input(undefined, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
2718
|
+
/**
|
|
2719
|
+
* Renders the native `readonly` attribute: the value is visible, focusable and
|
|
2720
|
+
* selectable but not editable. Unlike `disabled`, a readonly field stays in the
|
|
2721
|
+
* tab order and its form control stays enabled.
|
|
2722
|
+
*/
|
|
2723
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
2724
|
+
/**
|
|
2725
|
+
* Renders the native `required` attribute so browsers and assistive technology
|
|
2726
|
+
* expose the field as required. Validation itself stays with the consumer's form
|
|
2727
|
+
* validators (e.g. `Validators.required`); forms that fully own validation UX
|
|
2728
|
+
* should also set `novalidate` to suppress native submit blocking.
|
|
2729
|
+
*
|
|
2730
|
+
* Note: this drives only the native/a11y semantics. When wrapped in a
|
|
2731
|
+
* `tn-form-field`, the visual `*` indicator is inferred automatically from the
|
|
2732
|
+
* control's `Validators.required`; only validator-less setups need the form
|
|
2733
|
+
* field's own `required` input alongside this one.
|
|
2734
|
+
*/
|
|
2735
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
2631
2736
|
/**
|
|
2632
2737
|
* Integer/decimal switch — only meaningful when `inputType` is `InputType.Number`.
|
|
2633
2738
|
*
|
|
@@ -2657,12 +2762,27 @@ class TnInputComponent {
|
|
|
2657
2762
|
* (e.g. `1.5 GiB`). Ignored unless `inputType` is `Size`.
|
|
2658
2763
|
*/
|
|
2659
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" }] : []));
|
|
2660
2773
|
// Icon inputs
|
|
2661
2774
|
prefixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIcon" }] : []));
|
|
2662
2775
|
prefixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIconLibrary" }] : []));
|
|
2663
2776
|
suffixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIcon" }] : []));
|
|
2664
2777
|
suffixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconLibrary" }] : []));
|
|
2665
2778
|
suffixIconAriaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconAriaLabel" }] : []));
|
|
2779
|
+
/**
|
|
2780
|
+
* Semantic test-id base for the suffix-action button. The library prepends the
|
|
2781
|
+
* element type (`button`) and renders the result under whichever attribute name
|
|
2782
|
+
* is configured via `TN_TEST_ATTR` (default `data-testid`) — e.g.
|
|
2783
|
+
* `suffixActionTestId="toggle-password"` → `button-toggle-password`.
|
|
2784
|
+
*/
|
|
2785
|
+
suffixActionTestId = input(undefined, ...(ngDevMode ? [{ debugName: "suffixActionTestId" }] : []));
|
|
2666
2786
|
onSuffixAction = output();
|
|
2667
2787
|
hasPrefixIcon = computed(() => !!this.prefixIcon(), ...(ngDevMode ? [{ debugName: "hasPrefixIcon" }] : []));
|
|
2668
2788
|
hasSuffixIcon = computed(() => !!this.suffixIcon(), ...(ngDevMode ? [{ debugName: "hasSuffixIcon" }] : []));
|
|
@@ -2672,8 +2792,28 @@ class TnInputComponent {
|
|
|
2672
2792
|
isSize = computed(() => this.inputType() === InputType.Size, ...(ngDevMode ? [{ debugName: "isSize" }] : []));
|
|
2673
2793
|
/** True when the field only accepts whole numbers (i.e. `allowDecimals` is false). */
|
|
2674
2794
|
integerOnly = computed(() => !this.allowDecimals(), ...(ngDevMode ? [{ debugName: "integerOnly" }] : []));
|
|
2675
|
-
/**
|
|
2676
|
-
|
|
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" }] : []));
|
|
2677
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. */
|
|
2678
2818
|
numericInputMode = computed(() => {
|
|
2679
2819
|
if (!this.isNumeric()) {
|
|
@@ -2683,6 +2823,30 @@ class TnInputComponent {
|
|
|
2683
2823
|
}, ...(ngDevMode ? [{ debugName: "numericInputMode" }] : []));
|
|
2684
2824
|
/** Number and size modes are always single-line; they win over `multiline` if both are set. */
|
|
2685
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" }] : []));
|
|
2686
2850
|
// Unique per instance: a hard-coded id produced duplicate ids when multiple
|
|
2687
2851
|
// tn-inputs share a page (invalid HTML, and it breaks any future label `for=`,
|
|
2688
2852
|
// aria-describedby, or getElementById targeting this input).
|
|
@@ -2790,6 +2954,9 @@ class TnInputComponent {
|
|
|
2790
2954
|
}
|
|
2791
2955
|
this.onTouched();
|
|
2792
2956
|
}
|
|
2957
|
+
togglePasswordVisibility() {
|
|
2958
|
+
this.passwordVisible.update((visible) => !visible);
|
|
2959
|
+
}
|
|
2793
2960
|
/** Strips any character that can't appear in the current numeric mode (single leading '-', single '.' for decimals). */
|
|
2794
2961
|
sanitizeNumeric(raw) {
|
|
2795
2962
|
const allowDecimal = !this.integerOnly();
|
|
@@ -2818,13 +2985,13 @@ class TnInputComponent {
|
|
|
2818
2985
|
return Number.isNaN(parsed) ? null : parsed;
|
|
2819
2986
|
}
|
|
2820
2987
|
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: [
|
|
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: [
|
|
2822
2989
|
{
|
|
2823
2990
|
provide: NG_VALUE_ACCESSOR,
|
|
2824
2991
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2825
2992
|
multi: true
|
|
2826
2993
|
}
|
|
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"] }] });
|
|
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"] }] });
|
|
2828
2995
|
}
|
|
2829
2996
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, decorators: [{
|
|
2830
2997
|
type: Component,
|
|
@@ -2834,8 +3001,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2834
3001
|
useExisting: forwardRef(() => TnInputComponent),
|
|
2835
3002
|
multi: true
|
|
2836
3003
|
}
|
|
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"] }] } });
|
|
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"] }] } });
|
|
2839
3006
|
|
|
2840
3007
|
/**
|
|
2841
3008
|
* Harness for interacting with tn-icon in tests.
|
|
@@ -3095,6 +3262,7 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3095
3262
|
prefixIconEl = this.locatorForOptional('tn-icon.tn-input__prefix-icon');
|
|
3096
3263
|
suffixButton = this.locatorForOptional('.tn-input__suffix-action');
|
|
3097
3264
|
suffixIconEl = this.locatorForOptional(TnIconHarness.with({ ancestor: '.tn-input__suffix-action' }));
|
|
3265
|
+
visibilityToggle = this.locatorForOptional('.tn-input__visibility-toggle');
|
|
3098
3266
|
/**
|
|
3099
3267
|
* Gets a `HarnessPredicate` that can be used to search for an input
|
|
3100
3268
|
* with specific attributes.
|
|
@@ -3106,6 +3274,9 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3106
3274
|
return new HarnessPredicate(TnInputHarness, options)
|
|
3107
3275
|
.addOption('placeholder', options.placeholder, async (harness, placeholder) => {
|
|
3108
3276
|
return (await harness.getPlaceholder()) === placeholder;
|
|
3277
|
+
})
|
|
3278
|
+
.addOption('name', options.name, async (harness, name) => {
|
|
3279
|
+
return (await harness.getName()) === name;
|
|
3109
3280
|
});
|
|
3110
3281
|
}
|
|
3111
3282
|
/**
|
|
@@ -3206,6 +3377,58 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3206
3377
|
const input = await this.inputEl();
|
|
3207
3378
|
return input.getAttribute('aria-label');
|
|
3208
3379
|
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Gets the native `name` attribute (set via the `name` input).
|
|
3382
|
+
*
|
|
3383
|
+
* @returns Promise resolving to the name string, or null if unset.
|
|
3384
|
+
*/
|
|
3385
|
+
async getName() {
|
|
3386
|
+
if (await this.isMultiline()) {
|
|
3387
|
+
const textarea = await this.textareaEl();
|
|
3388
|
+
return textarea.getAttribute('name');
|
|
3389
|
+
}
|
|
3390
|
+
const input = await this.inputEl();
|
|
3391
|
+
return input.getAttribute('name');
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Gets the native `autocomplete` attribute (set via the `autocomplete` input).
|
|
3395
|
+
*
|
|
3396
|
+
* @returns Promise resolving to the autocomplete token, or null if unset.
|
|
3397
|
+
*/
|
|
3398
|
+
async getAutocomplete() {
|
|
3399
|
+
if (await this.isMultiline()) {
|
|
3400
|
+
const textarea = await this.textareaEl();
|
|
3401
|
+
return textarea.getAttribute('autocomplete');
|
|
3402
|
+
}
|
|
3403
|
+
const input = await this.inputEl();
|
|
3404
|
+
return input.getAttribute('autocomplete');
|
|
3405
|
+
}
|
|
3406
|
+
/**
|
|
3407
|
+
* Checks whether the input is readonly (set via the `readonly` input).
|
|
3408
|
+
*
|
|
3409
|
+
* @returns Promise resolving to true if the input is readonly.
|
|
3410
|
+
*/
|
|
3411
|
+
async isReadonly() {
|
|
3412
|
+
if (await this.isMultiline()) {
|
|
3413
|
+
const textarea = await this.textareaEl();
|
|
3414
|
+
return (await textarea.getProperty('readOnly')) ?? false;
|
|
3415
|
+
}
|
|
3416
|
+
const input = await this.inputEl();
|
|
3417
|
+
return (await input.getProperty('readOnly')) ?? false;
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Checks whether the input is marked required (set via the `required` input).
|
|
3421
|
+
*
|
|
3422
|
+
* @returns Promise resolving to true if the input is required.
|
|
3423
|
+
*/
|
|
3424
|
+
async isRequired() {
|
|
3425
|
+
if (await this.isMultiline()) {
|
|
3426
|
+
const textarea = await this.textareaEl();
|
|
3427
|
+
return (await textarea.getProperty('required')) ?? false;
|
|
3428
|
+
}
|
|
3429
|
+
const input = await this.inputEl();
|
|
3430
|
+
return (await input.getProperty('required')) ?? false;
|
|
3431
|
+
}
|
|
3209
3432
|
/**
|
|
3210
3433
|
* Gets the placeholder text.
|
|
3211
3434
|
*
|
|
@@ -3298,6 +3521,43 @@ class TnInputHarness extends ComponentHarness {
|
|
|
3298
3521
|
}
|
|
3299
3522
|
return button.click();
|
|
3300
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
|
+
}
|
|
3301
3561
|
/**
|
|
3302
3562
|
* Focuses the input element.
|
|
3303
3563
|
*
|
|
@@ -6196,6 +6456,17 @@ function activeErrorKey(errors) {
|
|
|
6196
6456
|
class TnFormFieldComponent {
|
|
6197
6457
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
6198
6458
|
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
6459
|
+
/**
|
|
6460
|
+
* Forces the visual `*` required indicator next to the label. Usually
|
|
6461
|
+
* unnecessary: the indicator is inferred automatically when the projected
|
|
6462
|
+
* control carries `Validators.required`. Set this only when inference can't
|
|
6463
|
+
* see the requirement — e.g. a validator wrapped in `Validators.compose(...)`
|
|
6464
|
+
* or a custom validator that emits a `required`-style error.
|
|
6465
|
+
*
|
|
6466
|
+
* The indicator is purely visual — for native/a11y semantics pair it with the
|
|
6467
|
+
* projected control's own `required` input (e.g. `tn-input`'s, which renders
|
|
6468
|
+
* the native attribute).
|
|
6469
|
+
*/
|
|
6199
6470
|
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
6200
6471
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
6201
6472
|
subscriptSizing = input('dynamic', ...(ngDevMode ? [{ debugName: "subscriptSizing" }] : []));
|
|
@@ -6227,7 +6498,15 @@ class TnFormFieldComponent {
|
|
|
6227
6498
|
invalid: false,
|
|
6228
6499
|
interacted: false,
|
|
6229
6500
|
errors: null,
|
|
6501
|
+
required: false,
|
|
6230
6502
|
}, ...(ngDevMode ? [{ debugName: "controlState" }] : []));
|
|
6503
|
+
/**
|
|
6504
|
+
* Whether the required indicator renders: forced via the `required` input, or
|
|
6505
|
+
* inferred from the projected control's validators (mirrors Angular Material's
|
|
6506
|
+
* `hasValidator(Validators.required)` approach — reference equality, so composed
|
|
6507
|
+
* or custom required-like validators need the explicit input).
|
|
6508
|
+
*/
|
|
6509
|
+
showRequired = computed(() => this.required() || this.controlState().required, ...(ngDevMode ? [{ debugName: "showRequired" }] : []));
|
|
6231
6510
|
hasError = computed(() => {
|
|
6232
6511
|
const state = this.controlState();
|
|
6233
6512
|
return state.invalid && state.interacted;
|
|
@@ -6260,6 +6539,7 @@ class TnFormFieldComponent {
|
|
|
6260
6539
|
invalid: !!control.invalid,
|
|
6261
6540
|
interacted: !!(control.dirty || control.touched),
|
|
6262
6541
|
errors: control.errors ?? null,
|
|
6542
|
+
required: !!control.control?.hasValidator(Validators.required),
|
|
6263
6543
|
});
|
|
6264
6544
|
}
|
|
6265
6545
|
}
|
|
@@ -6331,11 +6611,11 @@ class TnFormFieldComponent {
|
|
|
6331
6611
|
return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
|
|
6332
6612
|
}, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
|
|
6333
6613
|
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]=\"
|
|
6614
|
+
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
6615
|
}
|
|
6336
6616
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
|
|
6337
6617
|
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]=\"
|
|
6618
|
+
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
6619
|
}], 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
6620
|
|
|
6341
6621
|
/**
|
|
@@ -7378,81 +7658,6 @@ class TnSelectHarness extends ComponentHarness {
|
|
|
7378
7658
|
}
|
|
7379
7659
|
}
|
|
7380
7660
|
|
|
7381
|
-
/**
|
|
7382
|
-
* Marks an icon name for inclusion in the TrueNAS UI sprite generation.
|
|
7383
|
-
*
|
|
7384
|
-
* This function serves two purposes:
|
|
7385
|
-
* 1. At runtime: Applies library-specific prefixes (e.g., mdi-, app-)
|
|
7386
|
-
* 2. At build time: Marker for the scanner to detect icons for sprite inclusion
|
|
7387
|
-
*
|
|
7388
|
-
* Use this when icon names are computed dynamically or come from variables,
|
|
7389
|
-
* to ensure they're included in the sprite at build time.
|
|
7390
|
-
*
|
|
7391
|
-
* @example
|
|
7392
|
-
* // Static icon name - automatically detected from template
|
|
7393
|
-
* <tn-icon name="folder"></tn-icon>
|
|
7394
|
-
*
|
|
7395
|
-
* @example
|
|
7396
|
-
* // Dynamic MDI icon
|
|
7397
|
-
* const iconName = condition ? tnIconMarker("pencil", "mdi") : tnIconMarker("delete", "mdi");
|
|
7398
|
-
* <tn-icon [name]="iconName"></tn-icon>
|
|
7399
|
-
*
|
|
7400
|
-
* @example
|
|
7401
|
-
* // Dynamic custom icon (consumer's own icon)
|
|
7402
|
-
* const logo = tnIconMarker("your-logo-name", "custom");
|
|
7403
|
-
* <tn-icon [name]="logo"></tn-icon>
|
|
7404
|
-
*
|
|
7405
|
-
* @example
|
|
7406
|
-
* // Array of dynamic icons
|
|
7407
|
-
* const actions = [
|
|
7408
|
-
* { name: "Save", icon: tnIconMarker("content-save", "mdi") },
|
|
7409
|
-
* { name: "Cancel", icon: tnIconMarker("close", "mdi") }
|
|
7410
|
-
* ];
|
|
7411
|
-
*
|
|
7412
|
-
* @param iconName - The icon name to mark for sprite inclusion
|
|
7413
|
-
* @param library - Optional library type: 'mdi', 'material', or 'custom'
|
|
7414
|
-
* @returns The icon name with appropriate prefix applied
|
|
7415
|
-
* @public
|
|
7416
|
-
*/
|
|
7417
|
-
function tnIconMarker(iconName, library) {
|
|
7418
|
-
// Apply library-specific prefixes
|
|
7419
|
-
if (library === 'mdi' && !iconName.startsWith('mdi-')) {
|
|
7420
|
-
return `mdi-${iconName}`;
|
|
7421
|
-
}
|
|
7422
|
-
if (library === 'custom' && !iconName.startsWith('app-')) {
|
|
7423
|
-
return `app-${iconName}`;
|
|
7424
|
-
}
|
|
7425
|
-
if (library === 'material' && !iconName.startsWith('mat-')) {
|
|
7426
|
-
return `mat-${iconName}`;
|
|
7427
|
-
}
|
|
7428
|
-
return iconName;
|
|
7429
|
-
}
|
|
7430
|
-
/**
|
|
7431
|
-
* INTERNAL LIBRARY USE ONLY
|
|
7432
|
-
*
|
|
7433
|
-
* Marks an icon name for inclusion in the sprite generation with library namespace.
|
|
7434
|
-
* This function MUST be used by library component code for custom icons.
|
|
7435
|
-
*
|
|
7436
|
-
* The TypeScript type enforces that the icon name starts with 'tn-' prefix,
|
|
7437
|
-
* which reserves this namespace exclusively for library-provided custom icons.
|
|
7438
|
-
*
|
|
7439
|
-
* @example
|
|
7440
|
-
* ```typescript
|
|
7441
|
-
* // ✅ Correct - Library component code
|
|
7442
|
-
* const icon = libIconMarker('tn-dataset');
|
|
7443
|
-
*
|
|
7444
|
-
* // ❌ Wrong - Will cause TypeScript error
|
|
7445
|
-
* const icon = libIconMarker('dataset');
|
|
7446
|
-
* ```
|
|
7447
|
-
*
|
|
7448
|
-
* @param iconName - The icon name with 'tn-' prefix (enforced by TypeScript)
|
|
7449
|
-
* @returns The same icon name (identity function)
|
|
7450
|
-
* @internal
|
|
7451
|
-
*/
|
|
7452
|
-
function libIconMarker(iconName) {
|
|
7453
|
-
return iconName;
|
|
7454
|
-
}
|
|
7455
|
-
|
|
7456
7661
|
/// <reference types="jest" />
|
|
7457
7662
|
/**
|
|
7458
7663
|
* Creates default mock implementation of TnSpriteLoaderService.
|