@truenas/ui-components 0.1.45 → 0.1.47
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/fesm2022/truenas-ui-components.mjs +78 -4
- 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__/forwarding-components/multi-icon.component.html +7 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/multi-icon.component.ts +15 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/no-library.component.html +3 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/no-library.component.ts +11 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/not-forwarding.component.html +1 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/not-forwarding.component.ts +10 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/single-icon.component.html +3 -0
- package/scripts/icon-sprite/__fixtures__/forwarding-components/single-icon.component.ts +13 -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 +77 -0
- package/scripts/icon-sprite/lib/find-icons-in-forwarding-components.ts +209 -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 +70 -5
|
@@ -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
|
}
|
|
@@ -4645,6 +4681,7 @@ class TnFormFieldComponent {
|
|
|
4645
4681
|
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
4646
4682
|
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
4647
4683
|
testId = input('', ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
4684
|
+
subscriptSizing = input('dynamic', ...(ngDevMode ? [{ debugName: "subscriptSizing" }] : []));
|
|
4648
4685
|
control = contentChild(NgControl, ...(ngDevMode ? [{ debugName: "control" }] : []));
|
|
4649
4686
|
hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
|
|
4650
4687
|
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
@@ -4708,13 +4745,16 @@ class TnFormFieldComponent {
|
|
|
4708
4745
|
showHint = computed(() => {
|
|
4709
4746
|
return !!this.hint() && !this.showError();
|
|
4710
4747
|
}, ...(ngDevMode ? [{ debugName: "showHint" }] : []));
|
|
4748
|
+
showSubscript = computed(() => {
|
|
4749
|
+
return this.subscriptSizing() === 'fixed' || this.showError() || this.showHint();
|
|
4750
|
+
}, ...(ngDevMode ? [{ debugName: "showSubscript" }] : []));
|
|
4711
4751
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4712
|
-
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 } }, 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 <div class=\"tn-form-field-subscript\">\n
|
|
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"] });
|
|
4713
4753
|
}
|
|
4714
4754
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFormFieldComponent, decorators: [{
|
|
4715
4755
|
type: Component,
|
|
4716
|
-
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 <div class=\"tn-form-field-subscript\">\n
|
|
4717
|
-
}], 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 }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { isSignal: true }] }] } });
|
|
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"] }]
|
|
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 }] }] } });
|
|
4718
4758
|
|
|
4719
4759
|
/**
|
|
4720
4760
|
* Harness for interacting with `tn-form-field` in tests.
|
|
@@ -4871,6 +4911,40 @@ class TnFormFieldHarness extends ComponentHarness {
|
|
|
4871
4911
|
const root = await this.locatorFor('.tn-form-field')();
|
|
4872
4912
|
return root.getAttribute('data-testid');
|
|
4873
4913
|
}
|
|
4914
|
+
/**
|
|
4915
|
+
* Checks whether the subscript wrapper is currently rendered.
|
|
4916
|
+
*
|
|
4917
|
+
* @returns Promise resolving to true if the subscript area is present in the DOM.
|
|
4918
|
+
*
|
|
4919
|
+
* @example
|
|
4920
|
+
* ```typescript
|
|
4921
|
+
* const field = await loader.getHarness(TnFormFieldHarness.with({ label: 'Name' }));
|
|
4922
|
+
* expect(await field.hasSubscript()).toBe(false);
|
|
4923
|
+
* ```
|
|
4924
|
+
*/
|
|
4925
|
+
async hasSubscript() {
|
|
4926
|
+
const subscript = await this.locatorForOptional('.tn-form-field-subscript')();
|
|
4927
|
+
return subscript !== null;
|
|
4928
|
+
}
|
|
4929
|
+
/**
|
|
4930
|
+
* Gets the current subscript sizing mode.
|
|
4931
|
+
*
|
|
4932
|
+
* @returns Promise resolving to `'fixed'` or `'dynamic'`.
|
|
4933
|
+
*
|
|
4934
|
+
* @example
|
|
4935
|
+
* ```typescript
|
|
4936
|
+
* const field = await loader.getHarness(TnFormFieldHarness.with({ label: 'Name' }));
|
|
4937
|
+
* expect(await field.getSubscriptSizing()).toBe('dynamic');
|
|
4938
|
+
* ```
|
|
4939
|
+
*/
|
|
4940
|
+
async getSubscriptSizing() {
|
|
4941
|
+
const subscript = await this.locatorForOptional('.tn-form-field-subscript')();
|
|
4942
|
+
if (!subscript) {
|
|
4943
|
+
return 'dynamic';
|
|
4944
|
+
}
|
|
4945
|
+
const isDynamic = await subscript.hasClass('tn-form-field-subscript-dynamic');
|
|
4946
|
+
return isDynamic ? 'dynamic' : 'fixed';
|
|
4947
|
+
}
|
|
4874
4948
|
}
|
|
4875
4949
|
|
|
4876
4950
|
class TnSelectComponent {
|