@truenas/ui-components 0.1.13 → 0.1.15

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.
@@ -1119,6 +1119,15 @@ class TnInputComponent {
1119
1119
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1120
1120
  multiline = input(false, ...(ngDevMode ? [{ debugName: "multiline" }] : []));
1121
1121
  rows = input(3, ...(ngDevMode ? [{ debugName: "rows" }] : []));
1122
+ // Icon inputs
1123
+ prefixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIcon" }] : []));
1124
+ prefixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIconLibrary" }] : []));
1125
+ suffixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIcon" }] : []));
1126
+ suffixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconLibrary" }] : []));
1127
+ suffixIconAriaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "suffixIconAriaLabel" }] : []));
1128
+ onSuffixAction = output();
1129
+ hasPrefixIcon = computed(() => !!this.prefixIcon(), ...(ngDevMode ? [{ debugName: "hasPrefixIcon" }] : []));
1130
+ hasSuffixIcon = computed(() => !!this.suffixIcon(), ...(ngDevMode ? [{ debugName: "hasSuffixIcon" }] : []));
1122
1131
  id = 'tn-input';
1123
1132
  value = '';
1124
1133
  // CVA disabled state management
@@ -1153,24 +1162,425 @@ class TnInputComponent {
1153
1162
  this.onTouched();
1154
1163
  }
