@truenas/ui-components 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, input, output, viewChild, signal, computed, effect, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, ViewEncapsulation, Directive, contentChildren, ViewContainerRef, contentChild, ChangeDetectorRef, HostListener, TemplateRef, IterableDiffers, Pipe, PLATFORM_ID, DestroyRef } from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, input, output, viewChild, signal, computed, effect, forwardRef, Component, model, afterNextRender, ChangeDetectionStrategy, Injectable, ViewEncapsulation, Directive, contentChildren, ViewContainerRef, contentChild, ChangeDetectorRef, HostListener, TemplateRef, IterableDiffers, Pipe, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID, DestroyRef } from '@angular/core';
|
|
3
3
|
import * as i1$4 from '@angular/forms';
|
|
4
4
|
import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
|
|
5
5
|
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
@@ -4333,10 +4333,15 @@ class TnSelectComponent {
|
|
|
4333
4333
|
placeholder = input('Select an option', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
4334
4334
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
4335
4335
|
testId = input('', ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
4336
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
4337
|
+
compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
|
|
4336
4338
|
selectionChange = output();
|
|
4339
|
+
/** Emits the full array of selected values after each toggle in multiple mode. */
|
|
4340
|
+
multiSelectionChange = output();
|
|
4337
4341
|
// Internal state signals
|
|
4338
4342
|
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
4339
4343
|
selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
|
|
4344
|
+
selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
|
|
4340
4345
|
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
|
|
4341
4346
|
// Computed disabled state (combines input and form state)
|
|
4342
4347
|
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
@@ -4367,7 +4372,17 @@ class TnSelectComponent {
|
|
|
4367
4372
|
}
|
|
4368
4373
|
// ControlValueAccessor implementation
|
|
4369
4374
|
writeValue(value) {
|
|
4370
|
-
this.
|
|
4375
|
+
if (this.multiple()) {
|
|
4376
|
+
if (Array.isArray(value)) {
|
|
4377
|
+
this.selectedValues.set(value);
|
|
4378
|
+
}
|
|
4379
|
+
else {
|
|
4380
|
+
this.selectedValues.set(value != null ? [value] : []);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
else {
|
|
4384
|
+
this.selectedValue.set(Array.isArray(value) ? value[0] ?? null : value);
|
|
4385
|
+
}
|
|
4371
4386
|
}
|
|
4372
4387
|
registerOnChange(fn) {
|
|
4373
4388
|
this.onChange = fn;
|
|
@@ -4392,8 +4407,16 @@ class TnSelectComponent {
|
|
|
4392
4407
|
this.isOpen.set(false);
|
|
4393
4408
|
this.onTouched();
|
|
4394
4409
|
}
|
|
4395
|
-
onOptionClick(option) {
|
|
4396
|
-
|
|
4410
|
+
onOptionClick(option, groupDisabled = false) {
|
|
4411
|
+
if (option.disabled || groupDisabled) {
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
if (this.multiple()) {
|
|
4415
|
+
this.toggleOption(option);
|
|
4416
|
+
}
|
|
4417
|
+
else {
|
|
4418
|
+
this.selectOption(option);
|
|
4419
|
+
}
|
|
4397
4420
|
}
|
|
4398
4421
|
selectOption(option) {
|
|
4399
4422
|
if (option.disabled) {
|
|
@@ -4403,19 +4426,46 @@ class TnSelectComponent {
|
|
|
4403
4426
|
this.onChange(option.value);
|
|
4404
4427
|
this.selectionChange.emit(option.value);
|
|
4405
4428
|
this.closeDropdown();
|
|
4406
|
-
this.cdr.markForCheck();
|
|
4429
|
+
this.cdr.markForCheck();
|
|
4407
4430
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4431
|
+
toggleOption(option) {
|
|
4432
|
+
const current = this.selectedValues();
|
|
4433
|
+
const index = current.findIndex(v => this.compareValues(v, option.value));
|
|
4434
|
+
let updated;
|
|
4435
|
+
if (index >= 0) {
|
|
4436
|
+
updated = current.filter((_, i) => i !== index);
|
|
4437
|
+
}
|
|
4438
|
+
else {
|
|
4439
|
+
updated = [...current, option.value];
|
|
4440
|
+
}
|
|
4441
|
+
this.selectedValues.set(updated);
|
|
4442
|
+
this.onChange(updated);
|
|
4443
|
+
this.multiSelectionChange.emit(updated);
|
|
4444
|
+
this.cdr.markForCheck();
|
|
4445
|
+
}
|
|
4446
|
+
isOptionSelected(option) {
|
|
4447
|
+
if (this.multiple()) {
|
|
4448
|
+
return this.selectedValues().some(v => this.compareValues(v, option.value));
|
|
4449
|
+
}
|
|
4410
4450
|
return this.compareValues(this.selectedValue(), option.value);
|
|
4411
|
-
}
|
|
4451
|
+
}
|
|
4412
4452
|
getDisplayText = computed(() => {
|
|
4453
|
+
if (this.multiple()) {
|
|
4454
|
+
const values = this.selectedValues();
|
|
4455
|
+
if (values.length === 0) {
|
|
4456
|
+
return this.placeholder();
|
|
4457
|
+
}
|
|
4458
|
+
const labels = values
|
|
4459
|
+
.map(v => this.findOptionByValue(v)?.label ?? String(v))
|
|
4460
|
+
.filter(Boolean);
|
|
4461
|
+
return labels.join(', ');
|
|
4462
|
+
}
|
|
4413
4463
|
const value = this.selectedValue();
|
|
4414
4464
|
if (value === null || value === undefined) {
|
|
4415
4465
|
return this.placeholder();
|
|
4416
4466
|
}
|
|
4417
4467
|
const option = this.findOptionByValue(value);
|
|
4418
|
-
return option ? option.label : value;
|
|
4468
|
+
return option ? option.label : String(value);
|
|
4419
4469
|
}, ...(ngDevMode ? [{ debugName: "getDisplayText" }] : []));
|
|
4420
4470
|
findOptionByValue(value) {
|
|
4421
4471
|
// Search in regular options first
|
|
@@ -4436,6 +4486,10 @@ class TnSelectComponent {
|
|
|
4436
4486
|
return this.options().length > 0 || this.optionGroups().length > 0;
|
|
4437
4487
|
}, ...(ngDevMode ? [{ debugName: "hasAnyOptions" }] : []));
|
|
4438
4488
|
compareValues(a, b) {
|
|
4489
|
+
const customCompare = this.compareWith();
|
|
4490
|
+
if (customCompare) {
|
|
4491
|
+
return customCompare(a, b);
|
|
4492
|
+
}
|
|
4439
4493
|
if (a === b) {
|
|
4440
4494
|
return true;
|
|
4441
4495
|
}
|
|
@@ -4445,6 +4499,8 @@ class TnSelectComponent {
|
|
|
4445
4499
|
return false;
|
|
4446
4500
|
}
|
|
4447
4501
|
// Keyboard navigation
|
|
4502
|
+
// TODO: Add ArrowUp/ArrowDown option navigation, Enter/Space toggle,
|
|
4503
|
+
// and aria-activedescendant tracking for full keyboard accessibility.
|
|
4448
4504
|
onKeydown(event) {
|
|
4449
4505
|
switch (event.key) {
|
|
4450
4506
|
case 'Enter':
|
|
@@ -4469,24 +4525,24 @@ class TnSelectComponent {
|
|
|
4469
4525
|
}
|
|
4470
4526
|
}
|
|
4471
4527
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4472
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, providers: [
|
|
4528
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", multiSelectionChange: "multiSelectionChange" }, providers: [
|
|
4473
4529
|
{
|
|
4474
4530
|
provide: NG_VALUE_ACCESSOR,
|
|
4475
4531
|
useExisting: forwardRef(() => TnSelectComponent),
|
|
4476
4532
|
multi: true
|
|
4477
4533
|
}
|
|
4478
|
-
], ngImport: i0, template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"selectedValue() === null || selectedValue() === undefined\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"
|
|
4534
|
+
], ngImport: i0, template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + testId() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + testId() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.enter)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.space)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }] });
|
|
4479
4535
|
}
|
|
4480
4536
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
|
|
4481
4537
|
type: Component,
|
|
4482
|
-
args: [{ selector: 'tn-select', standalone: true, imports: [], providers: [
|
|
4538
|
+
args: [{ selector: 'tn-select', standalone: true, imports: [TnCheckboxComponent], providers: [
|
|
4483
4539
|
{
|
|
4484
4540
|
provide: NG_VALUE_ACCESSOR,
|
|
4485
4541
|
useExisting: forwardRef(() => TnSelectComponent),
|
|
4486
4542
|
multi: true
|
|
4487
4543
|
}
|
|
4488
|
-
], template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"selectedValue() === null || selectedValue() === undefined\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"
|
|
4489
|
-
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
|
|
4544
|
+
], template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + testId() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + testId() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.enter)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.space)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] }]
|
|
4545
|
+
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], multiSelectionChange: [{ type: i0.Output, args: ["multiSelectionChange"] }] } });
|
|
4490
4546
|
|
|
4491
4547
|
/**
|
|
4492
4548
|
* Harness for interacting with tn-select in tests.
|
|
@@ -7890,7 +7946,7 @@ class TnTimeInputComponent {
|
|
|
7890
7946
|
useExisting: forwardRef(() => TnTimeInputComponent),
|
|
7891
7947
|
multi: true
|
|
7892
7948
|
}
|
|
7893
|
-
], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "disabled", "testId"], outputs: ["selectionChange"] }] });
|
|
7949
|
+
], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "disabled", "testId", "multiple", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }] });
|
|
7894
7950
|
}
|
|
7895
7951
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTimeInputComponent, decorators: [{
|
|
7896
7952
|
type: Component,
|
|
@@ -10451,6 +10507,257 @@ class TnEmptyHarness extends ComponentHarness {
|
|
|
10451
10507
|
}
|
|
10452
10508
|
}
|
|
10453
10509
|
|
|
10510
|
+
var TnToastType;
|
|
10511
|
+
(function (TnToastType) {
|
|
10512
|
+
TnToastType["Info"] = "info";
|
|
10513
|
+
TnToastType["Success"] = "success";
|
|
10514
|
+
TnToastType["Warning"] = "warning";
|
|
10515
|
+
TnToastType["Error"] = "error";
|
|
10516
|
+
})(TnToastType || (TnToastType = {}));
|
|
10517
|
+
var TnToastPosition;
|
|
10518
|
+
(function (TnToastPosition) {
|
|
10519
|
+
TnToastPosition["Top"] = "top";
|
|
10520
|
+
TnToastPosition["Bottom"] = "bottom";
|
|
10521
|
+
})(TnToastPosition || (TnToastPosition = {}));
|
|
10522
|
+
|
|
10523
|
+
// Mark icons for sprite inclusion (dynamic names aren't detected by the scanner)
|
|
10524
|
+
const TOAST_ICONS = {
|
|
10525
|
+
[TnToastType.Info]: tnIconMarker('info', 'material'),
|
|
10526
|
+
[TnToastType.Success]: tnIconMarker('check_circle', 'material'),
|
|
10527
|
+
[TnToastType.Warning]: tnIconMarker('warning', 'material'),
|
|
10528
|
+
[TnToastType.Error]: tnIconMarker('error', 'material'),
|
|
10529
|
+
};
|
|
10530
|
+
class TnToastComponent {
|
|
10531
|
+
message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : []));
|
|
10532
|
+
action = signal(null, ...(ngDevMode ? [{ debugName: "action" }] : []));
|
|
10533
|
+
type = signal(TnToastType.Info, ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
10534
|
+
position = signal(TnToastPosition.Bottom, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
10535
|
+
visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
10536
|
+
icon = computed(() => TOAST_ICONS[this.type()], ...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
10537
|
+
onAction = () => { };
|
|
10538
|
+
onDismiss = () => { };
|
|
10539
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10540
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnToastComponent, isStandalone: true, selector: "tn-toast", host: { properties: { "class.tn-toast--top": "position() === \"top\"", "class.tn-toast--bottom": "position() === \"bottom\"" } }, ngImport: i0, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:.875rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:.875rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
10541
|
+
}
|
|
10542
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastComponent, decorators: [{
|
|
10543
|
+
type: Component,
|
|
10544
|
+
args: [{ selector: 'tn-toast', standalone: true, imports: [TnIconComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
10545
|
+
'[class.tn-toast--top]': 'position() === "top"',
|
|
10546
|
+
'[class.tn-toast--bottom]': 'position() === "bottom"',
|
|
10547
|
+
}, template: "<div\n class=\"tn-toast\"\n role=\"alert\"\n aria-live=\"polite\"\n [class.tn-toast--visible]=\"visible()\"\n [class.tn-toast--info]=\"type() === 'info'\"\n [class.tn-toast--success]=\"type() === 'success'\"\n [class.tn-toast--warning]=\"type() === 'warning'\"\n [class.tn-toast--error]=\"type() === 'error'\">\n <tn-icon class=\"tn-toast__icon\" size=\"sm\" [name]=\"icon()\" />\n <span class=\"tn-toast__message\">{{ message() }}</span>\n @if (action()) {\n <button\n class=\"tn-toast__action\"\n type=\"button\"\n (click)=\"onAction()\">\n {{ action() }}\n </button>\n }\n</div>\n", styles: ["tn-toast{position:fixed;left:50%;transform:translate(-50%);z-index:10000;pointer-events:none}tn-toast.tn-toast--bottom{bottom:1.5rem}tn-toast.tn-toast--top{top:1.5rem}.tn-toast{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;border-radius:.5rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:.875rem;line-height:1.4;pointer-events:auto;background-color:var(--tn-bg2, #333);color:var(--tn-fg1, #fff);border-left:3px solid transparent;box-shadow:0 8px 24px #0000004d;opacity:0;transform:translateY(1rem);transition:opacity .2s ease-out,transform .2s ease-out;max-width:560px}.tn-toast.tn-toast--visible{opacity:1;transform:translateY(0)}.tn-toast.tn-toast--info{border-left-color:var(--tn-info, #3b82f6)}.tn-toast.tn-toast--success{border-left-color:var(--tn-success, #10b981)}.tn-toast.tn-toast--warning{border-left-color:var(--tn-warning, #f59e0b)}.tn-toast.tn-toast--error{border-left-color:var(--tn-error, #ef4444)}.tn-toast__icon{flex-shrink:0}.tn-toast--info .tn-toast__icon{color:var(--tn-info, #3b82f6)}.tn-toast--success .tn-toast__icon{color:var(--tn-success, #10b981)}.tn-toast--warning .tn-toast__icon{color:var(--tn-warning, #f59e0b)}.tn-toast--error .tn-toast__icon{color:var(--tn-error, #ef4444)}.tn-toast__message{flex:1}.tn-toast__action{background:none;border:none;color:var(--tn-primary, #3b82f6);font-family:inherit;font-size:.875rem;font-weight:600;cursor:pointer;padding:.25rem .5rem;border-radius:.25rem;white-space:nowrap;transition:background-color .15s ease}.tn-toast__action:hover{background-color:#ffffff1a}@media(prefers-reduced-motion:reduce){.tn-toast{transition:none}}\n"] }]
|
|
10548
|
+
}] });
|
|
10549
|
+
|
|
10550
|
+
class TnToastRef {
|
|
10551
|
+
_onAction = new Subject();
|
|
10552
|
+
_afterDismissed = new Subject();
|
|
10553
|
+
_dismissed = false;
|
|
10554
|
+
/** @internal */
|
|
10555
|
+
_componentRef;
|
|
10556
|
+
/** Observable that emits when the action button is clicked. */
|
|
10557
|
+
onAction() {
|
|
10558
|
+
return this._onAction.asObservable();
|
|
10559
|
+
}
|
|
10560
|
+
/** Observable that emits when the toast is dismissed (by action, duration, or programmatically). */
|
|
10561
|
+
afterDismissed() {
|
|
10562
|
+
return this._afterDismissed.asObservable();
|
|
10563
|
+
}
|
|
10564
|
+
/** Programmatically dismiss the toast. */
|
|
10565
|
+
dismiss() {
|
|
10566
|
+
if (this._dismissed) {
|
|
10567
|
+
return;
|
|
10568
|
+
}
|
|
10569
|
+
this._dismissed = true;
|
|
10570
|
+
this._afterDismissed.next();
|
|
10571
|
+
this._afterDismissed.complete();
|
|
10572
|
+
this._onAction.complete();
|
|
10573
|
+
}
|
|
10574
|
+
/** @internal */
|
|
10575
|
+
_triggerAction() {
|
|
10576
|
+
this._onAction.next();
|
|
10577
|
+
this.dismiss();
|
|
10578
|
+
}
|
|
10579
|
+
}
|
|
10580
|
+
class TnToastService {
|
|
10581
|
+
appRef = inject(ApplicationRef);
|
|
10582
|
+
injector = inject(EnvironmentInjector);
|
|
10583
|
+
activeRef = null;
|
|
10584
|
+
/**
|
|
10585
|
+
* Opens a toast notification.
|
|
10586
|
+
*
|
|
10587
|
+
* @param message The message to display.
|
|
10588
|
+
* @param actionOrConfig Optional action button text, or config object.
|
|
10589
|
+
* @param config Optional config when action is provided as second arg.
|
|
10590
|
+
* @returns A TnToastRef that can be used to dismiss the toast or listen for events.
|
|
10591
|
+
*
|
|
10592
|
+
* @example
|
|
10593
|
+
* ```typescript
|
|
10594
|
+
* // Simple notification
|
|
10595
|
+
* this.toast.open('Changes saved');
|
|
10596
|
+
*
|
|
10597
|
+
* // With action button
|
|
10598
|
+
* const ref = this.toast.open('Item deleted', 'Undo');
|
|
10599
|
+
* ref.onAction().subscribe(() => this.undoDelete());
|
|
10600
|
+
*
|
|
10601
|
+
* // With config
|
|
10602
|
+
* this.toast.open('Error occurred', { type: 'error', duration: 6000 });
|
|
10603
|
+
*
|
|
10604
|
+
* // Action + config
|
|
10605
|
+
* this.toast.open('Failed to save', 'Retry', { type: 'error' });
|
|
10606
|
+
* ```
|
|
10607
|
+
*/
|
|
10608
|
+
open(message, actionOrConfig, config) {
|
|
10609
|
+
// Dismiss any existing toast
|
|
10610
|
+
if (this.activeRef) {
|
|
10611
|
+
this.activeRef.dismiss();
|
|
10612
|
+
}
|
|
10613
|
+
// Parse overloaded args
|
|
10614
|
+
let action;
|
|
10615
|
+
let resolvedConfig = {};
|
|
10616
|
+
if (typeof actionOrConfig === 'string') {
|
|
10617
|
+
action = actionOrConfig;
|
|
10618
|
+
resolvedConfig = config ?? {};
|
|
10619
|
+
}
|
|
10620
|
+
else if (actionOrConfig) {
|
|
10621
|
+
resolvedConfig = actionOrConfig;
|
|
10622
|
+
}
|
|
10623
|
+
const duration = resolvedConfig.duration ?? 4000;
|
|
10624
|
+
const type = resolvedConfig.type ?? TnToastType.Info;
|
|
10625
|
+
const position = resolvedConfig.position ?? TnToastPosition.Bottom;
|
|
10626
|
+
// Create ref
|
|
10627
|
+
const ref = new TnToastRef();
|
|
10628
|
+
this.activeRef = ref;
|
|
10629
|
+
// Create component
|
|
10630
|
+
const componentRef = createComponent(TnToastComponent, {
|
|
10631
|
+
environmentInjector: this.injector,
|
|
10632
|
+
});
|
|
10633
|
+
ref._componentRef = componentRef;
|
|
10634
|
+
const instance = componentRef.instance;
|
|
10635
|
+
instance.message.set(message);
|
|
10636
|
+
instance.action.set(action ?? null);
|
|
10637
|
+
instance.type.set(type);
|
|
10638
|
+
instance.position.set(position);
|
|
10639
|
+
instance.onAction = () => ref._triggerAction();
|
|
10640
|
+
instance.onDismiss = () => ref.dismiss();
|
|
10641
|
+
// Attach to DOM
|
|
10642
|
+
this.appRef.attachView(componentRef.hostView);
|
|
10643
|
+
document.body.appendChild(componentRef.location.nativeElement);
|
|
10644
|
+
// Animate in
|
|
10645
|
+
requestAnimationFrame(() => {
|
|
10646
|
+
instance.visible.set(true);
|
|
10647
|
+
});
|
|
10648
|
+
// Auto-dismiss
|
|
10649
|
+
let timeout = null;
|
|
10650
|
+
if (duration > 0) {
|
|
10651
|
+
timeout = setTimeout(() => ref.dismiss(), duration);
|
|
10652
|
+
}
|
|
10653
|
+
// Cleanup on dismiss
|
|
10654
|
+
ref.afterDismissed().subscribe(() => {
|
|
10655
|
+
if (timeout) {
|
|
10656
|
+
clearTimeout(timeout);
|
|
10657
|
+
}
|
|
10658
|
+
instance.visible.set(false);
|
|
10659
|
+
// Wait for animation to complete before removing
|
|
10660
|
+
setTimeout(() => {
|
|
10661
|
+
this.appRef.detachView(componentRef.hostView);
|
|
10662
|
+
componentRef.destroy();
|
|
10663
|
+
if (this.activeRef === ref) {
|
|
10664
|
+
this.activeRef = null;
|
|
10665
|
+
}
|
|
10666
|
+
}, 200);
|
|
10667
|
+
});
|
|
10668
|
+
return ref;
|
|
10669
|
+
}
|
|
10670
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
10671
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastService, providedIn: 'root' });
|
|
10672
|
+
}
|
|
10673
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnToastService, decorators: [{
|
|
10674
|
+
type: Injectable,
|
|
10675
|
+
args: [{ providedIn: 'root' }]
|
|
10676
|
+
}] });
|
|
10677
|
+
|
|
10678
|
+
/**
|
|
10679
|
+
* A mock implementation of TnToastService for unit testing.
|
|
10680
|
+
*
|
|
10681
|
+
* Records all `open()` calls so tests can assert on toast messages,
|
|
10682
|
+
* types, and actions without rendering actual toast components.
|
|
10683
|
+
*
|
|
10684
|
+
* @example
|
|
10685
|
+
* ```typescript
|
|
10686
|
+
* import { TnToastTesting } from '@truenas/ui-components';
|
|
10687
|
+
*
|
|
10688
|
+
* let toastMock: TnToastMock;
|
|
10689
|
+
*
|
|
10690
|
+
* beforeEach(() => {
|
|
10691
|
+
* toastMock = new TnToastMock();
|
|
10692
|
+
* TestBed.configureTestingModule({
|
|
10693
|
+
* providers: [TnToastTesting.providers(toastMock)],
|
|
10694
|
+
* });
|
|
10695
|
+
* });
|
|
10696
|
+
*
|
|
10697
|
+
* it('should show success toast', () => {
|
|
10698
|
+
* // ... trigger action that opens a toast
|
|
10699
|
+
* expect(toastMock.calls.length).toBe(1);
|
|
10700
|
+
* expect(toastMock.lastCall?.message).toBe('Saved successfully');
|
|
10701
|
+
* expect(toastMock.lastCall?.config.type).toBe(TnToastType.Success);
|
|
10702
|
+
* });
|
|
10703
|
+
*
|
|
10704
|
+
* it('should handle action click', () => {
|
|
10705
|
+
* // ... trigger action that opens a toast with action
|
|
10706
|
+
* toastMock.lastCall?.ref._triggerAction();
|
|
10707
|
+
* // ... assert retry behavior
|
|
10708
|
+
* });
|
|
10709
|
+
* ```
|
|
10710
|
+
*/
|
|
10711
|
+
class TnToastMock {
|
|
10712
|
+
/** All recorded toast open() calls. */
|
|
10713
|
+
calls = [];
|
|
10714
|
+
/** The most recent toast call, or undefined if none. */
|
|
10715
|
+
get lastCall() {
|
|
10716
|
+
return this.calls[this.calls.length - 1];
|
|
10717
|
+
}
|
|
10718
|
+
/** Clears all recorded calls. */
|
|
10719
|
+
reset() {
|
|
10720
|
+
this.calls = [];
|
|
10721
|
+
}
|
|
10722
|
+
open(message, actionOrConfig, config) {
|
|
10723
|
+
let action;
|
|
10724
|
+
let resolvedConfig = {};
|
|
10725
|
+
if (typeof actionOrConfig === 'string') {
|
|
10726
|
+
action = actionOrConfig;
|
|
10727
|
+
resolvedConfig = config ?? {};
|
|
10728
|
+
}
|
|
10729
|
+
else if (actionOrConfig) {
|
|
10730
|
+
resolvedConfig = actionOrConfig;
|
|
10731
|
+
}
|
|
10732
|
+
const ref = new TnToastRef();
|
|
10733
|
+
this.calls.push({ message, action, config: resolvedConfig, ref });
|
|
10734
|
+
return ref;
|
|
10735
|
+
}
|
|
10736
|
+
}
|
|
10737
|
+
/**
|
|
10738
|
+
* Test utilities for TnToastService.
|
|
10739
|
+
*
|
|
10740
|
+
* Provides a mock that records toast calls without rendering components,
|
|
10741
|
+
* making tests fast and deterministic.
|
|
10742
|
+
*
|
|
10743
|
+
* @example
|
|
10744
|
+
* ```typescript
|
|
10745
|
+
* const toastMock = new TnToastMock();
|
|
10746
|
+
*
|
|
10747
|
+
* TestBed.configureTestingModule({
|
|
10748
|
+
* providers: [TnToastTesting.providers(toastMock)],
|
|
10749
|
+
* });
|
|
10750
|
+
* ```
|
|
10751
|
+
*/
|
|
10752
|
+
class TnToastTesting {
|
|
10753
|
+
/**
|
|
10754
|
+
* Returns providers that replace TnToastService with the given mock.
|
|
10755
|
+
*/
|
|
10756
|
+
static providers(mock) {
|
|
10757
|
+
return [{ provide: TnToastService, useValue: mock }];
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
|
|
10454
10761
|
class TnKeyboardShortcutService {
|
|
10455
10762
|
shortcuts = new Map();
|
|
10456
10763
|
globalEnabled = true;
|
|
@@ -11054,5 +11361,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
11054
11361
|
* Generated bundle index. Do not edit.
|
|
11055
11362
|
*/
|
|
11056
11363
|
|
|
11057
|
-
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, 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, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, 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 };
|
|
11364
|
+
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, 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, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTabsComponent, TnTabsHarness, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
|
|
11058
11365
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|