@truenas/ui-components 0.3.12 → 0.3.14
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.
|
@@ -2022,8 +2022,14 @@ class TnBannerComponent {
|
|
|
2022
2022
|
actionContent = contentChildren(TnBannerActionDirective, ...(ngDevMode ? [{ debugName: "actionContent" }] : []));
|
|
2023
2023
|
/** Signal indicating whether action content has been projected */
|
|
2024
2024
|
hasAction = computed(() => this.actionContent().length > 0, ...(ngDevMode ? [{ debugName: "hasAction" }] : []));
|
|
2025
|
+
/**
|
|
2026
|
+
* Signal indicating whether the built-in structured layout (icon + heading/message)
|
|
2027
|
+
* should be rendered. When neither heading nor message is provided, the banner
|
|
2028
|
+
* instead renders arbitrary projected content from the default slot.
|
|
2029
|
+
*/
|
|
2030
|
+
hasStructuredContent = computed(() => !!this.heading() || !!this.message(), ...(ngDevMode ? [{ debugName: "hasStructuredContent" }] : []));
|
|
2025
2031
|
// Signal-based inputs (modern Angular 19+)
|
|
2026
|
-
heading = input
|
|
2032
|
+
heading = input(undefined, ...(ngDevMode ? [{ debugName: "heading" }] : []));
|
|
2027
2033
|
message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
|
|
2028
2034
|
type = input('info', ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
2029
2035
|
bordered = input(false, ...(ngDevMode ? [{ debugName: "bordered" }] : []));
|
|
@@ -2082,12 +2088,12 @@ class TnBannerComponent {
|
|
|
2082
2088
|
return result;
|
|
2083
2089
|
}, ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
2084
2090
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2085
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired:
|
|
2091
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }] });
|
|
2086
2092
|
}
|
|
2087
2093
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, decorators: [{
|
|
2088
2094
|
type: Component,
|
|
2089
|
-
args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n
|
|
2090
|
-
}], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required:
|
|
2095
|
+
args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"] }]
|
|
2096
|
+
}], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }] } });
|
|
2091
2097
|
|
|
2092
2098
|
/**
|
|
2093
2099
|
* Harness for interacting with tn-banner in tests.
|
|
@@ -4199,6 +4205,262 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4199
4205
|
}]
|
|
4200
4206
|
}], ctorParameters: () => [] });
|
|
4201
4207
|
|
|
4208
|
+
/**
|
|
4209
|
+
* A minimal file-selection form control. Renders a styled "Choose File" button
|
|
4210
|
+
* that opens the native file dialog and exposes the picked `File`(s) both as a
|
|
4211
|
+
* `(change)` output and as the value of an Angular form control.
|
|
4212
|
+
*
|
|
4213
|
+
* The selected file name(s) are shown next to the button (toggle with
|
|
4214
|
+
* `showFileName`). Use inside a `<tn-form-field>` to get a label, required
|
|
4215
|
+
* asterisk and help tooltip.
|
|
4216
|
+
*
|
|
4217
|
+
* Like every native file input, the browser forbids programmatically setting
|
|
4218
|
+
* the chosen files for security reasons. Writing a non-empty value via a form
|
|
4219
|
+
* control therefore only updates the displayed name; writing `null` (or an empty
|
|
4220
|
+
* array) clears the control. User selection is the only way to populate real
|
|
4221
|
+
* `File` objects.
|
|
4222
|
+
*
|
|
4223
|
+
* @example
|
|
4224
|
+
* ```html
|
|
4225
|
+
* <tn-form-field label="Update File" [required]="true" tooltip="Upload a .tar file">
|
|
4226
|
+
* <tn-file-input accept=".tar" formControlName="update" (change)="onFile($event)" />
|
|
4227
|
+
* </tn-form-field>
|
|
4228
|
+
* ```
|
|
4229
|
+
*/
|
|
4230
|
+
class TnFileInputComponent {
|
|
4231
|
+
fileInputEl = viewChild.required('fileInput');
|
|
4232
|
+
/** Label rendered inside the trigger button. */
|
|
4233
|
+
buttonLabel = input('Choose File', ...(ngDevMode ? [{ debugName: "buttonLabel" }] : []));
|
|
4234
|
+
/**
|
|
4235
|
+
* Native `accept` attribute forwarded to the file input — a comma-separated
|
|
4236
|
+
* list of extensions and/or MIME types (e.g. `".tar,.txt"`, `"image/*"`).
|
|
4237
|
+
*/
|
|
4238
|
+
accept = input(undefined, ...(ngDevMode ? [{ debugName: "accept" }] : []));
|
|
4239
|
+
/** Allow selecting more than one file. The value becomes a `File[]`. */
|
|
4240
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
4241
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
4242
|
+
/** Whether to render the selected file name(s) beside the button. */
|
|
4243
|
+
showFileName = input(true, ...(ngDevMode ? [{ debugName: "showFileName" }] : []));
|
|
4244
|
+
/** Text shown beside the button when no file is selected. */
|
|
4245
|
+
noFileText = input('No file chosen', ...(ngDevMode ? [{ debugName: "noFileText" }] : []));
|
|
4246
|
+
/**
|
|
4247
|
+
* Accessible label for the trigger button, forwarded to `tn-button`. Set this
|
|
4248
|
+
* when the visible "Choose File" text alone doesn't convey what is being
|
|
4249
|
+
* uploaded — e.g. pass the field label so a screen reader announces
|
|
4250
|
+
* "Update File" rather than just "Choose File". (`tn-form-field`'s `<label>`
|
|
4251
|
+
* is not programmatically associated with the projected control.)
|
|
4252
|
+
*/
|
|
4253
|
+
ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
4254
|
+
/**
|
|
4255
|
+
* Semantic test-id base. Rendered under whichever attribute name is configured
|
|
4256
|
+
* via `TN_TEST_ATTR` (default `data-testid`).
|
|
4257
|
+
*/
|
|
4258
|
+
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
4259
|
+
/**
|
|
4260
|
+
* Emitted whenever the selection changes. `null` when the input is cleared.
|
|
4261
|
+
* Named `selectionChange` (not `change`) to avoid colliding with the native
|
|
4262
|
+
* `change` event bubbling from the inner file input.
|
|
4263
|
+
*/
|
|
4264
|
+
selectionChange = output();
|
|
4265
|
+
// Selected files, kept for display and for the form value.
|
|
4266
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
4267
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
|
|
4268
|
+
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
4269
|
+
fileNames = computed(() => this.selectedFiles().map(file => file.name).join(', '), ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
|
|
4270
|
+
hasFiles = computed(() => this.selectedFiles().length > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
|
|
4271
|
+
onChange = (_value) => { };
|
|
4272
|
+
onTouched = () => { };
|
|
4273
|
+
// ControlValueAccessor
|
|
4274
|
+
writeValue(value) {
|
|
4275
|
+
// Native file inputs cannot be populated programmatically; we only mirror
|
|
4276
|
+
// the value for display and treat null/empty as a clear of the control.
|
|
4277
|
+
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
|
4278
|
+
this.selectedFiles.set([]);
|
|
4279
|
+
this.fileInputEl().nativeElement.value = '';
|
|
4280
|
+
}
|
|
4281
|
+
else {
|
|
4282
|
+
this.selectedFiles.set(Array.isArray(value) ? value : [value]);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
registerOnChange(fn) {
|
|
4286
|
+
this.onChange = fn;
|
|
4287
|
+
}
|
|
4288
|
+
registerOnTouched(fn) {
|
|
4289
|
+
this.onTouched = fn;
|
|
4290
|
+
}
|
|
4291
|
+
setDisabledState(isDisabled) {
|
|
4292
|
+
this.formDisabled.set(isDisabled);
|
|
4293
|
+
}
|
|
4294
|
+
openFileDialog() {
|
|
4295
|
+
if (this.isDisabled()) {
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
// Clear the native value first so re-picking the same file still fires
|
|
4299
|
+
// `change` (browsers suppress it when the selection is identical).
|
|
4300
|
+
this.fileInputEl().nativeElement.value = '';
|
|
4301
|
+
this.fileInputEl().nativeElement.click();
|
|
4302
|
+
}
|
|
4303
|
+
/** Marks the control as touched when focus leaves it (e.g. for validation). */
|
|
4304
|
+
onBlur() {
|
|
4305
|
+
this.onTouched();
|
|
4306
|
+
}
|
|
4307
|
+
onFilesSelected(event) {
|
|
4308
|
+
const target = event.target;
|
|
4309
|
+
const files = target.files ? Array.from(target.files) : [];
|
|
4310
|
+
this.selectedFiles.set(files);
|
|
4311
|
+
const value = this.toValue(files);
|
|
4312
|
+
this.onChange(value);
|
|
4313
|
+
this.onTouched();
|
|
4314
|
+
this.selectionChange.emit(value);
|
|
4315
|
+
}
|
|
4316
|
+
toValue(files) {
|
|
4317
|
+
if (files.length === 0) {
|
|
4318
|
+
return null;
|
|
4319
|
+
}
|
|
4320
|
+
return this.multiple() ? files : files[0];
|
|
4321
|
+
}
|
|
4322
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4323
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnFileInputComponent, isStandalone: true, selector: "tn-file-input", inputs: { buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showFileName: { classPropertyName: "showFileName", publicName: "showFileName", isSignal: true, isRequired: false, transformFunction: null }, noFileText: { classPropertyName: "noFileText", publicName: "noFileText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { classAttribute: "tn-file-input" }, providers: [
|
|
4324
|
+
{
|
|
4325
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4326
|
+
useExisting: forwardRef(() => TnFileInputComponent),
|
|
4327
|
+
multi: true
|
|
4328
|
+
}
|
|
4329
|
+
], viewQueries: [{ propertyName: "fileInputEl", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-file-input__container\" tnTestIdType=\"file-input\" [tnTestId]=\"testId()\" (focusout)=\"onBlur()\">\n <input\n #fileInput\n type=\"file\"\n class=\"tn-file-input__native\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\">\n\n <tn-button\n class=\"tn-file-input__button\"\n color=\"default\"\n [label]=\"buttonLabel()\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"ariaLabel()\"\n (onClick)=\"openFileDialog()\" />\n\n @if (showFileName()) {\n <span class=\"tn-file-input__filename\" [class.tn-file-input__filename--empty]=\"!hasFiles()\">\n {{ hasFiles() ? fileNames() : noFileText() }}\n </span>\n }\n\n <!--\n Always-present live region so screen readers are told what was picked even\n when the visible file name is suppressed (showFileName=false).\n -->\n <span class=\"tn-file-input__announcer\" aria-live=\"polite\">\n {{ hasFiles() ? fileNames() : '' }}\n </span>\n</div>\n", styles: [":host{display:inline-block}.tn-file-input__container{display:inline-flex;align-items:center;gap:12px}.tn-file-input__native,.tn-file-input__announcer{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.tn-file-input__button{flex-shrink:0}.tn-file-input__filename{font-size:14px;color:var(--tn-fg1);max-width:30ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-file-input__filename--empty{color:var(--tn-fg2)}\n"], dependencies: [{ kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "icon", "iconPosition", "disabled", "type", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
4330
|
+
}
|
|
4331
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnFileInputComponent, decorators: [{
|
|
4332
|
+
type: Component,
|
|
4333
|
+
args: [{ selector: 'tn-file-input', standalone: true, imports: [TnButtonComponent, TnTestIdDirective], providers: [
|
|
4334
|
+
{
|
|
4335
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4336
|
+
useExisting: forwardRef(() => TnFileInputComponent),
|
|
4337
|
+
multi: true
|
|
4338
|
+
}
|
|
4339
|
+
], host: {
|
|
4340
|
+
'class': 'tn-file-input'
|
|
4341
|
+
}, template: "<div class=\"tn-file-input__container\" tnTestIdType=\"file-input\" [tnTestId]=\"testId()\" (focusout)=\"onBlur()\">\n <input\n #fileInput\n type=\"file\"\n class=\"tn-file-input__native\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\">\n\n <tn-button\n class=\"tn-file-input__button\"\n color=\"default\"\n [label]=\"buttonLabel()\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"ariaLabel()\"\n (onClick)=\"openFileDialog()\" />\n\n @if (showFileName()) {\n <span class=\"tn-file-input__filename\" [class.tn-file-input__filename--empty]=\"!hasFiles()\">\n {{ hasFiles() ? fileNames() : noFileText() }}\n </span>\n }\n\n <!--\n Always-present live region so screen readers are told what was picked even\n when the visible file name is suppressed (showFileName=false).\n -->\n <span class=\"tn-file-input__announcer\" aria-live=\"polite\">\n {{ hasFiles() ? fileNames() : '' }}\n </span>\n</div>\n", styles: [":host{display:inline-block}.tn-file-input__container{display:inline-flex;align-items:center;gap:12px}.tn-file-input__native,.tn-file-input__announcer{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.tn-file-input__button{flex-shrink:0}.tn-file-input__filename{font-size:14px;color:var(--tn-fg1);max-width:30ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-file-input__filename--empty{color:var(--tn-fg2)}\n"] }]
|
|
4342
|
+
}], propDecorators: { fileInputEl: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileName", required: false }] }], noFileText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noFileText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
|
|
4343
|
+
|
|
4344
|
+
/**
|
|
4345
|
+
* Harness for interacting with `tn-file-input` in tests.
|
|
4346
|
+
*
|
|
4347
|
+
* @example
|
|
4348
|
+
* ```typescript
|
|
4349
|
+
* const fileInput = await loader.getHarness(TnFileInputHarness.with({ testId: 'update' }));
|
|
4350
|
+
* expect(await fileInput.getButtonText()).toBe('Choose File');
|
|
4351
|
+
* expect(await fileInput.hasFile()).toBe(false);
|
|
4352
|
+
* expect(await fileInput.getFileName()).toBe('No file chosen');
|
|
4353
|
+
* ```
|
|
4354
|
+
*/
|
|
4355
|
+
class TnFileInputHarness extends ComponentHarness {
|
|
4356
|
+
/** The selector for the host element of a `TnFileInputComponent` instance. */
|
|
4357
|
+
static hostSelector = 'tn-file-input';
|
|
4358
|
+
// The trigger is a `tn-button`; target its rendered `<button>` element.
|
|
4359
|
+
_button = this.locatorFor('.tn-file-input__button .storybook-button');
|
|
4360
|
+
_native = this.locatorFor('input[type="file"]');
|
|
4361
|
+
_filename = this.locatorForOptional('.tn-file-input__filename');
|
|
4362
|
+
/**
|
|
4363
|
+
* Gets a `HarnessPredicate` to search for a file input with specific attributes.
|
|
4364
|
+
*
|
|
4365
|
+
* @param options Options for filtering which instances are considered a match.
|
|
4366
|
+
* @returns A `HarnessPredicate` configured with the given options.
|
|
4367
|
+
*
|
|
4368
|
+
* @example
|
|
4369
|
+
* ```typescript
|
|
4370
|
+
* const fileInput = await loader.getHarness(TnFileInputHarness.with({ testId: 'update' }));
|
|
4371
|
+
* ```
|
|
4372
|
+
*/
|
|
4373
|
+
static with(options = {}) {
|
|
4374
|
+
return new HarnessPredicate(TnFileInputHarness, options)
|
|
4375
|
+
.addOption('buttonText', options.buttonText, (harness, text) => HarnessPredicate.stringMatches(harness.getButtonText(), text))
|
|
4376
|
+
.addOption('testId', options.testId, async (harness, testId) => (await harness.getTestId()) === testId);
|
|
4377
|
+
}
|
|
4378
|
+
/**
|
|
4379
|
+
* Gets the trigger button's text.
|
|
4380
|
+
*
|
|
4381
|
+
* @returns Promise resolving to the button label.
|
|
4382
|
+
*
|
|
4383
|
+
* @example
|
|
4384
|
+
* ```typescript
|
|
4385
|
+
* expect(await fileInput.getButtonText()).toBe('Choose File');
|
|
4386
|
+
* ```
|
|
4387
|
+
*/
|
|
4388
|
+
async getButtonText() {
|
|
4389
|
+
return (await (await this._button()).text()).trim();
|
|
4390
|
+
}
|
|
4391
|
+
/**
|
|
4392
|
+
* Gets the displayed file-name text (or the empty-state text).
|
|
4393
|
+
*
|
|
4394
|
+
* @returns Promise resolving to the text, or null when the name is hidden.
|
|
4395
|
+
*
|
|
4396
|
+
* @example
|
|
4397
|
+
* ```typescript
|
|
4398
|
+
* expect(await fileInput.getFileName()).toBe('No file chosen');
|
|
4399
|
+
* ```
|
|
4400
|
+
*/
|
|
4401
|
+
async getFileName() {
|
|
4402
|
+
const filename = await this._filename();
|
|
4403
|
+
return filename ? (await filename.text()).trim() : null;
|
|
4404
|
+
}
|
|
4405
|
+
/**
|
|
4406
|
+
* Whether a file is currently selected. A native file input reports an empty
|
|
4407
|
+
* `value` until the user picks a file, so this reflects real user selection.
|
|
4408
|
+
*
|
|
4409
|
+
* @returns Promise resolving to true when a file has been chosen.
|
|
4410
|
+
*
|
|
4411
|
+
* @example
|
|
4412
|
+
* ```typescript
|
|
4413
|
+
* expect(await fileInput.hasFile()).toBe(false);
|
|
4414
|
+
* ```
|
|
4415
|
+
*/
|
|
4416
|
+
async hasFile() {
|
|
4417
|
+
const native = await this._native();
|
|
4418
|
+
const value = (await native.getProperty('value')) ?? '';
|
|
4419
|
+
return value !== '';
|
|
4420
|
+
}
|
|
4421
|
+
/**
|
|
4422
|
+
* Whether the control is disabled.
|
|
4423
|
+
*
|
|
4424
|
+
* @returns Promise resolving to true if the button is disabled.
|
|
4425
|
+
*
|
|
4426
|
+
* @example
|
|
4427
|
+
* ```typescript
|
|
4428
|
+
* expect(await fileInput.isDisabled()).toBe(true);
|
|
4429
|
+
* ```
|
|
4430
|
+
*/
|
|
4431
|
+
async isDisabled() {
|
|
4432
|
+
const button = await this._button();
|
|
4433
|
+
return (await button.getProperty('disabled')) ?? false;
|
|
4434
|
+
}
|
|
4435
|
+
/**
|
|
4436
|
+
* Opens the native file dialog by clicking the trigger button. The browser
|
|
4437
|
+
* does not let tests pick a real file, so this is mainly useful for asserting
|
|
4438
|
+
* click handling and focus behaviour.
|
|
4439
|
+
*
|
|
4440
|
+
* @example
|
|
4441
|
+
* ```typescript
|
|
4442
|
+
* await fileInput.open();
|
|
4443
|
+
* ```
|
|
4444
|
+
*/
|
|
4445
|
+
async open() {
|
|
4446
|
+
await (await this._button()).click();
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* Gets the test-id attribute value from the container.
|
|
4450
|
+
*
|
|
4451
|
+
* @returns Promise resolving to the test-id, or null when unset.
|
|
4452
|
+
*
|
|
4453
|
+
* @example
|
|
4454
|
+
* ```typescript
|
|
4455
|
+
* expect(await fileInput.getTestId()).toBe('file-input-update');
|
|
4456
|
+
* ```
|
|
4457
|
+
*/
|
|
4458
|
+
async getTestId() {
|
|
4459
|
+
const container = await this.locatorFor('.tn-file-input__container')();
|
|
4460
|
+
return (await container.getAttribute('data-testid')) ?? (await container.getAttribute('data-test'));
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4202
4464
|
/**
|
|
4203
4465
|
* Strips lightweight label markup (**bold**, *italic*, `code`), returning
|
|
4204
4466
|
* plain text for attribute contexts such as aria-label and title.
|
|
@@ -17345,5 +17607,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
17345
17607
|
* Generated bundle index. Do not edit.
|
|
17346
17608
|
*/
|
|
17347
17609
|
|
|
17348
|
-
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnFormSectionComponent, TnFormSectionHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, formatSize, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, parseLabelMarkup, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
17610
|
+
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnFormSectionComponent, TnFormSectionHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, formatSize, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, parseLabelMarkup, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
17349
17611
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|