1155
1164
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1156
- 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 } }, providers: [
1165
+ 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 }, 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: [
1157
1166
  {
1158
1167
  provide: NG_VALUE_ACCESSOR,
1159
1168
  useExisting: forwardRef(() => TnInputComponent),
1160
1169
  multi: true
1161
1170
  }
1162
- ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-input-container\">\n <!-- Regular input field -->\n @if (!multiline()) {\n <input\n #inputEl\n class=\"tn-input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"inputType()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (multiline()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n</div>\n", styles: [".tn-input-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);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;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-input.error{border-color:var(--tn-error, #dc3545)}.tn-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input{transition:none}}@media(prefers-contrast:high){.tn-input{border-width:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }] });
1171
+ ], 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 (!multiline()) {\n <input\n #inputEl\n class=\"tn-input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"inputType()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (multiline()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"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", "fullSize", "customSize"] }] });
1163
1172
  }
1164
1173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, decorators: [{
1165
1174
  type: Component,
1166
- args: [{ selector: 'tn-input', standalone: true, imports: [FormsModule, A11yModule], providers: [
1175
+ args: [{ selector: 'tn-input', standalone: true, imports: [FormsModule, A11yModule, TnIconComponent], providers: [
1167
1176
  {
1168
1177
  provide: NG_VALUE_ACCESSOR,
1169
1178
  useExisting: forwardRef(() => TnInputComponent),
1170
1179
  multi: true
1171
1180
  }
1172
- ], template: "<div class=\"tn-input-container\">\n <!-- Regular input field -->\n @if (!multiline()) {\n <input\n #inputEl\n class=\"tn-input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"inputType()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (multiline()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n</div>\n", styles: [".tn-input-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-input{display:block;width:100%;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);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;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:focus{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input:disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-input.error{border-color:var(--tn-error, #dc3545)}.tn-input.error:focus{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input{transition:none}}@media(prefers-contrast:high){.tn-input{border-width:2px}}\n"] }]
1173
- }], 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 }] }] } });
1181
+ ], 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 (!multiline()) {\n <input\n #inputEl\n class=\"tn-input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"inputType()\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"testId()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (multiline()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.placeholder]=\"placeholder()\"\n [attr.data-testid]=\"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"] }]
1182
+ }], 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 }] }], 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"] }] } });
1183
+
1184
+ /**
1185
+ * Harness for interacting with tn-icon in tests.
1186
+ * Provides filtering by icon name and library for existence checks, as well as click interaction.
1187
+ *
1188
+ * @example
1189
+ * ```typescript
1190
+ * // Check for existence
1191
+ * const icon = await loader.getHarness(TnIconHarness);
1192
+ *
1193
+ * // Find icon by name
1194
+ * const folderIcon = await loader.getHarness(
1195
+ * TnIconHarness.with({ name: 'folder' })
1196
+ * );
1197
+ *
1198
+ * // Find icon by name and library
1199
+ * const mdiIcon = await loader.getHarness(
1200
+ * TnIconHarness.with({ name: 'account-circle', library: 'mdi' })
1201
+ * );
1202
+ *
1203
+ * // Check if icon exists
1204
+ * const hasIcon = await loader.hasHarness(
1205
+ * TnIconHarness.with({ name: 'check' })
1206
+ * );
1207
+ *
1208
+ * // Click an icon
1209
+ * const closeIcon = await loader.getHarness(
1210
+ * TnIconHarness.with({ name: 'close' })
1211
+ * );
1212
+ * await closeIcon.click();
1213
+ * ```
1214
+ */
1215
+ class TnIconHarness extends ComponentHarness {
1216
+ /**
1217
+ * The selector for the host element of a `TnIconComponent` instance.
1218
+ */
1219
+ static hostSelector = 'tn-icon';
1220
+ /**
1221
+ * Gets a `HarnessPredicate` that can be used to search for an icon
1222
+ * with specific attributes.
1223
+ *
1224
+ * @param options Options for filtering which icon instances are considered a match.
1225
+ * @returns A `HarnessPredicate` configured with the given options.
1226
+ *
1227
+ * @example
1228
+ * ```typescript
1229
+ * // Find icon by name
1230
+ * const icon = await loader.getHarness(
1231
+ * TnIconHarness.with({ name: 'home' })
1232
+ * );
1233
+ *
1234
+ * // Find icon by library
1235
+ * const customIcon = await loader.getHarness(
1236
+ * TnIconHarness.with({ library: 'custom' })
1237
+ * );
1238
+ *
1239
+ * // Find icon with specific size
1240
+ * const largeIcon = await loader.getHarness(
1241
+ * TnIconHarness.with({ size: 'lg' })
1242
+ * );
1243
+ * ```
1244
+ */
1245
+ static with(options = {}) {
1246
+ return new HarnessPredicate(TnIconHarness, options)
1247
+ .addOption('name', options.name, async (harness, name) => {
1248
+ return (await harness.getName()) === name;
1249
+ })
1250
+ .addOption('library', options.library, async (harness, library) => {
1251
+ return (await harness.getLibrary()) === library;
1252
+ })
1253
+ .addOption('size', options.size, async (harness, size) => {
1254
+ return (await harness.getSize()) === size;
1255
+ })
1256
+ .addOption('fullSize', options.fullSize, async (harness, fullSize) => {
1257
+ return (await harness.isFullSize()) === fullSize;
1258
+ })
1259
+ .addOption('customSize', options.customSize, async (harness, customSize) => {
1260
+ return (await harness.getCustomSize()) === customSize;
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Gets the icon name.
1265
+ *
1266
+ * @returns Promise resolving to the icon name.
1267
+ *
1268
+ * @example
1269
+ * ```typescript
1270
+ * const icon = await loader.getHarness(TnIconHarness);
1271
+ * const name = await icon.getName();
1272
+ * expect(name).toBe('folder');
1273
+ * ```
1274
+ */
1275
+ async getName() {
1276
+ const host = await this.host();
1277
+ return host.getAttribute('name');
1278
+ }
1279
+ /**
1280
+ * Gets the icon library.
1281
+ *
1282
+ * @returns Promise resolving to the icon library.
1283
+ *
1284
+ * @example
1285
+ * ```typescript
1286
+ * const icon = await loader.getHarness(TnIconHarness);
1287
+ * const library = await icon.getLibrary();
1288
+ * expect(library).toBe('mdi');
1289
+ * ```
1290
+ */
1291
+ async getLibrary() {
1292
+ const host = await this.host();
1293
+ return host.getAttribute('library');
1294
+ }
1295
+ /**
1296
+ * Gets the icon size.
1297
+ *
1298
+ * @returns Promise resolving to the icon size.
1299
+ *
1300
+ * @example
1301
+ * ```typescript
1302
+ * const icon = await loader.getHarness(TnIconHarness);
1303
+ * const size = await icon.getSize();
1304
+ * expect(size).toBe('lg');
1305
+ * ```
1306
+ */
1307
+ async getSize() {
1308
+ const host = await this.host();
1309
+ return host.getAttribute('size');
1310
+ }
1311
+ /**
1312
+ * Gets the icon color.
1313
+ *
1314
+ * @returns Promise resolving to the icon color.
1315
+ *
1316
+ * @example
1317
+ * ```typescript
1318
+ * const icon = await loader.getHarness(TnIconHarness);
1319
+ * const color = await icon.getColor();
1320
+ * expect(color).toBe('primary');
1321
+ * ```
1322
+ */
1323
+ async getColor() {
1324
+ const host = await this.host();
1325
+ return host.getAttribute('color');
1326
+ }
1327
+ /**
1328
+ * Checks if the icon is in full-size mode.
1329
+ *
1330
+ * @returns Promise resolving to true if the icon is full-size, false otherwise.
1331
+ *
1332
+ * @example
1333
+ * ```typescript
1334
+ * const icon = await loader.getHarness(TnIconHarness);
1335
+ * const isFullSize = await icon.isFullSize();
1336
+ * expect(isFullSize).toBe(true);
1337
+ * ```
1338
+ */
1339
+ async isFullSize() {
1340
+ const host = await this.host();
1341
+ const fullSizeAttr = await host.getAttribute('full-size');
1342
+ return fullSizeAttr === 'true';
1343
+ }
1344
+ /**
1345
+ * Gets the icon custom size.
1346
+ *
1347
+ * @returns Promise resolving to the custom size value, or null if not set.
1348
+ *
1349
+ * @example
1350
+ * ```typescript
1351
+ * const icon = await loader.getHarness(TnIconHarness);
1352
+ * const customSize = await icon.getCustomSize();
1353
+ * expect(customSize).toBe('64px');
1354
+ * ```
1355
+ */
1356
+ async getCustomSize() {
1357
+ const host = await this.host();
1358
+ return host.getAttribute('custom-size');
1359
+ }
1360
+ /**
1361
+ * Checks if the icon is using a custom size.
1362
+ *
1363
+ * @returns Promise resolving to true if a custom size is set, false otherwise.
1364
+ *
1365
+ * @example
1366
+ * ```typescript
1367
+ * const icon = await loader.getHarness(TnIconHarness);
1368
+ * const hasCustomSize = await icon.hasCustomSize();
1369
+ * expect(hasCustomSize).toBe(true);
1370
+ * ```
1371
+ */
1372
+ async hasCustomSize() {
1373
+ const customSize = await this.getCustomSize();
1374
+ return customSize !== null;
1375
+ }
1376
+ /**
1377
+ * Clicks the icon.
1378
+ *
1379
+ * @returns Promise that resolves when the click action is complete.
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * const icon = await loader.getHarness(TnIconHarness.with({ name: 'close' }));
1384
+ * await icon.click();
1385
+ * ```
1386
+ */
1387
+ async click() {
1388
+ const host = await this.host();
1389
+ return host.click();
1390
+ }
1391
+ }
1392
+
1393
+ /**
1394
+ * Harness for interacting with tn-input in tests.
1395
+ * Provides methods for querying state, setting values, and interacting with prefix/suffix icons.
1396
+ *
1397
+ * @example
1398
+ * ```typescript
1399
+ * // Get the input harness
1400
+ * const input = await loader.getHarness(TnInputHarness);
1401
+ *
1402
+ * // Set and read a value
1403
+ * await input.setValue('hello');
1404
+ * expect(await input.getValue()).toBe('hello');
1405
+ *
1406
+ * // Check for prefix icon
1407
+ * expect(await input.hasPrefixIcon()).toBe(true);
1408
+ *
1409
+ * // Click suffix action
1410
+ * await input.clickSuffixAction();
1411
+ * ```
1412
+ */
1413
+ class TnInputHarness extends ComponentHarness {
1414
+ /**
1415
+ * The selector for the host element of a `TnInputComponent` instance.
1416
+ */
1417
+ static hostSelector = 'tn-input';
1418
+ inputEl = this.locatorFor('input.tn-input');
1419
+ textareaEl = this.locatorFor('textarea.tn-input');
1420
+ prefixIconEl = this.locatorForOptional('tn-icon.tn-input__prefix-icon');
1421
+ suffixButton = this.locatorForOptional('.tn-input__suffix-action');
1422
+ suffixIconEl = this.locatorForOptional(TnIconHarness.with({ ancestor: '.tn-input__suffix-action' }));
1423
+ /**
1424
+ * Gets a `HarnessPredicate` that can be used to search for an input
1425
+ * with specific attributes.
1426
+ *
1427
+ * @param options Options for filtering which input instances are considered a match.
1428
+ * @returns A `HarnessPredicate` configured with the given options.
1429
+ */
1430
+ static with(options = {}) {
1431
+ return new HarnessPredicate(TnInputHarness, options)
1432
+ .addOption('placeholder', options.placeholder, async (harness, placeholder) => {
1433
+ return (await harness.getPlaceholder()) === placeholder;
1434
+ });
1435
+ }
1436
+ /**
1437
+ * Gets the current value of the input.
1438
+ *
1439
+ * @returns Promise resolving to the input value.
1440
+ */
1441
+ async getValue() {
1442
+ if (await this.isMultiline()) {
1443
+ const textarea = await this.textareaEl();
1444
+ return (await textarea.getProperty('value')) ?? '';
1445
+ }
1446
+ const input = await this.inputEl();
1447
+ return (await input.getProperty('value')) ?? '';
1448
+ }
1449
+ /**
1450
+ * Sets the value of the input by clearing and typing.
1451
+ *
1452
+ * @param value The value to set.
1453
+ */
1454
+ async setValue(value) {
1455
+ if (await this.isMultiline()) {
1456
+ const textarea = await this.textareaEl();
1457
+ await textarea.clear();
1458
+ await textarea.sendKeys(value);
1459
+ return;
1460
+ }
1461
+ const input = await this.inputEl();
1462
+ await input.clear();
1463
+ await input.sendKeys(value);
1464
+ }
1465
+ /**
1466
+ * Gets the placeholder text.
1467
+ *
1468
+ * @returns Promise resolving to the placeholder string.
1469
+ */
1470
+ async getPlaceholder() {
1471
+ if (await this.isMultiline()) {
1472
+ const textarea = await this.textareaEl();
1473
+ return textarea.getAttribute('placeholder');
1474
+ }
1475
+ const input = await this.inputEl();
1476
+ return input.getAttribute('placeholder');
1477
+ }
1478
+ /**
1479
+ * Checks whether the input is disabled.
1480
+ *
1481
+ * @returns Promise resolving to true if the input is disabled.
1482
+ */
1483
+ async isDisabled() {
1484
+ if (await this.isMultiline()) {
1485
+ const textarea = await this.textareaEl();
1486
+ return (await textarea.getProperty('disabled')) ?? false;
1487
+ }
1488
+ const input = await this.inputEl();
1489
+ return (await input.getProperty('disabled')) ?? false;
1490
+ }
1491
+ /**
1492
+ * Checks whether the input renders as a textarea.
1493
+ *
1494
+ * @returns Promise resolving to true if multiline.
1495
+ */
1496
+ async isMultiline() {
1497
+ const textareas = await this.locatorForAll('textarea.tn-input')();
1498
+ return textareas.length > 0;
1499
+ }
1500
+ /**
1501
+ * Checks whether a prefix icon is present.
1502
+ *
1503
+ * @returns Promise resolving to true if a prefix icon exists.
1504
+ */
1505
+ async hasPrefixIcon() {
1506
+ const icon = await this.prefixIconEl();
1507
+ return icon !== null;
1508
+ }
1509
+ /**
1510
+ * Gets the prefix icon harness for further inspection.
1511
+ *
1512
+ * @returns Promise resolving to the prefix TnIconHarness, or null if not present.
1513
+ */
1514
+ async getPrefixIcon() {
1515
+ const el = await this.prefixIconEl();
1516
+ if (!el) {
1517
+ return null;
1518
+ }
1519
+ const allIcons = await this.locatorForAll(TnIconHarness)();
1520
+ for (const icon of allIcons) {
1521
+ const host = await icon.host();
1522
+ if (await host.hasClass('tn-input__prefix-icon')) {
1523
+ return icon;
1524
+ }
1525
+ }
1526
+ return null;
1527
+ }
1528
+ /**
1529
+ * Checks whether a suffix action button is present.
1530
+ *
1531
+ * @returns Promise resolving to true if a suffix action exists.
1532
+ */
1533
+ async hasSuffixAction() {
1534
+ const button = await this.suffixButton();
1535
+ return button !== null;
1536
+ }
1537
+ /**
1538
+ * Gets the suffix icon harness for further inspection.
1539
+ *
1540
+ * @returns Promise resolving to the suffix TnIconHarness, or null if not present.
1541
+ */
1542
+ async getSuffixIcon() {
1543
+ return this.suffixIconEl();
1544
+ }
1545
+ /**
1546
+ * Clicks the suffix action button.
1547
+ *
1548
+ * @returns Promise that resolves when the click action is complete.
1549
+ */
1550
+ async clickSuffixAction() {
1551
+ const button = await this.suffixButton();
1552
+ if (!button) {
1553
+ throw new Error('No suffix action button found on this input.');
1554
+ }
1555
+ return button.click();
1556
+ }
1557
+ /**
1558
+ * Focuses the input element.
1559
+ *
1560
+ * @returns Promise that resolves when the input is focused.
1561
+ */
1562
+ async focus() {
1563
+ if (await this.isMultiline()) {
1564
+ const textarea = await this.textareaEl();
1565
+ return textarea.focus();
1566
+ }
1567
+ const input = await this.inputEl();
1568
+ return input.focus();
1569
+ }
1570
+ /**
1571
+ * Blurs the input element.
1572
+ *
1573
+ * @returns Promise that resolves when the input is blurred.
1574
+ */
1575
+ async blur() {
1576
+ if (await this.isMultiline()) {
1577
+ const textarea = await this.textareaEl();
1578
+ return textarea.blur();
1579
+ }
1580
+ const input = await this.inputEl();
1581
+ return input.blur();
1582
+ }
1583
+ }
1174
1584
 
1175
1585
  class TnInputDirective {
1176
1586
  constructor() { }
@@ -1896,15 +2306,15 @@ class TnTabComponent {
1896
2306
  // Internal properties set by parent TnTabsComponent (public signals for parent control)
1897
2307
  index = signal(0, ...(ngDevMode ? [{ debugName: "index" }] : []));
1898
2308
  isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1899
- tabsComponent; // Will be set by parent
2309
+ tabsComponent;
1900
2310
  elementRef = inject((ElementRef));
1901
2311
  hasIconContent = signal(false, ...(ngDevMode ? [{ debugName: "hasIconContent" }] : []));
1902
2312
  ngAfterContentInit() {
1903
2313
  this.hasIconContent.set(!!this.iconContent());
1904
2314
  }
1905
2315
  onClick() {
1906
- if (!this.disabled()) {
1907
- this.selected.emit();
2316
+ if (!this.disabled() && this.tabsComponent) {
2317
+ this.tabsComponent.selectTab(this.index());
1908
2318
  }
1909
2319
  }
1910
2320
  onKeydown(event) {
@@ -2017,10 +2427,6 @@ class TnTabsComponent {
2017
2427
  }
2018
2428
  this.cdr.detectChanges();
2019
2429
  });
2020
- }
2021
- ngAfterContentInit() {
2022
- this.initializeTabs();
2023
- this.selectTab(this.internalSelectedIndex());
2024
2430
  // Listen for tab changes
2025
2431
  effect(() => {
2026
2432
  // Track tabs signal to react to changes
@@ -2031,6 +2437,10 @@ class TnTabsComponent {
2031
2437
  }
2032
2438
  });
2033
2439
  }
2440
+ ngAfterContentInit() {
2441
+ this.initializeTabs();
2442
+ this.selectTab(this.internalSelectedIndex());
2443
+ }
2034
2444
  ngAfterViewInit() {
2035
2445
  // Wait for next tick to ensure DOM is fully rendered
2036
2446
  setTimeout(() => {
@@ -2056,12 +2466,6 @@ class TnTabsComponent {
2056
2466
  if (tab.elementRef) {
2057
2467
  this.focusMonitor.monitor(tab.elementRef);
2058
2468
  }
2059
- // Set up click handlers
2060
- tab.selected.subscribe(() => {
2061
- if (!tab.disabled()) {
2062
- this.selectTab(index);
2063
- }
2064
- });
2065
2469
  });
2066
2470
  this.panels().forEach((panel, index) => {
2067
2471
  panel.index.set(index);
@@ -2221,40 +2625,443 @@ class TnTabsComponent {
2221
2625
  }
2222
2626
  return 0;
2223
2627
  }
2224
- getLastEnabledTabIndex() {
2225
- const tabs = this.tabs();
2226
- for (let i = tabs.length - 1; i >= 0; i--) {
2227
- if (!tabs[i]?.disabled()) {
2228
- return i;
2229
- }
2230
- }
2231
- return tabs.length - 1;
2628
+ getLastEnabledTabIndex() {
2629
+ const tabs = this.tabs();
2630
+ for (let i = tabs.length - 1; i >= 0; i--) {
2631
+ if (!tabs[i]?.disabled()) {
2632
+ return i;
2633
+ }
2634
+ }
2635
+ return tabs.length - 1;
2636
+ }
2637
+ focusTab(index) {
2638
+ const tab = this.tabs()[index];
2639
+ if (tab && tab.elementRef) {
2640
+ tab.elementRef.nativeElement.focus();
2641
+ }
2642
+ }
2643
+ classes = computed(() => {
2644
+ const classes = ['tn-tabs'];
2645
+ if (this.orientation() === 'vertical') {
2646
+ classes.push('tn-tabs--vertical');
2647
+ }
2648
+ else {
2649
+ classes.push('tn-tabs--horizontal');
2650
+ }
2651
+ // Add highlight position class
2652
+ classes.push(`tn-tabs--highlight-${this.highlightPosition()}`);
2653
+ return classes.join(' ');
2654
+ }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
2655
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2656
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTabsComponent, isStandalone: true, selector: "tn-tabs", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, highlightPosition: { classPropertyName: "highlightPosition", publicName: "highlightPosition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndexChange: "selectedIndexChange", tabChange: "tabChange" }, queries: [{ propertyName: "tabs", predicate: TnTabComponent, isSignal: true }, { propertyName: "panels", predicate: TnTabPanelComponent, isSignal: true }], viewQueries: [{ propertyName: "tabHeader", first: true, predicate: ["tabHeader"], descendants: true, isSignal: true }], ngImport: i0, template: "<div role=\"tablist\" [ngClass]=\"classes()\" [attr.aria-orientation]=\"orientation()\">\n <div #tabHeader class=\"tn-tabs__header\">\n <ng-content select=\"tn-tab\" />\n @if (highlightBarVisible()) {\n <div class=\"tn-tabs__highlight-bar\"\n [style.left.px]=\"highlightBarLeft()\"\n [style.width.px]=\"highlightBarWidth()\"\n [style.top.px]=\"highlightBarTop()\"\n [style.height.px]=\"highlightBarHeight()\">\n </div>\n }\n </div>\n\n <div class=\"tn-tabs__content\">\n <ng-content select=\"tn-tab-panel\" />\n </div>\n</div>", styles: [".tn-tabs{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;font-family:var(--tn-font-family-body, \"Inter\", sans-serif)}.tn-tabs--disabled{opacity:.6;pointer-events:none}.tn-tabs--vertical{flex-direction:row}.tn-tabs--vertical .tn-tabs__header{flex-direction:column;border-bottom:none;border-right:1px solid var(--tn-lines, #e0e0e0);min-width:240px;width:auto}.tn-tabs--vertical .tn-tabs__content{flex:1;min-width:0}.tn-tabs--vertical .tn-tabs__highlight-bar{bottom:auto;height:auto}.tn-tabs--vertical .tn-tab{justify-content:flex-start;text-align:left;width:100%}.tn-tabs--vertical .tn-tab:hover:not(.tn-tab--disabled){background-color:var(--tn-alt-bg1, #f5f5f5);color:var(--tn-fg1, #333)}.tn-tabs__header{display:flex;background-color:var(--tn-bg1, #fff);position:relative;border-bottom:1px solid var(--tn-lines, #e0e0e0)}.tn-tabs__highlight-bar{position:absolute;bottom:-1px;height:2px;background-color:var(--tn-primary, #0095d5);transition:left .3s ease,width .3s ease,top .3s ease,height .3s ease;z-index:1}.tn-tabs__content{flex:1;position:relative;background-color:var(--tn-bg1, #fff);min-height:0;width:100%;overflow:hidden}@media(max-width:768px){.tn-tabs__header{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-tabs__header::-webkit-scrollbar{display:none}.tn-tabs--vertical .tn-tabs__header{overflow-y:auto;overflow-x:visible;max-height:300px}}@media(prefers-reduced-motion:reduce){.tn-tabs__highlight-bar{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2657
+ }
2658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTabsComponent, decorators: [{
2659
+ type: Component,
2660
+ args: [{ selector: 'tn-tabs', standalone: true, imports: [CommonModule, A11yModule, TnTabComponent, TnTabPanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div role=\"tablist\" [ngClass]=\"classes()\" [attr.aria-orientation]=\"orientation()\">\n <div #tabHeader class=\"tn-tabs__header\">\n <ng-content select=\"tn-tab\" />\n @if (highlightBarVisible()) {\n <div class=\"tn-tabs__highlight-bar\"\n [style.left.px]=\"highlightBarLeft()\"\n [style.width.px]=\"highlightBarWidth()\"\n [style.top.px]=\"highlightBarTop()\"\n [style.height.px]=\"highlightBarHeight()\">\n </div>\n }\n </div>\n\n <div class=\"tn-tabs__content\">\n <ng-content select=\"tn-tab-panel\" />\n </div>\n</div>", styles: [".tn-tabs{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;font-family:var(--tn-font-family-body, \"Inter\", sans-serif)}.tn-tabs--disabled{opacity:.6;pointer-events:none}.tn-tabs--vertical{flex-direction:row}.tn-tabs--vertical .tn-tabs__header{flex-direction:column;border-bottom:none;border-right:1px solid var(--tn-lines, #e0e0e0);min-width:240px;width:auto}.tn-tabs--vertical .tn-tabs__content{flex:1;min-width:0}.tn-tabs--vertical .tn-tabs__highlight-bar{bottom:auto;height:auto}.tn-tabs--vertical .tn-tab{justify-content:flex-start;text-align:left;width:100%}.tn-tabs--vertical .tn-tab:hover:not(.tn-tab--disabled){background-color:var(--tn-alt-bg1, #f5f5f5);color:var(--tn-fg1, #333)}.tn-tabs__header{display:flex;background-color:var(--tn-bg1, #fff);position:relative;border-bottom:1px solid var(--tn-lines, #e0e0e0)}.tn-tabs__highlight-bar{position:absolute;bottom:-1px;height:2px;background-color:var(--tn-primary, #0095d5);transition:left .3s ease,width .3s ease,top .3s ease,height .3s ease;z-index:1}.tn-tabs__content{flex:1;position:relative;background-color:var(--tn-bg1, #fff);min-height:0;width:100%;overflow:hidden}@media(max-width:768px){.tn-tabs__header{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-tabs__header::-webkit-scrollbar{display:none}.tn-tabs--vertical .tn-tabs__header{overflow-y:auto;overflow-x:visible;max-height:300px}}@media(prefers-reduced-motion:reduce){.tn-tabs__highlight-bar{transition:none}}\n"] }]
2661
+ }], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTabComponent), { isSignal: true }] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTabPanelComponent), { isSignal: true }] }], tabHeader: [{ type: i0.ViewChild, args: ['tabHeader', { isSignal: true }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], highlightPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightPosition", required: false }] }], selectedIndexChange: [{ type: i0.Output, args: ["selectedIndexChange"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }] } });
2662
+
2663
+ /**
2664
+ * Harness for interacting with tn-tab in tests.
2665
+ * Provides methods for querying individual tab state and simulating selection.
2666
+ *
2667
+ * @example
2668
+ * ```typescript
2669
+ * // Find a tab by label
2670
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: 'Settings' }));
2671
+ *
2672
+ * // Check if active
2673
+ * expect(await tab.isSelected()).toBe(false);
2674
+ *
2675
+ * // Select it
2676
+ * await tab.select();
2677
+ * expect(await tab.isSelected()).toBe(true);
2678
+ * ```
2679
+ */
2680
+ class TnTabHarness extends ComponentHarness {
2681
+ /**
2682
+ * The selector for the host element of a `TnTabComponent` instance.
2683
+ */
2684
+ static hostSelector = 'tn-tab';
2685
+ _button = this.locatorFor('button[role="tab"]');
2686
+ /**
2687
+ * Gets a `HarnessPredicate` that can be used to search for a tab
2688
+ * with specific attributes.
2689
+ *
2690
+ * @param options Options for filtering which tab instances are considered a match.
2691
+ * @returns A `HarnessPredicate` configured with the given options.
2692
+ *
2693
+ * @example
2694
+ * ```typescript
2695
+ * // Find tab by exact label
2696
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: 'Overview' }));
2697
+ *
2698
+ * // Find tab by regex
2699
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: /settings/i }));
2700
+ * ```
2701
+ */
2702
+ static with(options = {}) {
2703
+ return new HarnessPredicate(TnTabHarness, options)
2704
+ .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
2705
+ }
2706
+ /**
2707
+ * Gets the tab's label text.
2708
+ *
2709
+ * @returns Promise resolving to the tab's label string.
2710
+ *
2711
+ * @example
2712
+ * ```typescript
2713
+ * const tab = await loader.getHarness(TnTabHarness);
2714
+ * expect(await tab.getLabel()).toBe('Overview');
2715
+ * ```
2716
+ */
2717
+ async getLabel() {
2718
+ const labelEl = await this.locatorFor('.tn-tab__label')();
2719
+ return (await labelEl.text()).trim();
2720
+ }
2721
+ /**
2722
+ * Checks whether the tab is currently selected (active).
2723
+ *
2724
+ * @returns Promise resolving to true if the tab is selected.
2725
+ *
2726
+ * @example
2727
+ * ```typescript
2728
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: 'Overview' }));
2729
+ * expect(await tab.isSelected()).toBe(true);
2730
+ * ```
2731
+ */
2732
+ async isSelected() {
2733
+ const button = await this._button();
2734
+ return (await button.getAttribute('aria-selected')) === 'true';
2735
+ }
2736
+ /**
2737
+ * Checks whether the tab is disabled.
2738
+ *
2739
+ * @returns Promise resolving to true if the tab is disabled.
2740
+ *
2741
+ * @example
2742
+ * ```typescript
2743
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: 'Disabled Tab' }));
2744
+ * expect(await tab.isDisabled()).toBe(true);
2745
+ * ```
2746
+ */
2747
+ async isDisabled() {
2748
+ const button = await this._button();
2749
+ return (await button.getProperty('disabled')) ?? false;
2750
+ }
2751
+ /**
2752
+ * Selects the tab by clicking it.
2753
+ *
2754
+ * @returns Promise that resolves when the tab has been clicked.
2755
+ *
2756
+ * @example
2757
+ * ```typescript
2758
+ * const tab = await loader.getHarness(TnTabHarness.with({ label: 'Settings' }));
2759
+ * await tab.select();
2760
+ * ```
2761
+ */
2762
+ async select() {
2763
+ const button = await this._button();
2764
+ await button.click();
2765
+ }
2766
+ /**
2767
+ * Gets the tab's testId attribute value.
2768
+ *
2769
+ * @returns Promise resolving to the data-testid value, or null if not set.
2770
+ *
2771
+ * @example
2772
+ * ```typescript
2773
+ * const tab = await loader.getHarness(TnTabHarness);
2774
+ * expect(await tab.getTestId()).toBe('my-tab');
2775
+ * ```
2776
+ */
2777
+ async getTestId() {
2778
+ const button = await this._button();
2779
+ return button.getAttribute('data-testid');
2780
+ }
2781
+ }
2782
+
2783
+ /**
2784
+ * Harness for interacting with tn-tab-panel in tests.
2785
+ * Provides methods for querying panel visibility and content.
2786
+ *
2787
+ * @example
2788
+ * ```typescript
2789
+ * // Get all panels
2790
+ * const panels = await loader.getAllHarnesses(TnTabPanelHarness);
2791
+ *
2792
+ * // Check which panel is active
2793
+ * for (const panel of panels) {
2794
+ * if (await panel.isActive()) {
2795
+ * const text = await panel.getTextContent();
2796
+ * expect(text).toContain('Overview');
2797
+ * }
2798
+ * }
2799
+ * ```
2800
+ */
2801
+ class TnTabPanelHarness extends ComponentHarness {
2802
+ /**
2803
+ * The selector for the host element of a `TnTabPanelComponent` instance.
2804
+ */
2805
+ static hostSelector = 'tn-tab-panel';
2806
+ _panel = this.locatorFor('[role="tabpanel"]');
2807
+ /**
2808
+ * Gets a `HarnessPredicate` that can be used to search for a tab panel
2809
+ * with specific attributes.
2810
+ *
2811
+ * @param options Options for filtering which panel instances are considered a match.
2812
+ * @returns A `HarnessPredicate` configured with the given options.
2813
+ *
2814
+ * @example
2815
+ * ```typescript
2816
+ * const panel = await loader.getHarness(
2817
+ * TnTabPanelHarness.with({ textContains: 'Overview' })
2818
+ * );
2819
+ * ```
2820
+ */
2821
+ static with(options = {}) {
2822
+ return new HarnessPredicate(TnTabPanelHarness, options)
2823
+ .addOption('textContains', options.textContains, (harness, text) => HarnessPredicate.stringMatches(harness.getTextContent(), text));
2824
+ }
2825
+ /**
2826
+ * Checks whether the panel is currently active (visible).
2827
+ *
2828
+ * @returns Promise resolving to true if the panel is active.
2829
+ *
2830
+ * @example
2831
+ * ```typescript
2832
+ * const panel = await loader.getHarness(TnTabPanelHarness);
2833
+ * expect(await panel.isActive()).toBe(true);
2834
+ * ```
2835
+ */
2836
+ async isActive() {
2837
+ const panel = await this._panel();
2838
+ return (await panel.getAttribute('aria-hidden')) === 'false';
2839
+ }
2840
+ /**
2841
+ * Gets the text content of the panel.
2842
+ *
2843
+ * @returns Promise resolving to the panel's text content.
2844
+ *
2845
+ * @example
2846
+ * ```typescript
2847
+ * const panel = await loader.getHarness(TnTabPanelHarness);
2848
+ * expect(await panel.getTextContent()).toContain('Overview');
2849
+ * ```
2850
+ */
2851
+ async getTextContent() {
2852
+ const panel = await this._panel();
2853
+ return (await panel.text()).trim();
2854
+ }
2855
+ /**
2856
+ * Gets the panel's testId attribute value.
2857
+ *
2858
+ * @returns Promise resolving to the data-testid value, or null if not set.
2859
+ *
2860
+ * @example
2861
+ * ```typescript
2862
+ * const panel = await loader.getHarness(TnTabPanelHarness);
2863
+ * expect(await panel.getTestId()).toBe('my-panel');
2864
+ * ```
2865
+ */
2866
+ async getTestId() {
2867
+ const panel = await this._panel();
2868
+ return panel.getAttribute('data-testid');
2869
+ }
2870
+ }
2871
+
2872
+ /**
2873
+ * Harness for interacting with tn-tabs in tests.
2874
+ * Provides methods for querying tab state, selecting tabs, and inspecting panels.
2875
+ *
2876
+ * @example
2877
+ * ```typescript
2878
+ * // Get the tabs harness
2879
+ * const tabs = await loader.getHarness(TnTabsHarness);
2880
+ *
2881
+ * // Select a tab by label
2882
+ * await tabs.selectTab({ label: 'Settings' });
2883
+ *
2884
+ * // Get the selected tab label
2885
+ * const selected = await tabs.getSelectedTab();
2886
+ * expect(await selected.getLabel()).toBe('Settings');
2887
+ *
2888
+ * // Get all tab labels
2889
+ * const labels = await tabs.getTabLabels();
2890
+ * expect(labels).toEqual(['Overview', 'Details', 'Settings']);
2891
+ * ```
2892
+ */
2893
+ class TnTabsHarness extends ComponentHarness {
2894
+ /**
2895
+ * The selector for the host element of a `TnTabsComponent` instance.
2896
+ */
2897
+ static hostSelector = 'tn-tabs';
2898
+ /**
2899
+ * Gets a `HarnessPredicate` that can be used to search for tabs
2900
+ * with specific attributes.
2901
+ *
2902
+ * @param options Options for filtering which tabs instances are considered a match.
2903
+ * @returns A `HarnessPredicate` configured with the given options.
2904
+ *
2905
+ * @example
2906
+ * ```typescript
2907
+ * // Find by orientation
2908
+ * const tabs = await loader.getHarness(TnTabsHarness.with({ orientation: 'vertical' }));
2909
+ *
2910
+ * // Find the tab group containing a "Settings" tab
2911
+ * const tabs = await loader.getHarness(TnTabsHarness.with({ hasTab: 'Settings' }));
2912
+ *
2913
+ * // Find by regex
2914
+ * const tabs = await loader.getHarness(TnTabsHarness.with({ hasTab: /settings/i }));
2915
+ * ```
2916
+ */
2917
+ static with(options = {}) {
2918
+ return new HarnessPredicate(TnTabsHarness, options)
2919
+ .addOption('orientation', options.orientation, async (harness, orientation) => {
2920
+ return (await harness.getOrientation()) === orientation;
2921
+ })
2922
+ .addOption('hasTab', options.hasTab, async (harness, label) => {
2923
+ const labels = await harness.getTabLabels();
2924
+ if (label instanceof RegExp) {
2925
+ return labels.some(l => label.test(l));
2926
+ }
2927
+ return labels.includes(label);
2928
+ });
2929
+ }
2930
+ /**
2931
+ * Gets all tab harnesses within this tab group.
2932
+ *
2933
+ * @returns Promise resolving to an array of `TnTabHarness` instances.
2934
+ *
2935
+ * @example
2936
+ * ```typescript
2937
+ * const tabs = await loader.getHarness(TnTabsHarness);
2938
+ * const allTabs = await tabs.getTabs();
2939
+ * expect(allTabs.length).toBe(3);
2940
+ * ```
2941
+ */
2942
+ async getTabs() {
2943
+ return this.locatorForAll(TnTabHarness)();
2944
+ }
2945
+ /**
2946
+ * Gets a specific tab by filter criteria.
2947
+ *
2948
+ * @param filter Optional filter to match a specific tab.
2949
+ * @returns Promise resolving to the matching `TnTabHarness`.
2950
+ *
2951
+ * @example
2952
+ * ```typescript
2953
+ * const tabs = await loader.getHarness(TnTabsHarness);
2954
+ * const settingsTab = await tabs.getTab({ label: 'Settings' });
2955
+ * ```
2956
+ */
2957
+ async getTab(filter = {}) {
2958
+ return this.locatorFor(TnTabHarness.with(filter))();
2959
+ }
2960
+ /**
2961
+ * Gets all panel harnesses within this tab group.
2962
+ *
2963
+ * @returns Promise resolving to an array of `TnTabPanelHarness` instances.
2964
+ *
2965
+ * @example
2966
+ * ```typescript
2967
+ * const tabs = await loader.getHarness(TnTabsHarness);
2968
+ * const panels = await tabs.getPanels();
2969
+ * expect(panels.length).toBe(3);
2970
+ * ```
2971
+ */
2972
+ async getPanels() {
2973
+ return this.locatorForAll(TnTabPanelHarness)();
2974
+ }
2975
+ /**
2976
+ * Gets all tab labels as strings.
2977
+ *
2978
+ * @returns Promise resolving to an array of tab label strings.
2979
+ *
2980
+ * @example
2981
+ * ```typescript
2982
+ * const tabs = await loader.getHarness(TnTabsHarness);
2983
+ * const labels = await tabs.getTabLabels();
2984
+ * expect(labels).toEqual(['Overview', 'Details', 'Settings']);
2985
+ * ```
2986
+ */
2987
+ async getTabLabels() {
2988
+ const tabs = await this.getTabs();
2989
+ return Promise.all(tabs.map(tab => tab.getLabel()));
2990
+ }
2991
+ /**
2992
+ * Gets the currently selected (active) tab.
2993
+ *
2994
+ * @returns Promise resolving to the active `TnTabHarness`.
2995
+ * @throws If no active tab is found.
2996
+ *
2997
+ * @example
2998
+ * ```typescript
2999
+ * const tabs = await loader.getHarness(TnTabsHarness);
3000
+ * const selected = await tabs.getSelectedTab();
3001
+ * expect(await selected.getLabel()).toBe('Overview');
3002
+ * ```
3003
+ */
3004
+ async getSelectedTab() {
3005
+ const tabs = await this.getTabs();
3006
+ for (const tab of tabs) {
3007
+ if (await tab.isSelected()) {
3008
+ return tab;
3009
+ }
3010
+ }
3011
+ throw new Error('No selected tab found');
3012
+ }
3013
+ /**
3014
+ * Selects a tab by filter criteria. Clicks the matching tab button.
3015
+ *
3016
+ * @param filter Filter to identify which tab to select.
3017
+ * @returns Promise that resolves when the tab has been selected.
3018
+ *
3019
+ * @example
3020
+ * ```typescript
3021
+ * const tabs = await loader.getHarness(TnTabsHarness);
3022
+ *
3023
+ * // Select by label
3024
+ * await tabs.selectTab({ label: 'Settings' });
3025
+ *
3026
+ * // Select by regex
3027
+ * await tabs.selectTab({ label: /detail/i });
3028
+ * ```
3029
+ */
3030
+ async selectTab(filter) {
3031
+ const tab = await this.getTab(filter);
3032
+ await tab.select();
3033
+ }
3034
+ /**
3035
+ * Gets the orientation of the tab group.
3036
+ *
3037
+ * @returns Promise resolving to 'horizontal' or 'vertical'.
3038
+ *
3039
+ * @example
3040
+ * ```typescript
3041
+ * const tabs = await loader.getHarness(TnTabsHarness);
3042
+ * expect(await tabs.getOrientation()).toBe('horizontal');
3043
+ * ```
3044
+ */
3045
+ async getOrientation() {
3046
+ const tablist = await this.locatorFor('[role="tablist"]')();
3047
+ return (await tablist.getAttribute('aria-orientation')) ?? 'horizontal';
2232
3048
  }
2233
- focusTab(index) {
2234
- const tab = this.tabs()[index];
2235
- if (tab && tab.elementRef) {
2236
- tab.elementRef.nativeElement.focus();
2237
- }
3049
+ /**
3050
+ * Gets the number of tabs.
3051
+ *
3052
+ * @returns Promise resolving to the tab count.
3053
+ *
3054
+ * @example
3055
+ * ```typescript
3056
+ * const tabs = await loader.getHarness(TnTabsHarness);
3057
+ * expect(await tabs.getTabCount()).toBe(3);
3058
+ * ```
3059
+ */
3060
+ async getTabCount() {
3061
+ const tabs = await this.getTabs();
3062
+ return tabs.length;
2238
3063
  }
2239
- classes = computed(() => {
2240
- const classes = ['tn-tabs'];
2241
- if (this.orientation() === 'vertical') {
2242
- classes.push('tn-tabs--vertical');
2243
- }
2244
- else {
2245
- classes.push('tn-tabs--horizontal');
2246
- }
2247
- // Add highlight position class
2248
- classes.push(`tn-tabs--highlight-${this.highlightPosition()}`);
2249
- return classes.join(' ');
2250
- }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
2251
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2252
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnTabsComponent, isStandalone: true, selector: "tn-tabs", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, highlightPosition: { classPropertyName: "highlightPosition", publicName: "highlightPosition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndexChange: "selectedIndexChange", tabChange: "tabChange" }, queries: [{ propertyName: "tabs", predicate: TnTabComponent, isSignal: true }, { propertyName: "panels", predicate: TnTabPanelComponent, isSignal: true }], viewQueries: [{ propertyName: "tabHeader", first: true, predicate: ["tabHeader"], descendants: true, isSignal: true }], ngImport: i0, template: "<div role=\"tablist\" [ngClass]=\"classes()\" [attr.aria-orientation]=\"orientation()\">\n <div #tabHeader class=\"tn-tabs__header\">\n <ng-content select=\"tn-tab\" />\n @if (highlightBarVisible()) {\n <div class=\"tn-tabs__highlight-bar\"\n [style.left.px]=\"highlightBarLeft()\"\n [style.width.px]=\"highlightBarWidth()\"\n [style.top.px]=\"highlightBarTop()\"\n [style.height.px]=\"highlightBarHeight()\">\n </div>\n }\n </div>\n\n <div class=\"tn-tabs__content\">\n <ng-content select=\"tn-tab-panel\" />\n </div>\n</div>", styles: [".tn-tabs{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;font-family:var(--tn-font-family-body, \"Inter\", sans-serif)}.tn-tabs--disabled{opacity:.6;pointer-events:none}.tn-tabs--vertical{flex-direction:row}.tn-tabs--vertical .tn-tabs__header{flex-direction:column;border-bottom:none;border-right:1px solid var(--tn-lines, #e0e0e0);min-width:240px;width:auto}.tn-tabs--vertical .tn-tabs__content{flex:1;min-width:0}.tn-tabs--vertical .tn-tabs__highlight-bar{bottom:auto;height:auto}.tn-tabs--vertical .tn-tab{justify-content:flex-start;text-align:left;width:100%}.tn-tabs--vertical .tn-tab:hover:not(.tn-tab--disabled){background-color:var(--tn-alt-bg1, #f5f5f5);color:var(--tn-fg1, #333)}.tn-tabs__header{display:flex;background-color:var(--tn-bg1, #fff);position:relative;border-bottom:1px solid var(--tn-lines, #e0e0e0)}.tn-tabs__highlight-bar{position:absolute;bottom:-1px;height:2px;background-color:var(--tn-primary, #0095d5);transition:left .3s ease,width .3s ease,top .3s ease,height .3s ease;z-index:1}.tn-tabs__content{flex:1;position:relative;background-color:var(--tn-bg1, #fff);min-height:0;width:100%;overflow:hidden}@media(max-width:768px){.tn-tabs__header{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-tabs__header::-webkit-scrollbar{display:none}.tn-tabs--vertical .tn-tabs__header{overflow-y:auto;overflow-x:visible;max-height:300px}}@media(prefers-reduced-motion:reduce){.tn-tabs__highlight-bar{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2253
3064
  }
2254
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTabsComponent, decorators: [{
2255
- type: Component,
2256
- args: [{ selector: 'tn-tabs', standalone: true, imports: [CommonModule, A11yModule, TnTabComponent, TnTabPanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div role=\"tablist\" [ngClass]=\"classes()\" [attr.aria-orientation]=\"orientation()\">\n <div #tabHeader class=\"tn-tabs__header\">\n <ng-content select=\"tn-tab\" />\n @if (highlightBarVisible()) {\n <div class=\"tn-tabs__highlight-bar\"\n [style.left.px]=\"highlightBarLeft()\"\n [style.width.px]=\"highlightBarWidth()\"\n [style.top.px]=\"highlightBarTop()\"\n [style.height.px]=\"highlightBarHeight()\">\n </div>\n }\n </div>\n\n <div class=\"tn-tabs__content\">\n <ng-content select=\"tn-tab-panel\" />\n </div>\n</div>", styles: [".tn-tabs{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;font-family:var(--tn-font-family-body, \"Inter\", sans-serif)}.tn-tabs--disabled{opacity:.6;pointer-events:none}.tn-tabs--vertical{flex-direction:row}.tn-tabs--vertical .tn-tabs__header{flex-direction:column;border-bottom:none;border-right:1px solid var(--tn-lines, #e0e0e0);min-width:240px;width:auto}.tn-tabs--vertical .tn-tabs__content{flex:1;min-width:0}.tn-tabs--vertical .tn-tabs__highlight-bar{bottom:auto;height:auto}.tn-tabs--vertical .tn-tab{justify-content:flex-start;text-align:left;width:100%}.tn-tabs--vertical .tn-tab:hover:not(.tn-tab--disabled){background-color:var(--tn-alt-bg1, #f5f5f5);color:var(--tn-fg1, #333)}.tn-tabs__header{display:flex;background-color:var(--tn-bg1, #fff);position:relative;border-bottom:1px solid var(--tn-lines, #e0e0e0)}.tn-tabs__highlight-bar{position:absolute;bottom:-1px;height:2px;background-color:var(--tn-primary, #0095d5);transition:left .3s ease,width .3s ease,top .3s ease,height .3s ease;z-index:1}.tn-tabs__content{flex:1;position:relative;background-color:var(--tn-bg1, #fff);min-height:0;width:100%;overflow:hidden}@media(max-width:768px){.tn-tabs__header{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.tn-tabs__header::-webkit-scrollbar{display:none}.tn-tabs--vertical .tn-tabs__header{overflow-y:auto;overflow-x:visible;max-height:300px}}@media(prefers-reduced-motion:reduce){.tn-tabs__highlight-bar{transition:none}}\n"] }]
2257
- }], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTabComponent), { isSignal: true }] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnTabPanelComponent), { isSignal: true }] }], tabHeader: [{ type: i0.ViewChild, args: ['tabHeader', { isSignal: true }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], highlightPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightPosition", required: false }] }], selectedIndexChange: [{ type: i0.Output, args: ["selectedIndexChange"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }] } });
2258
3065
 
2259
3066
  class TnKeyboardShortcutComponent {
2260
3067
  shortcut = input('', ...(ngDevMode ? [{ debugName: "shortcut" }] : []));
@@ -2762,215 +3569,6 @@ class TnSelectHarness extends ComponentHarness {
2762
3569
  }
2763
3570
  }
2764
3571
 
2765
- /**
2766
- * Harness for interacting with tn-icon in tests.
2767
- * Provides filtering by icon name and library for existence checks, as well as click interaction.
2768
- *
2769
- * @example
2770
- * ```typescript
2771
- * // Check for existence
2772
- * const icon = await loader.getHarness(TnIconHarness);
2773
- *
2774
- * // Find icon by name
2775
- * const folderIcon = await loader.getHarness(
2776
- * TnIconHarness.with({ name: 'folder' })
2777
- * );
2778
- *
2779
- * // Find icon by name and library
2780
- * const mdiIcon = await loader.getHarness(
2781
- * TnIconHarness.with({ name: 'account-circle', library: 'mdi' })
2782
- * );
2783
- *
2784
- * // Check if icon exists
2785
- * const hasIcon = await loader.hasHarness(
2786
- * TnIconHarness.with({ name: 'check' })
2787
- * );
2788
- *
2789
- * // Click an icon
2790
- * const closeIcon = await loader.getHarness(
2791
- * TnIconHarness.with({ name: 'close' })
2792
- * );
2793
- * await closeIcon.click();
2794
- * ```
2795
- */
2796
- class TnIconHarness extends ComponentHarness {
2797
- /**
2798
- * The selector for the host element of a `TnIconComponent` instance.
2799
- */
2800
- static hostSelector = 'tn-icon';
2801
- /**
2802
- * Gets a `HarnessPredicate` that can be used to search for an icon
2803
- * with specific attributes.
2804
- *
2805
- * @param options Options for filtering which icon instances are considered a match.
2806
- * @returns A `HarnessPredicate` configured with the given options.
2807
- *
2808
- * @example
2809
- * ```typescript
2810
- * // Find icon by name
2811
- * const icon = await loader.getHarness(
2812
- * TnIconHarness.with({ name: 'home' })
2813
- * );
2814
- *
2815
- * // Find icon by library
2816
- * const customIcon = await loader.getHarness(
2817
- * TnIconHarness.with({ library: 'custom' })
2818
- * );
2819
- *
2820
- * // Find icon with specific size
2821
- * const largeIcon = await loader.getHarness(
2822
- * TnIconHarness.with({ size: 'lg' })
2823
- * );
2824
- * ```
2825
- */
2826
- static with(options = {}) {
2827
- return new HarnessPredicate(TnIconHarness, options)
2828
- .addOption('name', options.name, async (harness, name) => {
2829
- return (await harness.getName()) === name;
2830
- })
2831
- .addOption('library', options.library, async (harness, library) => {
2832
- return (await harness.getLibrary()) === library;
2833
- })
2834
- .addOption('size', options.size, async (harness, size) => {
2835
- return (await harness.getSize()) === size;
2836
- })
2837
- .addOption('fullSize', options.fullSize, async (harness, fullSize) => {
2838
- return (await harness.isFullSize()) === fullSize;
2839
- })
2840
- .addOption('customSize', options.customSize, async (harness, customSize) => {
2841
- return (await harness.getCustomSize()) === customSize;
2842
- });
2843
- }
2844
- /**
2845
- * Gets the icon name.
2846
- *
2847
- * @returns Promise resolving to the icon name.
2848
- *
2849
- * @example
2850
- * ```typescript
2851
- * const icon = await loader.getHarness(TnIconHarness);
2852
- * const name = await icon.getName();
2853
- * expect(name).toBe('folder');
2854
- * ```
2855
- */
2856
- async getName() {
2857
- const host = await this.host();
2858
- return host.getAttribute('name');
2859
- }
2860
- /**
2861
- * Gets the icon library.
2862
- *
2863
- * @returns Promise resolving to the icon library.
2864
- *
2865
- * @example
2866
- * ```typescript
2867
- * const icon = await loader.getHarness(TnIconHarness);
2868
- * const library = await icon.getLibrary();
2869
- * expect(library).toBe('mdi');
2870
- * ```
2871
- */
2872
- async getLibrary() {
2873
- const host = await this.host();
2874
- return host.getAttribute('library');
2875
- }
2876
- /**
2877
- * Gets the icon size.
2878
- *
2879
- * @returns Promise resolving to the icon size.
2880
- *
2881
- * @example
2882
- * ```typescript
2883
- * const icon = await loader.getHarness(TnIconHarness);
2884
- * const size = await icon.getSize();
2885
- * expect(size).toBe('lg');
2886
- * ```
2887
- */
2888
- async getSize() {
2889
- const host = await this.host();
2890
- return host.getAttribute('size');
2891
- }
2892
- /**
2893
- * Gets the icon color.
2894
- *
2895
- * @returns Promise resolving to the icon color.
2896
- *
2897
- * @example
2898
- * ```typescript
2899
- * const icon = await loader.getHarness(TnIconHarness);
2900
- * const color = await icon.getColor();
2901
- * expect(color).toBe('primary');
2902
- * ```
2903
- */
2904
- async getColor() {
2905
- const host = await this.host();
2906
- return host.getAttribute('color');
2907
- }
2908
- /**
2909
- * Checks if the icon is in full-size mode.
2910
- *
2911
- * @returns Promise resolving to true if the icon is full-size, false otherwise.
2912
- *
2913
- * @example
2914
- * ```typescript
2915
- * const icon = await loader.getHarness(TnIconHarness);
2916
- * const isFullSize = await icon.isFullSize();
2917
- * expect(isFullSize).toBe(true);
2918
- * ```
2919
- */
2920
- async isFullSize() {
2921
- const host = await this.host();
2922
- const fullSizeAttr = await host.getAttribute('full-size');
2923
- return fullSizeAttr === 'true';
2924
- }
2925
- /**
2926
- * Gets the icon custom size.
2927
- *
2928
- * @returns Promise resolving to the custom size value, or null if not set.
2929
- *
2930
- * @example
2931
- * ```typescript
2932
- * const icon = await loader.getHarness(TnIconHarness);
2933
- * const customSize = await icon.getCustomSize();
2934
- * expect(customSize).toBe('64px');
2935
- * ```
2936
- */
2937
- async getCustomSize() {
2938
- const host = await this.host();
2939
- return host.getAttribute('custom-size');
2940
- }
2941
- /**
2942
- * Checks if the icon is using a custom size.
2943
- *
2944
- * @returns Promise resolving to true if a custom size is set, false otherwise.
2945
- *
2946
- * @example
2947
- * ```typescript
2948
- * const icon = await loader.getHarness(TnIconHarness);
2949
- * const hasCustomSize = await icon.hasCustomSize();
2950
- * expect(hasCustomSize).toBe(true);
2951
- * ```
2952
- */
2953
- async hasCustomSize() {
2954
- const customSize = await this.getCustomSize();
2955
- return customSize !== null;
2956
- }
2957
- /**
2958
- * Clicks the icon.
2959
- *
2960
- * @returns Promise that resolves when the click action is complete.
2961
- *
2962
- * @example
2963
- * ```typescript
2964
- * const icon = await loader.getHarness(TnIconHarness.with({ name: 'close' }));
2965
- * await icon.click();
2966
- * ```
2967
- */
2968
- async click() {
2969
- const host = await this.host();
2970
- return host.click();
2971
- }
2972
- }
2973
-
2974
3572
  /**
2975
3573
  * Marks an icon name for inclusion in the TrueNAS UI sprite generation.
2976
3574
  *
@@ -8711,5 +9309,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
8711
9309
  * Generated bundle index. Do not edit.
8712
9310
  */
8713
9311
 
8714
- export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogShellComponent, TnDividerComponent, TnDividerDirective, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuComponent, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSlideToggleComponent, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabPanelComponent, TnTableColumnDirective, TnTableComponent, TnTabsComponent, TnTheme, TnThemeService, TnTimeInputComponent, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
9312
+ export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogShellComponent, TnDividerComponent, TnDividerDirective, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuComponent, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSlideToggleComponent, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTabsComponent, TnTabsHarness, TnTheme, TnThemeService, TnTimeInputComponent, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
8715
9313
  //# sourceMappingURL=truenas-ui-components.mjs.map