@truenas/ui-components 0.5.0 → 0.5.2
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.
|
@@ -2838,7 +2838,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2838
2838
|
*/
|
|
2839
2839
|
const INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]';
|
|
2840
2840
|
class TnTooltipDirective {
|
|
2841
|
-
|
|
2841
|
+
// Nullable by contract: consumers bind expressions like [tnTooltip]="reason ?? null",
|
|
2842
|
+
// and under strictTemplates a string-only input would reject them at compile time.
|
|
2843
|
+
// The transform normalises at the boundary so every read site still sees a string.
|
|
2844
|
+
message = input('', { ...(ngDevMode ? { debugName: "message" } : {}), alias: 'tnTooltip',
|
|
2845
|
+
transform: (value) => value ?? '' });
|
|
2842
2846
|
position = input('above', { ...(ngDevMode ? { debugName: "position" } : {}), alias: 'tnTooltipPosition' });
|
|
2843
2847
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'tnTooltipDisabled' });
|
|
2844
2848
|
showDelay = input(0, { ...(ngDevMode ? { debugName: "showDelay" } : {}), alias: 'tnTooltipShowDelay' });
|
|
@@ -5100,9 +5104,12 @@ class TnChipInputComponent {
|
|
|
5100
5104
|
/**
|
|
5101
5105
|
* Semantic test-id base. The library prepends the `chip-input` element type
|
|
5102
5106
|
* (e.g. `testId="tags"` → `chip-input-tags`); each chip and suggestion is
|
|
5103
|
-
* scoped beneath it.
|
|
5107
|
+
* scoped beneath it. Falls back to the bound control name when unset, so
|
|
5108
|
+
* `<tn-chip-input formControlName="isnsServers">` emits `chip-input-isns-servers`.
|
|
5104
5109
|
*/
|
|
5105
5110
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
5111
|
+
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
5112
|
+
resolvedTestId = controlTestId(this.testId);
|
|
5106
5113
|
/** Emits the committed value whenever a chip is added. */
|
|
5107
5114
|
chipAdded = output();
|
|
5108
5115
|
/** Emits the removed value whenever a chip is removed. */
|
|
@@ -5310,13 +5317,52 @@ class TnChipInputComponent {
|
|
|
5310
5317
|
const match = this.optionList().find((option) => this.valueMatches(option.value, value));
|
|
5311
5318
|
return match ? match.label : String(value);
|
|
5312
5319
|
}
|
|
5313
|
-
/**
|
|
5320
|
+
/**
|
|
5321
|
+
* Scopes a per-chip test id beneath the component's base.
|
|
5322
|
+
*
|
|
5323
|
+
* The discriminator is the value itself when primitive, else the matching
|
|
5324
|
+
* option's label — `String(value)` on an object is `[object Object]`, which
|
|
5325
|
+
* would stamp an identical id on every chip. Duplicates are worse than
|
|
5326
|
+
* absence for automation, so an object value with no option to name it yet
|
|
5327
|
+
* (options still loading) stays attribute-free rather than colliding. A primitive
|
|
5328
|
+
* that normalizes away is dropped for the same reason — see
|
|
5329
|
+
* {@link discriminatedTestId}.
|
|
5330
|
+
*/
|
|
5314
5331
|
chipTestId(value) {
|
|
5315
|
-
const base = this.
|
|
5316
|
-
if (
|
|
5332
|
+
const base = this.resolvedTestId();
|
|
5333
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
5334
|
+
return this.discriminatedTestId(scopeTestId(base, value));
|
|
5335
|
+
}
|
|
5336
|
+
const match = this.optionList().find((option) => this.valueMatches(option.value, value));
|
|
5337
|
+
return match ? this.discriminatedTestId(scopeTestId(base, match.label)) : undefined;
|
|
5338
|
+
}
|
|
5339
|
+
/**
|
|
5340
|
+
* Scopes a per-suggestion test id beneath the component's base, via the shared
|
|
5341
|
+
* dropdown-option derivation. Unlike `tn-select` / `tn-autocomplete`, which
|
|
5342
|
+
* emit an unscoped `option-<value>` when they have no base, an unidentified
|
|
5343
|
+
* chip-input stays attribute-free — its rows carry no page-unique id, so
|
|
5344
|
+
* emitting one would invite collisions between inputs.
|
|
5345
|
+
*/
|
|
5346
|
+
suggestionTestId(option) {
|
|
5347
|
+
return this.discriminatedTestId(optionTestId(this.resolvedTestId(), option));
|
|
5348
|
+
}
|
|
5349
|
+
/**
|
|
5350
|
+
* Keeps a scoped id only when both halves of it survive normalization.
|
|
5351
|
+
*
|
|
5352
|
+
* The base has to be usable: an unscoped `chip-<value>` would collide across every
|
|
5353
|
+
* chip-input on the page, so unidentified inputs stay attribute-free. The
|
|
5354
|
+
* discriminator has to contribute a segment of its own, because
|
|
5355
|
+
* `kebabTestSegment` drops every run of non-alphanumerics — a value like `*`, `**`
|
|
5356
|
+
* or a CJK-only tag normalizes to nothing and collapses the id back to the bare
|
|
5357
|
+
* base, stamping the same id on every chip. That is the duplicate-id case the
|
|
5358
|
+
* object-value path already avoids, reached through a primitive instead.
|
|
5359
|
+
*/
|
|
5360
|
+
discriminatedTestId(scoped) {
|
|
5361
|
+
const baseId = composeTestId(undefined, this.resolvedTestId());
|
|
5362
|
+
if (baseId === '' || composeTestId(undefined, scoped) === baseId) {
|
|
5317
5363
|
return undefined;
|
|
5318
5364
|
}
|
|
5319
|
-
return
|
|
5365
|
+
return scoped;
|
|
5320
5366
|
}
|
|
5321
5367
|
// ── Internal ──
|
|
5322
5368
|
isCommitKey(event) {
|
|
@@ -5435,7 +5481,7 @@ class TnChipInputComponent {
|
|
|
5435
5481
|
useExisting: forwardRef(() => TnChipInputComponent),
|
|
5436
5482
|
multi: true,
|
|
5437
5483
|
},
|
|
5438
|
-
], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }, { propertyName: "dropdownTemplate", first: true, predicate: ["dropdownTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Click-to-focus is a pointer affordance only; keyboard users Tab straight to\n the real <input> inside, so the container itself is not a focus target. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<div\n #container\n class=\"tn-chip-input\"\n [class.tn-chip-input--disabled]=\"isDisabled()\"\n (click)=\"focusInput()\"\n>\n @for (value of values(); track $index) {\n <tn-chip\n class=\"tn-chip-input__chip\"\n color=\"secondary\"\n [label]=\"displayLabel(value)\"\n [closable]=\"!isDisabled()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"chipTestId(value)\"\n (onClose)=\"removeChip($index)\"\n />\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"tn-chip-input__field\"\n role=\"combobox\"\n autocomplete=\"off\"\n tnTestIdType=\"chip-input\"\n [tnTestId]=\"
|
|
5484
|
+
], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }, { propertyName: "dropdownTemplate", first: true, predicate: ["dropdownTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Click-to-focus is a pointer affordance only; keyboard users Tab straight to\n the real <input> inside, so the container itself is not a focus target. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<div\n #container\n class=\"tn-chip-input\"\n [class.tn-chip-input--disabled]=\"isDisabled()\"\n (click)=\"focusInput()\"\n>\n @for (value of values(); track $index) {\n <tn-chip\n class=\"tn-chip-input__chip\"\n color=\"secondary\"\n [label]=\"displayLabel(value)\"\n [closable]=\"!isDisabled()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"chipTestId(value)\"\n (onClose)=\"removeChip($index)\"\n />\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"tn-chip-input__field\"\n role=\"combobox\"\n autocomplete=\"off\"\n tnTestIdType=\"chip-input\"\n [tnTestId]=\"resolvedTestId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"fieldAria.labelledby()\"\n [attr.aria-describedby]=\"fieldAria.describedby()\"\n [attr.aria-invalid]=\"fieldAria.invalid()\"\n [attr.aria-required]=\"fieldAria.required()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? uid + '-listbox' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n</div>\n\n<!-- Suggestion dropdown \u2014 portaled via CDK overlay so it escapes ancestor\n overflow/clipping. Attach/detach is driven by open()/close(). -->\n<ng-template #dropdownTemplate>\n <div\n class=\"tn-chip-input__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-listbox'\"\n >\n @for (suggestion of filteredSuggestions(); track $index) {\n <!-- Options are tabindex=\"-1\" and never focused: keyboard selection goes\n through the input's aria-activedescendant + the Enter branch in\n onKeydown, so a key handler here would be dead code. Click is a\n pointer-only affordance. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-chip-input__option\"\n role=\"option\"\n tabindex=\"-1\"\n tnTestIdType=\"option\"\n [tnTestId]=\"suggestionTestId(suggestion)\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"onSuggestionMousedown($event)\"\n (click)=\"onSuggestionClick(suggestion)\"\n >\n {{ suggestion.label }}\n </div>\n }\n </div>\n</ng-template>\n", styles: [".tn-chip-input{display:flex;flex-wrap:wrap;align-items:center;gap:.375rem;min-height:2.5rem;padding:.375rem .5rem;box-sizing:border-box;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;cursor:text;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-chip-input:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-chip-input--disabled{background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-chip-input__chip{flex-shrink:0}.tn-chip-input__field{flex:1;min-width:120px;padding:.25rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;outline:none}.tn-chip-input__field::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-chip-input__field:disabled{cursor:not-allowed}.tn-chip-input__dropdown{max-height:var(--tn-chip-input-panel-max-height, 320px);overflow-y:auto;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.tn-chip-input__option{padding:.5rem .75rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;color:var(--tn-fg1, #212529);cursor:pointer}.tn-chip-input__option:hover,.tn-chip-input__option.highlighted{background-color:var(--tn-bg3, #f3f4f6)}@media(prefers-reduced-motion:reduce){.tn-chip-input{transition:none}}@media(prefers-contrast:high){.tn-chip-input{border-width:2px}}\n"], dependencies: [{ kind: "component", type: TnChipComponent, selector: "tn-chip", inputs: ["label", "icon", "closable", "disabled", "color", "testId"], outputs: ["onClose", "onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
5439
5485
|
}
|
|
5440
5486
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnChipInputComponent, decorators: [{
|
|
5441
5487
|
type: Component,
|
|
@@ -5445,7 +5491,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
5445
5491
|
useExisting: forwardRef(() => TnChipInputComponent),
|
|
5446
5492
|
multi: true,
|
|
5447
5493
|
},
|
|
5448
|
-
], template: "<!-- Click-to-focus is a pointer affordance only; keyboard users Tab straight to\n the real <input> inside, so the container itself is not a focus target. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<div\n #container\n class=\"tn-chip-input\"\n [class.tn-chip-input--disabled]=\"isDisabled()\"\n (click)=\"focusInput()\"\n>\n @for (value of values(); track $index) {\n <tn-chip\n class=\"tn-chip-input__chip\"\n color=\"secondary\"\n [label]=\"displayLabel(value)\"\n [closable]=\"!isDisabled()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"chipTestId(value)\"\n (onClose)=\"removeChip($index)\"\n />\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"tn-chip-input__field\"\n role=\"combobox\"\n autocomplete=\"off\"\n tnTestIdType=\"chip-input\"\n [tnTestId]=\"
|
|
5494
|
+
], template: "<!-- Click-to-focus is a pointer affordance only; keyboard users Tab straight to\n the real <input> inside, so the container itself is not a focus target. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<div\n #container\n class=\"tn-chip-input\"\n [class.tn-chip-input--disabled]=\"isDisabled()\"\n (click)=\"focusInput()\"\n>\n @for (value of values(); track $index) {\n <tn-chip\n class=\"tn-chip-input__chip\"\n color=\"secondary\"\n [label]=\"displayLabel(value)\"\n [closable]=\"!isDisabled()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"chipTestId(value)\"\n (onClose)=\"removeChip($index)\"\n />\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"tn-chip-input__field\"\n role=\"combobox\"\n autocomplete=\"off\"\n tnTestIdType=\"chip-input\"\n [tnTestId]=\"resolvedTestId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"fieldAria.labelledby()\"\n [attr.aria-describedby]=\"fieldAria.describedby()\"\n [attr.aria-invalid]=\"fieldAria.invalid()\"\n [attr.aria-required]=\"fieldAria.required()\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? uid + '-listbox' : null\"\n [attr.aria-activedescendant]=\"highlightedIndex() >= 0 ? uid + '-option-' + highlightedIndex() : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n</div>\n\n<!-- Suggestion dropdown \u2014 portaled via CDK overlay so it escapes ancestor\n overflow/clipping. Attach/detach is driven by open()/close(). -->\n<ng-template #dropdownTemplate>\n <div\n class=\"tn-chip-input__dropdown\"\n role=\"listbox\"\n [attr.id]=\"uid + '-listbox'\"\n >\n @for (suggestion of filteredSuggestions(); track $index) {\n <!-- Options are tabindex=\"-1\" and never focused: keyboard selection goes\n through the input's aria-activedescendant + the Enter branch in\n onKeydown, so a key handler here would be dead code. Click is a\n pointer-only affordance. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <div\n class=\"tn-chip-input__option\"\n role=\"option\"\n tabindex=\"-1\"\n tnTestIdType=\"option\"\n [tnTestId]=\"suggestionTestId(suggestion)\"\n [class.highlighted]=\"highlightedIndex() === $index\"\n [attr.id]=\"uid + '-option-' + $index\"\n [attr.aria-selected]=\"highlightedIndex() === $index\"\n (mousedown)=\"onSuggestionMousedown($event)\"\n (click)=\"onSuggestionClick(suggestion)\"\n >\n {{ suggestion.label }}\n </div>\n }\n </div>\n</ng-template>\n", styles: [".tn-chip-input{display:flex;flex-wrap:wrap;align-items:center;gap:.375rem;min-height:2.5rem;padding:.375rem .5rem;box-sizing:border-box;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;cursor:text;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-chip-input:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px color-mix(in srgb,var(--tn-primary, #007bff) 25%,transparent)}.tn-chip-input--disabled{background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-chip-input__chip{flex-shrink:0}.tn-chip-input__field{flex:1;min-width:120px;padding:.25rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;outline:none}.tn-chip-input__field::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-chip-input__field:disabled{cursor:not-allowed}.tn-chip-input__dropdown{max-height:var(--tn-chip-input-panel-max-height, 320px);overflow-y:auto;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.tn-chip-input__option{padding:.5rem .75rem;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;font-size:1rem;color:var(--tn-fg1, #212529);cursor:pointer}.tn-chip-input__option:hover,.tn-chip-input__option.highlighted{background-color:var(--tn-bg3, #f3f4f6)}@media(prefers-reduced-motion:reduce){.tn-chip-input{transition:none}}@media(prefers-contrast:high){.tn-chip-input{border-width:2px}}\n"] }]
|
|
5449
5495
|
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], separatorKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "separatorKeys", required: false }] }], addOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "addOnBlur", required: false }] }], allowCustomValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCustomValue", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], maxChips: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxChips", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], chipAdded: [{ type: i0.Output, args: ["chipAdded"] }], chipRemoved: [{ type: i0.Output, args: ["chipRemoved"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], container: [{ type: i0.ViewChild, args: ['container', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdownTemplate', { isSignal: true }] }] } });
|
|
5450
5496
|
|
|
5451
5497
|
/**
|