@truenas/ui-components 0.1.46 → 0.1.48
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.
- package/README.md +28 -1
- package/assets/tn-icons/forwarding-mappings.json +33 -0
- package/fesm2022/truenas-ui-components.mjs +39 -3
- package/fesm2022/truenas-ui-components.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/icon-sprite/__fixtures__/consumer-templates/basic.component.html +30 -0
- package/scripts/icon-sprite/__fixtures__/custom-icons/brand.svg +1 -0
- package/scripts/icon-sprite/__fixtures__/custom-icons/logo.svg +1 -0
- package/scripts/icon-sprite/__fixtures__/marker-sources/icons.ts +24 -0
- package/scripts/icon-sprite/cli-main.ts +27 -8
- package/scripts/icon-sprite/generate-sprite.ts +15 -4
- package/scripts/icon-sprite/jest.config.ts +14 -0
- package/scripts/icon-sprite/lib/find-icons-in-forwarding-components.spec.ts +182 -0
- package/scripts/icon-sprite/lib/find-icons-in-forwarding-components.ts +68 -0
- package/scripts/icon-sprite/lib/find-icons-in-templates.spec.ts +119 -0
- package/scripts/icon-sprite/lib/find-icons-in-templates.ts +66 -17
- package/scripts/icon-sprite/lib/find-icons-with-marker.spec.ts +58 -0
- package/scripts/icon-sprite/lib/find-icons-with-marker.ts +37 -22
- package/scripts/icon-sprite/lib/validate-icons.spec.ts +170 -0
- package/scripts/icon-sprite/lib/validate-icons.ts +185 -0
- package/scripts/icon-sprite/tsconfig.json +14 -0
- package/types/truenas-ui-components.d.ts +7 -0
package/README.md
CHANGED
|
@@ -133,7 +133,8 @@ npm run icons
|
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
This will:
|
|
136
|
-
- Scan your templates for `<tn-icon>` elements
|
|
136
|
+
- Scan your templates for `<tn-icon>` and `<tn-icon-button>` elements
|
|
137
|
+
- Detect icons passed to forwarding components (e.g., `<tn-empty icon="inbox">`)
|
|
137
138
|
- Detect icons marked with `tnIconMarker()` in TypeScript
|
|
138
139
|
- Generate `src/assets/icons/sprite.svg` with only used icons
|
|
139
140
|
- Create `src/assets/icons/sprite-config.json` with manifest
|
|
@@ -195,6 +196,32 @@ export class MyComponent {
|
|
|
195
196
|
}
|
|
196
197
|
```
|
|
197
198
|
|
|
199
|
+
### Icon Forwarding Components
|
|
200
|
+
|
|
201
|
+
Some library components accept icon inputs and forward them to an internal `<tn-icon>`. The sprite generator automatically detects icons passed to these components:
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<!-- These icons are automatically included in the sprite -->
|
|
205
|
+
<tn-empty icon="inbox" iconLibrary="mdi" title="No messages"></tn-empty>
|
|
206
|
+
<tn-input prefixIcon="search" prefixIconLibrary="mdi"></tn-input>
|
|
207
|
+
<tn-chip icon="star"></tn-chip>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
No `tnIconMarker()` is needed for static icon attributes on forwarding components.
|
|
211
|
+
|
|
212
|
+
The library ships a `forwarding-mappings.json` manifest that tells the scanner which components forward icons. If your app has its own forwarding components, you can create a `forwarding-mappings.json` in your source directory:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
[
|
|
216
|
+
{
|
|
217
|
+
"selector": "app-status-badge",
|
|
218
|
+
"iconSlots": [
|
|
219
|
+
{ "iconAttribute": "icon", "libraryAttribute": "iconLibrary", "defaultLibrary": "mdi" }
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
```
|
|
224
|
+
|
|
198
225
|
### Custom Icons
|
|
199
226
|
|
|
200
227
|
1. **Create a directory for your custom SVG icons:**
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"selector": "tn-empty",
|
|
4
|
+
"iconSlots": [
|
|
5
|
+
{
|
|
6
|
+
"iconAttribute": "icon",
|
|
7
|
+
"libraryAttribute": "iconLibrary",
|
|
8
|
+
"defaultLibrary": "mdi"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"selector": "tn-input",
|
|
14
|
+
"iconSlots": [
|
|
15
|
+
{
|
|
16
|
+
"iconAttribute": "prefixIcon",
|
|
17
|
+
"libraryAttribute": "prefixIconLibrary"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"iconAttribute": "suffixIcon",
|
|
21
|
+
"libraryAttribute": "suffixIconLibrary"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"selector": "tn-chip",
|
|
27
|
+
"iconSlots": [
|
|
28
|
+
{
|
|
29
|
+
"iconAttribute": "icon"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, input, output, viewChild, signal, computed, effect, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, ViewEncapsulation, Directive, contentChildren, ViewContainerRef, contentChild, ChangeDetectorRef, HostListener, TemplateRef, DestroyRef, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, input, output, viewChild, signal, computed, effect, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, isDevMode, ViewEncapsulation, Directive, contentChildren, ViewContainerRef, contentChild, ChangeDetectorRef, HostListener, TemplateRef, DestroyRef, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
|
|
3
3
|
import * as i1$4 from '@angular/forms';
|
|
4
4
|
import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
|
|
5
5
|
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
@@ -1062,6 +1062,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1062
1062
|
}], ctorParameters: () => [{ type: i1$1.DomSanitizer }, { type: TnSpriteLoaderService }] });
|
|
1063
1063
|
|
|
1064
1064
|
class TnIconComponent {
|
|
1065
|
+
static warnedIcons = new Set();
|
|
1065
1066
|
name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
1066
1067
|
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
1067
1068
|
color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
@@ -1165,6 +1166,8 @@ class TnIconComponent {
|
|
|
1165
1166
|
if (registryResult) {
|
|
1166
1167
|
return registryResult;
|
|
1167
1168
|
}
|
|
1169
|
+
// Warn in dev mode when a sprite-prefixed icon is missing from the sprite
|
|
1170
|
+
this.warnMissingSpriteIcon(effectiveIconName, name, library);
|
|
1168
1171
|
// 2. Try built-in third-party patterns (deprecated - use registry instead)
|
|
1169
1172
|
const thirdPartyResult = this.tryThirdPartyIcon(effectiveIconName);
|
|
1170
1173
|
if (thirdPartyResult) {
|
|
@@ -1266,6 +1269,39 @@ class TnIconComponent {
|
|
|
1266
1269
|
// Default to first 2 characters
|
|
1267
1270
|
return name.substring(0, 2).toUpperCase();
|
|
1268
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Warns once per icon name in dev mode when a sprite-prefixed icon is not
|
|
1274
|
+
* found in the sprite. Only fires when the sprite is loaded and the icon
|
|
1275
|
+
* name has a prefix indicating it should be a sprite icon.
|
|
1276
|
+
*/
|
|
1277
|
+
warnMissingSpriteIcon(effectiveIconName, originalName, library) {
|
|
1278
|
+
if (!isDevMode()) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
// Only warn for icons that look like they should be in the sprite
|
|
1282
|
+
const isSpriteIcon = /^(mdi-|mat-|tn-|app-)/.test(effectiveIconName);
|
|
1283
|
+
if (!isSpriteIcon) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
// Don't warn if sprite hasn't loaded yet (would be a false positive)
|
|
1287
|
+
if (!this.iconRegistry.getSpriteLoader().isSpriteLoaded()) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
// Deduplicate: warn once per icon name per session
|
|
1291
|
+
if (TnIconComponent.warnedIcons.has(effectiveIconName)) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
TnIconComponent.warnedIcons.add(effectiveIconName);
|
|
1295
|
+
const lib = library || (effectiveIconName.startsWith('mdi-') ? 'mdi' : effectiveIconName.startsWith('mat-') ? 'material' : undefined);
|
|
1296
|
+
// Build the marker example without a literal tnIconMarker() call,
|
|
1297
|
+
// so the grep-based sprite scanner doesn't pick this up as an actual icon.
|
|
1298
|
+
const markerFn = 'tnIcon' + 'Marker';
|
|
1299
|
+
const markerExample = lib
|
|
1300
|
+
? `${markerFn}('${originalName}', '${lib}')`
|
|
1301
|
+
: `${markerFn}('${originalName}')`;
|
|
1302
|
+
console.warn(`[TrueNAS UI] Icon '${effectiveIconName}' not found in sprite. ` +
|
|
1303
|
+
`To include it, add ${markerExample} to your source and run 'npx truenas-icons generate'.`);
|
|
1304
|
+
}
|
|
1269
1305
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1270
1306
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnIconComponent, isStandalone: true, selector: "tn-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null }, fullSize: { classPropertyName: "fullSize", publicName: "fullSize", isSignal: true, isRequired: false, transformFunction: null }, customSize: { classPropertyName: "customSize", publicName: "customSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()", "attr.library": "library()", "attr.size": "size()", "attr.color": "color()", "attr.full-size": "fullSize() || null", "attr.custom-size": "customSize() || null", "style.width": "hostDimension()", "style.height": "hostDimension()", "style.font-size": "hostFontSize()", "style.color": "color() || null" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult().source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult().spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult().content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult().content }}</span>\n }\n }\n</div>", styles: ["tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;color:var(--tn-icon-color, currentColor);width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));font-size:var(--tn-icon-size, var(--tn-icon-md))}tn-icon[size=xs]{width:var(--tn-icon-xs);height:var(--tn-icon-xs);font-size:var(--tn-icon-xs)}tn-icon[size=sm]{width:var(--tn-icon-sm);height:var(--tn-icon-sm);font-size:var(--tn-icon-sm)}tn-icon[size=md]{width:var(--tn-icon-md);height:var(--tn-icon-md);font-size:var(--tn-icon-md)}tn-icon[size=lg]{width:var(--tn-icon-lg);height:var(--tn-icon-lg);font-size:var(--tn-icon-lg)}tn-icon[size=xl]{width:var(--tn-icon-xl);height:var(--tn-icon-xl);font-size:var(--tn-icon-xl)}.tn-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1271
1307
|
}
|
|
@@ -4713,11 +4749,11 @@ class TnFormFieldComponent {
|
|
|
4713
4749
|
return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
|
|
4714
4750
|
}, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
|
|
4715
4751
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4716
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" [attr.data-testid]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div
|
|
4752
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFormFieldComponent, isStandalone: true, selector: "tn-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-form-field\" [attr.data-testid]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"] });
|
|
4717
4753
|
}
|
|
4718
4754
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
|
|
4719
4755
|
type: Component,
|
|
4720
|
-
args: [{ selector: 'tn-form-field', standalone: true, imports: [], template: "<div class=\"tn-form-field\" [attr.data-testid]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div
|
|
4756
|
+
args: [{ selector: 'tn-form-field', standalone: true, imports: [], template: "<div class=\"tn-form-field\" [attr.data-testid]=\"testId()\">\n <!-- Label -->\n @if (label()) {\n <label class=\"tn-form-field-label\" [class.required]=\"required()\">\n {{ label() }}\n @if (required()) {\n <span class=\"required-asterisk\" aria-label=\"required\">*</span>\n }\n </label>\n }\n\n <!-- Form Control Content -->\n <div class=\"tn-form-field-wrapper\">\n <ng-content />\n </div>\n\n <!-- Hint or Error Message -->\n @if (showSubscript()) {\n <div class=\"tn-form-field-subscript\" [class.tn-form-field-subscript-dynamic]=\"subscriptSizing() === 'dynamic'\">\n @if (showError()) {\n <div\n class=\"tn-form-field-error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorMessage() }}\n </div>\n }\n @if (showHint()) {\n <div class=\"tn-form-field-hint\">\n {{ hint() }}\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tn-form-field{display:block;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-form-field-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-form-field-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-form-field-wrapper{position:relative;width:100%;overflow:visible}.tn-form-field-wrapper :ng-deep .tn-select-container,.tn-form-field-wrapper :ng-deep .tn-input-container{margin-bottom:0}.tn-form-field-wrapper :ng-deep .tn-select-label,.tn-form-field-wrapper :ng-deep .tn-input-label{display:none}.tn-form-field-wrapper :ng-deep .tn-select-error,.tn-form-field-wrapper :ng-deep .tn-input-error{display:none}.tn-form-field-wrapper :ng-deep .tn-select-dropdown{z-index:1000}.tn-form-field-subscript{min-height:1.25rem;margin-top:.25rem;font-size:.75rem;line-height:1.4}.tn-form-field-subscript-dynamic{min-height:0}.tn-form-field-error{color:var(--tn-error, #dc3545);margin:0}.tn-form-field-hint{color:var(--tn-fg2, #6c757d);margin:0}.tn-form-field-wrapper:has(:focus-visible) .tn-form-field-label{color:var(--tn-primary, #007bff)}.tn-form-field-wrapper:has(.error) .tn-form-field-label{color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-form-field-label{transition:none}}@media(prefers-contrast:high){.tn-form-field-label,.tn-form-field-error{font-weight:600}}\n"] }]
|
|
4721
4757
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], subscriptSizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscriptSizing", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { isSignal: true }] }] } });
|
|
4722
4758
|
|
|
4723
4759
|
/**
|