@truenas/ui-components 0.3.3 → 0.3.5
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,7 +1,7 @@
|
|
|
1
1
|
import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
|
|
2
2
|
import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, inject, Renderer2, ElementRef, effect, Directive, ViewContainerRef, output, viewChild, signal, untracked, isDevMode, forwardRef, model, afterNextRender, Injectable, contentChildren, Pipe, HostListener, linkedSignal, contentChild, ChangeDetectorRef, DestroyRef,
|
|
4
|
+
import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, inject, Renderer2, ElementRef, effect, Directive, ViewContainerRef, output, viewChild, signal, untracked, isDevMode, forwardRef, model, afterNextRender, Injectable, contentChildren, Pipe, HostListener, linkedSignal, contentChild, TemplateRef, ChangeDetectorRef, DestroyRef, isSignal, IterableDiffers, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
|
|
5
5
|
import * as i1$3 from '@angular/forms';
|
|
6
6
|
import { NG_VALUE_ACCESSOR, FormsModule, NgControl, Validators } from '@angular/forms';
|
|
7
7
|
import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
|
|
@@ -261,13 +261,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
261
261
|
}]
|
|
262
262
|
}], ctorParameters: () => [], propDecorators: { testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTestId", required: false }] }], tnTestIdType: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnTestIdType", required: false }] }] } });
|
|
263
263
|
|
|
264
|
-
let nextId$
|
|
264
|
+
let nextId$2 = 0;
|
|
265
265
|
class TnAutocompleteComponent {
|
|
266
266
|
elementRef = inject(ElementRef);
|
|
267
267
|
overlay = inject(Overlay);
|
|
268
268
|
viewContainerRef = inject(ViewContainerRef);
|
|
269
269
|
/** Unique instance ID for ARIA linkage */
|
|
270
|
-
uid = `tn-autocomplete-${nextId$
|
|
270
|
+
uid = `tn-autocomplete-${nextId$2++}`;
|
|
271
271
|
/**
|
|
272
272
|
* All available options. The `label` is displayed; the `value` is committed
|
|
273
273
|
* to the form control. A written value is resolved back to its option's
|
|
@@ -3208,7 +3208,7 @@ function unitExponent(unit) {
|
|
|
3208
3208
|
// Module-level counter for deterministic, unique instance ids (matches the
|
|
3209
3209
|
// tn-autocomplete convention). Deterministic ids are SSR/hydration-safe and
|
|
3210
3210
|
// stable across snapshots, unlike a Math.random() seed.
|
|
3211
|
-
let nextId = 0;
|
|
3211
|
+
let nextId$1 = 0;
|
|
3212
3212
|
class TnInputComponent {
|
|
3213
3213
|
inputEl = viewChild.required('inputEl');
|
|
3214
3214
|
inputType = input(InputType.PlainText, ...(ngDevMode ? [{ debugName: "inputType" }] : []));
|
|
@@ -3372,7 +3372,7 @@ class TnInputComponent {
|
|
|
3372
3372
|
// Unique per instance: a hard-coded id produced duplicate ids when multiple
|
|
3373
3373
|
// tn-inputs share a page (invalid HTML, and it breaks any future label `for=`,
|
|
3374
3374
|
// aria-describedby, or getElementById targeting this input).
|
|
3375
|
-
id = `tn-input-${nextId++}`;
|
|
3375
|
+
id = `tn-input-${nextId$1++}`;
|
|
3376
3376
|
// Protected: the display string is owned by the component and driven through the
|
|
3377
3377
|
// CVA flow (writeValue / onValueChange). External writes would bypass that flow.
|
|
3378
3378
|
// A signal so the `[value]` binding reflects writeValue reactively — a plain field
|
|
@@ -4368,6 +4368,536 @@ class TnChipHarness extends ComponentHarness {
|
|
|
4368
4368
|
}
|
|
4369
4369
|
}
|
|
4370
4370
|
|
|
4371
|
+
let nextId = 0;
|
|
4372
|
+
/**
|
|
4373
|
+
* An editable, multi-value chip input — tokenized entry where typed text
|
|
4374
|
+
* becomes removable `tn-chip`s alongside an inline text field. Text is
|
|
4375
|
+
* committed to a chip on Enter (or a configurable separator key); Backspace on
|
|
4376
|
+
* an empty field removes the last chip.
|
|
4377
|
+
*
|
|
4378
|
+
* It is a `ControlValueAccessor` over `string[]`, so it drops into a reactive
|
|
4379
|
+
* or template-driven form (`[formControl]`, `[(ngModel)]`) and slots into a
|
|
4380
|
+
* `tn-form-field` as a real projected control — the field's required/error
|
|
4381
|
+
* inference reads this control directly.
|
|
4382
|
+
*
|
|
4383
|
+
* Suggestions are optional: pass `[suggestions]` for a static list, or drive
|
|
4384
|
+
* them asynchronously by listening to `(searchChange)` and updating
|
|
4385
|
+
* `[suggestions]` as results arrive. The dropdown is portaled through a CDK
|
|
4386
|
+
* overlay so it escapes any ancestor `overflow: hidden` (cards, side panels).
|
|
4387
|
+
*
|
|
4388
|
+
* @example
|
|
4389
|
+
* ```html
|
|
4390
|
+
* <tn-form-field label="Tags">
|
|
4391
|
+
* <tn-chip-input [formControl]="tags" placeholder="Add a tag…" />
|
|
4392
|
+
* </tn-form-field>
|
|
4393
|
+
* ```
|
|
4394
|
+
*/
|
|
4395
|
+
class TnChipInputComponent {
|
|
4396
|
+
overlay = inject(Overlay);
|
|
4397
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
4398
|
+
/** Unique instance id for ARIA linkage between the input and its dropdown. */
|
|
4399
|
+
uid = `tn-chip-input-${nextId++}`;
|
|
4400
|
+
/** Placeholder shown in the text field when it is empty. */
|
|
4401
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
4402
|
+
/** Disables the whole control — chips become non-removable and the field read-only. */
|
|
4403
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
4404
|
+
/**
|
|
4405
|
+
* Keys that commit the current text as a chip, in addition to `Enter`.
|
|
4406
|
+
* Defaults to `Enter` plus comma. A separator key press never inserts its
|
|
4407
|
+
* own character.
|
|
4408
|
+
*/
|
|
4409
|
+
separatorKeys = input(['Enter', ','], ...(ngDevMode ? [{ debugName: "separatorKeys" }] : []));
|
|
4410
|
+
/** Commit a pending (non-empty) text value as a chip when the field loses focus. */
|
|
4411
|
+
addOnBlur = input(false, ...(ngDevMode ? [{ debugName: "addOnBlur" }] : []));
|
|
4412
|
+
/**
|
|
4413
|
+
* Allow the same value to be added more than once. Off by default.
|
|
4414
|
+
* Duplicate detection is exact-match (case-sensitive), so with this off
|
|
4415
|
+
* `Angular` and `angular` are still distinct values — only suggestion
|
|
4416
|
+
* *filtering* is case-insensitive.
|
|
4417
|
+
*/
|
|
4418
|
+
allowDuplicates = input(false, ...(ngDevMode ? [{ debugName: "allowDuplicates" }] : []));
|
|
4419
|
+
/** Hard cap on the number of chips; `undefined` means no limit. */
|
|
4420
|
+
maxChips = input(undefined, ...(ngDevMode ? [{ debugName: "maxChips" }] : []));
|
|
4421
|
+
/**
|
|
4422
|
+
* Optional suggestion list. When non-empty, a dropdown offers entries that
|
|
4423
|
+
* match the typed text and are not already selected. For async sources,
|
|
4424
|
+
* update this in response to `(searchChange)`.
|
|
4425
|
+
*/
|
|
4426
|
+
suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
4427
|
+
/** Accessible name for the text field. Leave unset inside a `tn-form-field`. */
|
|
4428
|
+
ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
4429
|
+
/**
|
|
4430
|
+
* Semantic test-id base. The library prepends the `chip-input` element type
|
|
4431
|
+
* (e.g. `testId="tags"` → `chip-input-tags`); each chip and suggestion is
|
|
4432
|
+
* scoped beneath it.
|
|
4433
|
+
*/
|
|
4434
|
+
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
4435
|
+
/** Emits the committed value whenever a chip is added. */
|
|
4436
|
+
chipAdded = output();
|
|
4437
|
+
/** Emits the removed value whenever a chip is removed. */
|
|
4438
|
+
chipRemoved = output();
|
|
4439
|
+
/**
|
|
4440
|
+
* Emits the current text as the user types (not on programmatic writes or
|
|
4441
|
+
* chip commits). Drive server-side suggestion lookups from this; debounce in
|
|
4442
|
+
* the consumer if the lookup is expensive.
|
|
4443
|
+
*/
|
|
4444
|
+
searchChange = output();
|
|
4445
|
+
container = viewChild.required('container');
|
|
4446
|
+
inputEl = viewChild.required('inputEl');
|
|
4447
|
+
dropdownTemplate = viewChild.required('dropdownTemplate');
|
|
4448
|
+
/** Committed chip values — the form model. */
|
|
4449
|
+
values = signal([], ...(ngDevMode ? [{ debugName: "values" }] : []));
|
|
4450
|
+
/** Current text in the field. */
|
|
4451
|
+
inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : []));
|
|
4452
|
+
/** Whether the suggestion dropdown is open. */
|
|
4453
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
4454
|
+
/** Index of the keyboard-highlighted suggestion, or -1. */
|
|
4455
|
+
highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
4456
|
+
/** Whether the text field currently holds focus — gates async re-opening. */
|
|
4457
|
+
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
4458
|
+
/** CVA disabled state pushed by the form. */
|
|
4459
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
|
|
4460
|
+
/** Combined disabled state from the input and the form. */
|
|
4461
|
+
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
4462
|
+
/** Whether another chip may still be added under `maxChips`. */
|
|
4463
|
+
canAddMore = computed(() => {
|
|
4464
|
+
const max = this.maxChips();
|
|
4465
|
+
return max === undefined || this.values().length < max;
|
|
4466
|
+
}, ...(ngDevMode ? [{ debugName: "canAddMore" }] : []));
|
|
4467
|
+
/** Suggestions that match the typed text and are not already selected. */
|
|
4468
|
+
filteredSuggestions = computed(() => {
|
|
4469
|
+
const selected = new Set(this.values());
|
|
4470
|
+
const term = this.inputValue().trim().toLowerCase();
|
|
4471
|
+
return this.suggestions().filter((suggestion) => {
|
|
4472
|
+
if (selected.has(suggestion)) {
|
|
4473
|
+
return false;
|
|
4474
|
+
}
|
|
4475
|
+
return term === '' || suggestion.toLowerCase().includes(term);
|
|
4476
|
+
});
|
|
4477
|
+
}, ...(ngDevMode ? [{ debugName: "filteredSuggestions" }] : []));
|
|
4478
|
+
onChange = () => { };
|
|
4479
|
+
onTouched = () => { };
|
|
4480
|
+
overlayRef;
|
|
4481
|
+
overlaySubs = [];
|
|
4482
|
+
constructor() {
|
|
4483
|
+
// Async suggestions: when the user types, onInput runs syncDropdown()
|
|
4484
|
+
// against the still-stale list and leaves the panel closed; results land a
|
|
4485
|
+
// tick later via [suggestions]. Re-open the panel once fresh matches arrive
|
|
4486
|
+
// while the field is focused and actively searching. This only ever opens
|
|
4487
|
+
// (never closes), so it doesn't fight Escape, blur, or the post-commit close
|
|
4488
|
+
// — those stay shut until the suggestion set next changes.
|
|
4489
|
+
effect(() => {
|
|
4490
|
+
const hasMatches = this.filteredSuggestions().length > 0;
|
|
4491
|
+
untracked(() => {
|
|
4492
|
+
const activelySearching = this.focused()
|
|
4493
|
+
&& this.inputValue().trim() !== ''
|
|
4494
|
+
&& this.canAddMore()
|
|
4495
|
+
&& !this.isDisabled();
|
|
4496
|
+
if (hasMatches && activelySearching) {
|
|
4497
|
+
this.open();
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
});
|
|
4501
|
+
}
|
|
4502
|
+
ngOnDestroy() {
|
|
4503
|
+
this.detachOverlay();
|
|
4504
|
+
}
|
|
4505
|
+
// ── ControlValueAccessor ──
|
|
4506
|
+
writeValue(value) {
|
|
4507
|
+
// Reflect the model verbatim — deliberately NOT clamped to maxChips. A form
|
|
4508
|
+
// may legitimately seed more values than the cap; silently dropping them
|
|
4509
|
+
// would lose data. The cap only blocks further user-driven additions.
|
|
4510
|
+
this.values.set(Array.isArray(value) ? [...value] : []);
|
|
4511
|
+
}
|
|
4512
|
+
registerOnChange(fn) {
|
|
4513
|
+
this.onChange = fn;
|
|
4514
|
+
}
|
|
4515
|
+
registerOnTouched(fn) {
|
|
4516
|
+
this.onTouched = fn;
|
|
4517
|
+
}
|
|
4518
|
+
setDisabledState(isDisabled) {
|
|
4519
|
+
this.formDisabled.set(isDisabled);
|
|
4520
|
+
if (isDisabled) {
|
|
4521
|
+
this.close();
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
// ── Template handlers ──
|
|
4525
|
+
/** Clicking anywhere in the container focuses the text field. */
|
|
4526
|
+
focusInput() {
|
|
4527
|
+
if (!this.isDisabled()) {
|
|
4528
|
+
this.inputEl().nativeElement.focus();
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
onInput(event) {
|
|
4532
|
+
const value = event.target.value;
|
|
4533
|
+
this.inputValue.set(value);
|
|
4534
|
+
this.searchChange.emit(value);
|
|
4535
|
+
this.highlightedIndex.set(-1);
|
|
4536
|
+
this.syncDropdown();
|
|
4537
|
+
}
|
|
4538
|
+
onFocus() {
|
|
4539
|
+
this.focused.set(true);
|
|
4540
|
+
this.syncDropdown();
|
|
4541
|
+
}
|
|
4542
|
+
onBlur() {
|
|
4543
|
+
this.focused.set(false);
|
|
4544
|
+
if (this.addOnBlur()) {
|
|
4545
|
+
this.addChip(this.inputValue());
|
|
4546
|
+
}
|
|
4547
|
+
this.close();
|
|
4548
|
+
this.onTouched();
|
|
4549
|
+
}
|
|
4550
|
+
onKeydown(event) {
|
|
4551
|
+
// Mid-IME-composition (Japanese/Chinese/Korean), the Enter that confirms a
|
|
4552
|
+
// candidate also fires keydown with isComposing=true — committing here would
|
|
4553
|
+
// swallow the confirmation and chip a half-composed value. Let it through.
|
|
4554
|
+
if (event.isComposing) {
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
const suggestions = this.filteredSuggestions();
|
|
4558
|
+
if (event.key === 'ArrowDown') {
|
|
4559
|
+
if (suggestions.length && this.canAddMore()) {
|
|
4560
|
+
event.preventDefault();
|
|
4561
|
+
this.open();
|
|
4562
|
+
this.highlightedIndex.set((this.highlightedIndex() + 1) % suggestions.length);
|
|
4563
|
+
this.scrollToHighlighted();
|
|
4564
|
+
}
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
if (event.key === 'ArrowUp') {
|
|
4568
|
+
if (suggestions.length && this.isOpen()) {
|
|
4569
|
+
event.preventDefault();
|
|
4570
|
+
const next = this.highlightedIndex() - 1;
|
|
4571
|
+
this.highlightedIndex.set(next < 0 ? suggestions.length - 1 : next);
|
|
4572
|
+
this.scrollToHighlighted();
|
|
4573
|
+
}
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
if (event.key === 'Escape') {
|
|
4577
|
+
if (this.isOpen()) {
|
|
4578
|
+
event.preventDefault();
|
|
4579
|
+
this.close();
|
|
4580
|
+
}
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4583
|
+
if (this.isCommitKey(event)) {
|
|
4584
|
+
event.preventDefault();
|
|
4585
|
+
const idx = this.highlightedIndex();
|
|
4586
|
+
if (this.isOpen() && idx >= 0 && idx < suggestions.length) {
|
|
4587
|
+
this.addChip(suggestions[idx]);
|
|
4588
|
+
}
|
|
4589
|
+
else {
|
|
4590
|
+
this.addChip(this.inputValue());
|
|
4591
|
+
}
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
// Backspace on an empty field removes the last chip — the standard
|
|
4595
|
+
// chip-input affordance for quick correction.
|
|
4596
|
+
if (event.key === 'Backspace' && this.inputValue() === '' && this.values().length > 0) {
|
|
4597
|
+
event.preventDefault();
|
|
4598
|
+
this.removeChip(this.values().length - 1);
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
onSuggestionClick(suggestion) {
|
|
4602
|
+
this.addChip(suggestion);
|
|
4603
|
+
this.inputEl().nativeElement.focus();
|
|
4604
|
+
}
|
|
4605
|
+
/** Prevents the option `mousedown` from blurring the input before the click lands. */
|
|
4606
|
+
onSuggestionMousedown(event) {
|
|
4607
|
+
event.preventDefault();
|
|
4608
|
+
}
|
|
4609
|
+
removeChip(index) {
|
|
4610
|
+
if (this.isDisabled()) {
|
|
4611
|
+
return;
|
|
4612
|
+
}
|
|
4613
|
+
const removed = this.values()[index];
|
|
4614
|
+
if (removed === undefined) {
|
|
4615
|
+
return;
|
|
4616
|
+
}
|
|
4617
|
+
this.values.update((values) => values.filter((_, i) => i !== index));
|
|
4618
|
+
this.onChange(this.values());
|
|
4619
|
+
this.onTouched();
|
|
4620
|
+
this.chipRemoved.emit(removed);
|
|
4621
|
+
// Removing via a chip's close button leaves focus on the (now-destroyed)
|
|
4622
|
+
// button; return it to the field so keyboard users stay oriented. The
|
|
4623
|
+
// Backspace path is already focused here, so this is a harmless no-op there.
|
|
4624
|
+
this.inputEl().nativeElement.focus();
|
|
4625
|
+
this.syncDropdown();
|
|
4626
|
+
}
|
|
4627
|
+
/** Scopes a per-chip test id beneath the component's base. */
|
|
4628
|
+
chipTestId(value) {
|
|
4629
|
+
const base = this.testId();
|
|
4630
|
+
if (base === undefined) {
|
|
4631
|
+
return undefined;
|
|
4632
|
+
}
|
|
4633
|
+
return [...(Array.isArray(base) ? base : [base]), value];
|
|
4634
|
+
}
|
|
4635
|
+
// ── Internal ──
|
|
4636
|
+
isCommitKey(event) {
|
|
4637
|
+
return event.key === 'Enter' || this.separatorKeys().includes(event.key);
|
|
4638
|
+
}
|
|
4639
|
+
addChip(raw) {
|
|
4640
|
+
const value = (raw ?? '').trim();
|
|
4641
|
+
if (!value || this.isDisabled() || !this.canAddMore()) {
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
if (!this.allowDuplicates() && this.values().includes(value)) {
|
|
4645
|
+
this.clearInput();
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
this.values.update((values) => [...values, value]);
|
|
4649
|
+
this.onChange(this.values());
|
|
4650
|
+
this.onTouched();
|
|
4651
|
+
this.chipAdded.emit(value);
|
|
4652
|
+
this.clearInput();
|
|
4653
|
+
}
|
|
4654
|
+
clearInput() {
|
|
4655
|
+
this.inputValue.set('');
|
|
4656
|
+
this.inputEl().nativeElement.value = '';
|
|
4657
|
+
this.close();
|
|
4658
|
+
}
|
|
4659
|
+
/**
|
|
4660
|
+
* Opens the dropdown when there is something to show, closes it otherwise.
|
|
4661
|
+
* Stays closed once the chip cap is reached — suggesting rows that
|
|
4662
|
+
* `addChip()` would reject is misleading.
|
|
4663
|
+
*/
|
|
4664
|
+
syncDropdown() {
|
|
4665
|
+
if (this.filteredSuggestions().length > 0 && this.canAddMore() && !this.isDisabled()) {
|
|
4666
|
+
this.open();
|
|
4667
|
+
}
|
|
4668
|
+
else {
|
|
4669
|
+
this.close();
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
/** Keeps the keyboard-highlighted suggestion visible within the scrolling panel. */
|
|
4673
|
+
scrollToHighlighted() {
|
|
4674
|
+
const idx = this.highlightedIndex();
|
|
4675
|
+
const options = this.overlayRef?.overlayElement
|
|
4676
|
+
?.querySelectorAll('.tn-chip-input__option');
|
|
4677
|
+
options?.[idx]?.scrollIntoView({ block: 'nearest' });
|
|
4678
|
+
}
|
|
4679
|
+
open() {
|
|
4680
|
+
if (this.isOpen() || this.isDisabled()) {
|
|
4681
|
+
return;
|
|
4682
|
+
}
|
|
4683
|
+
this.isOpen.set(true);
|
|
4684
|
+
this.attachOverlay();
|
|
4685
|
+
}
|
|
4686
|
+
close() {
|
|
4687
|
+
this.isOpen.set(false);
|
|
4688
|
+
this.highlightedIndex.set(-1);
|
|
4689
|
+
this.detachOverlay();
|
|
4690
|
+
}
|
|
4691
|
+
attachOverlay() {
|
|
4692
|
+
const anchor = this.container().nativeElement;
|
|
4693
|
+
const positionStrategy = this.overlay
|
|
4694
|
+
.position()
|
|
4695
|
+
.flexibleConnectedTo(anchor)
|
|
4696
|
+
.withPositions([
|
|
4697
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
4698
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
4699
|
+
]);
|
|
4700
|
+
this.overlayRef = this.overlay.create({
|
|
4701
|
+
positionStrategy,
|
|
4702
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
4703
|
+
hasBackdrop: false,
|
|
4704
|
+
width: anchor.offsetWidth,
|
|
4705
|
+
});
|
|
4706
|
+
this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
|
|
4707
|
+
this.overlaySubs.push(this.overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
4708
|
+
const target = event.target;
|
|
4709
|
+
if (target && anchor.contains(target)) {
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
this.close();
|
|
4713
|
+
}));
|
|
4714
|
+
}
|
|
4715
|
+
detachOverlay() {
|
|
4716
|
+
this.overlaySubs.forEach((sub) => sub.unsubscribe());
|
|
4717
|
+
this.overlaySubs = [];
|
|
4718
|
+
this.overlayRef?.dispose();
|
|
4719
|
+
this.overlayRef = undefined;
|
|
4720
|
+
}
|
|
4721
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnChipInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4722
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnChipInputComponent, isStandalone: true, selector: "tn-chip-input", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, separatorKeys: { classPropertyName: "separatorKeys", publicName: "separatorKeys", isSignal: true, isRequired: false, transformFunction: null }, addOnBlur: { classPropertyName: "addOnBlur", publicName: "addOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, maxChips: { classPropertyName: "maxChips", publicName: "maxChips", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", 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: { chipAdded: "chipAdded", chipRemoved: "chipRemoved", searchChange: "searchChange" }, providers: [
|
|
4723
|
+
{
|
|
4724
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4725
|
+
useExisting: forwardRef(() => TnChipInputComponent),
|
|
4726
|
+
multi: true,
|
|
4727
|
+
},
|
|
4728
|
+
], 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]=\"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]=\"testId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\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 suggestion) {\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]=\"chipTestId(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 }}\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"] }] });
|
|
4729
|
+
}
|
|
4730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnChipInputComponent, decorators: [{
|
|
4731
|
+
type: Component,
|
|
4732
|
+
args: [{ selector: 'tn-chip-input', standalone: true, imports: [TnChipComponent, TnTestIdDirective], providers: [
|
|
4733
|
+
{
|
|
4734
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4735
|
+
useExisting: forwardRef(() => TnChipInputComponent),
|
|
4736
|
+
multi: true,
|
|
4737
|
+
},
|
|
4738
|
+
], 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]=\"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]=\"testId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\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 suggestion) {\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]=\"chipTestId(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 }}\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"] }]
|
|
4739
|
+
}], 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 }] }], 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 }] }], 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 }] }] } });
|
|
4740
|
+
|
|
4741
|
+
/**
|
|
4742
|
+
* Harness for interacting with `tn-chip-input` in tests. Reads the committed
|
|
4743
|
+
* chips and drives the text field — adding values (optionally via the
|
|
4744
|
+
* suggestion dropdown) and removing them.
|
|
4745
|
+
*
|
|
4746
|
+
* @example
|
|
4747
|
+
* ```typescript
|
|
4748
|
+
* const tags = await loader.getHarness(TnChipInputHarness.with({ testId: 'chip-input-tags' }));
|
|
4749
|
+
* await tags.addChip('production');
|
|
4750
|
+
* expect(await tags.getChips()).toEqual(['production']);
|
|
4751
|
+
* await tags.removeChip('production');
|
|
4752
|
+
* ```
|
|
4753
|
+
*/
|
|
4754
|
+
class TnChipInputHarness extends ComponentHarness {
|
|
4755
|
+
static hostSelector = 'tn-chip-input';
|
|
4756
|
+
_input = this.locatorFor('.tn-chip-input__field');
|
|
4757
|
+
getChipHarnesses = this.locatorForAll(TnChipHarness);
|
|
4758
|
+
/** Suggestion options live in a CDK overlay on the document root. */
|
|
4759
|
+
documentRoot = this.documentRootLocatorFactory();
|
|
4760
|
+
static with(options = {}) {
|
|
4761
|
+
return new HarnessPredicate(TnChipInputHarness, options)
|
|
4762
|
+
.addOption('testId', options.testId, async (harness, testId) => (await (await harness._input()).getAttribute('data-testid')) === testId
|
|
4763
|
+
|| (await (await harness._input()).getAttribute('data-test')) === testId);
|
|
4764
|
+
}
|
|
4765
|
+
/** Labels of the currently committed chips, in order. */
|
|
4766
|
+
async getChips() {
|
|
4767
|
+
const chips = await this.getChipHarnesses();
|
|
4768
|
+
const labels = [];
|
|
4769
|
+
for (const chip of chips) {
|
|
4770
|
+
labels.push(await chip.getLabel());
|
|
4771
|
+
}
|
|
4772
|
+
return labels;
|
|
4773
|
+
}
|
|
4774
|
+
/** Types `value` into the field and commits it with Enter. */
|
|
4775
|
+
async addChip(value) {
|
|
4776
|
+
const input = await this._input();
|
|
4777
|
+
await input.focus();
|
|
4778
|
+
await input.setInputValue(value);
|
|
4779
|
+
await input.dispatchEvent('input');
|
|
4780
|
+
await input.sendKeys(TestKey.ENTER);
|
|
4781
|
+
}
|
|
4782
|
+
/** Types `value` into the field without committing it. */
|
|
4783
|
+
async typeText(value) {
|
|
4784
|
+
const input = await this._input();
|
|
4785
|
+
await input.focus();
|
|
4786
|
+
await input.setInputValue(value);
|
|
4787
|
+
await input.dispatchEvent('input');
|
|
4788
|
+
}
|
|
4789
|
+
/** Whether the text field currently holds DOM focus. */
|
|
4790
|
+
async isInputFocused() {
|
|
4791
|
+
return (await this._input()).isFocused();
|
|
4792
|
+
}
|
|
4793
|
+
/** Focuses the text field (opens the dropdown when there are suggestions). */
|
|
4794
|
+
async focus() {
|
|
4795
|
+
await (await this._input()).focus();
|
|
4796
|
+
}
|
|
4797
|
+
/** Blurs the text field. */
|
|
4798
|
+
async blur() {
|
|
4799
|
+
await (await this._input()).blur();
|
|
4800
|
+
}
|
|
4801
|
+
/** Sends a key (or `TestKey`) to the focused field — e.g. Backspace, arrows, a separator. */
|
|
4802
|
+
async pressKey(key) {
|
|
4803
|
+
await (await this._input()).sendKeys(key);
|
|
4804
|
+
}
|
|
4805
|
+
/** Types `value`, then commits the matching suggestion from the dropdown. */
|
|
4806
|
+
async selectSuggestion(value) {
|
|
4807
|
+
const input = await this._input();
|
|
4808
|
+
await input.focus();
|
|
4809
|
+
await input.setInputValue(value);
|
|
4810
|
+
await input.dispatchEvent('input');
|
|
4811
|
+
const options = await this.documentRoot.locatorForAll('.tn-chip-input__option')();
|
|
4812
|
+
for (const option of options) {
|
|
4813
|
+
if ((await option.text()).trim() === value) {
|
|
4814
|
+
await option.click();
|
|
4815
|
+
return;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
throw new Error(`No suggestion matching "${value}" was found.`);
|
|
4819
|
+
}
|
|
4820
|
+
/** Removes the chip with the given label via its close button. */
|
|
4821
|
+
async removeChip(value) {
|
|
4822
|
+
const chip = await this.locatorFor(TnChipHarness.with({ label: value }))();
|
|
4823
|
+
await chip.close();
|
|
4824
|
+
}
|
|
4825
|
+
/** Visible suggestion option texts, or `[]` when the dropdown is closed. */
|
|
4826
|
+
async getSuggestions() {
|
|
4827
|
+
const options = await this.documentRoot.locatorForAll('.tn-chip-input__option')();
|
|
4828
|
+
const texts = [];
|
|
4829
|
+
for (const option of options) {
|
|
4830
|
+
texts.push((await option.text()).trim());
|
|
4831
|
+
}
|
|
4832
|
+
return texts;
|
|
4833
|
+
}
|
|
4834
|
+
async isDisabled() {
|
|
4835
|
+
const input = await this._input();
|
|
4836
|
+
return (await input.getProperty('disabled')) === true;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4840
|
+
/**
|
|
4841
|
+
* Marks an `<ng-template>` whose content is rendered as the card's footer actions,
|
|
4842
|
+
* bottom-right in the footer after any declarative `secondaryAction`/`primaryAction`
|
|
4843
|
+
* buttons. The template's root nodes become direct children of the footer's flex row,
|
|
4844
|
+
* so projected `<tn-button>`s sit and align exactly like the declarative ones — pass
|
|
4845
|
+
* bare buttons, no wrapper or styling required.
|
|
4846
|
+
*
|
|
4847
|
+
* Reach for this only when an action needs markup the declarative `TnCardAction` config
|
|
4848
|
+
* can't express — most commonly a permission-gated button wrapped in a structural
|
|
4849
|
+
* directive. Because the content renders via `ngTemplateOutlet` in the consumer's
|
|
4850
|
+
* context (not light-DOM projection), structural directives like `*ixRequiresRoles`
|
|
4851
|
+
* work directly on the button with no wrapper element:
|
|
4852
|
+
*
|
|
4853
|
+
* @example
|
|
4854
|
+
* ```html
|
|
4855
|
+
* <tn-card>
|
|
4856
|
+
* <ng-template tnCardFooterActions>
|
|
4857
|
+
* <tn-button *ixRequiresRoles="roles" label="Add" (click)="add()" />
|
|
4858
|
+
* </ng-template>
|
|
4859
|
+
* </tn-card>
|
|
4860
|
+
* ```
|
|
4861
|
+
*/
|
|
4862
|
+
class TnCardFooterActionsDirective {
|
|
4863
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardFooterActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4864
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnCardFooterActionsDirective, isStandalone: true, selector: "ng-template[tnCardFooterActions]", ngImport: i0 });
|
|
4865
|
+
}
|
|
4866
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardFooterActionsDirective, decorators: [{
|
|
4867
|
+
type: Directive,
|
|
4868
|
+
args: [{
|
|
4869
|
+
selector: 'ng-template[tnCardFooterActions]',
|
|
4870
|
+
standalone: true,
|
|
4871
|
+
}]
|
|
4872
|
+
}] });
|
|
4873
|
+
/**
|
|
4874
|
+
* Marks an `<ng-template>` whose content is rendered as the card's header actions,
|
|
4875
|
+
* top-right in the header between the header control and the kebab menu. Use it for
|
|
4876
|
+
* header controls the declarative `headerControl`/`headerMenu` config can't express —
|
|
4877
|
+
* e.g. a permission-gated slide toggle. Same template-outlet semantics (and structural
|
|
4878
|
+
* directive support) as {@link TnCardFooterActionsDirective}.
|
|
4879
|
+
*
|
|
4880
|
+
* @example
|
|
4881
|
+
* ```html
|
|
4882
|
+
* <tn-card title="Shares">
|
|
4883
|
+
* <ng-template tnCardHeaderActions>
|
|
4884
|
+
* <tn-slide-toggle *ixRequiresRoles="roles" [checked]="running()" />
|
|
4885
|
+
* </ng-template>
|
|
4886
|
+
* </tn-card>
|
|
4887
|
+
* ```
|
|
4888
|
+
*/
|
|
4889
|
+
class TnCardHeaderActionsDirective {
|
|
4890
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardHeaderActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4891
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnCardHeaderActionsDirective, isStandalone: true, selector: "ng-template[tnCardHeaderActions]", ngImport: i0 });
|
|
4892
|
+
}
|
|
4893
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardHeaderActionsDirective, decorators: [{
|
|
4894
|
+
type: Directive,
|
|
4895
|
+
args: [{
|
|
4896
|
+
selector: 'ng-template[tnCardHeaderActions]',
|
|
4897
|
+
standalone: true,
|
|
4898
|
+
}]
|
|
4899
|
+
}] });
|
|
4900
|
+
|
|
4371
4901
|
class TnCardHeaderDirective {
|
|
4372
4902
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4373
4903
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnCardHeaderDirective, isStandalone: true, selector: "[tnCardHeader]", ngImport: i0 });
|
|
@@ -5037,6 +5567,12 @@ class TnCardComponent {
|
|
|
5037
5567
|
this.registerMdiIcons();
|
|
5038
5568
|
}
|
|
5039
5569
|
projectedHeader = contentChild(TnCardHeaderDirective, ...(ngDevMode ? [{ debugName: "projectedHeader" }] : []));
|
|
5570
|
+
// Projected action templates (escape hatch for actions the declarative config can't
|
|
5571
|
+
// express, e.g. a permission-gated control wrapped in a structural directive). Rendered
|
|
5572
|
+
// via ngTemplateOutlet so the buttons inside become direct children of the header/footer
|
|
5573
|
+
// flex rows — same orientation as the declarative action buttons, no wrapper needed.
|
|
5574
|
+
headerActions = contentChild(TnCardHeaderActionsDirective, { ...(ngDevMode ? { debugName: "headerActions" } : {}), read: TemplateRef });
|
|
5575
|
+
footerActions = contentChild(TnCardFooterActionsDirective, { ...(ngDevMode ? { debugName: "footerActions" } : {}), read: TemplateRef });
|
|
5040
5576
|
title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
5041
5577
|
titleLink = input(undefined, ...(ngDevMode ? [{ debugName: "titleLink" }] : [])); // Makes title navigable
|
|
5042
5578
|
elevation = input('medium', ...(ngDevMode ? [{ debugName: "elevation" }] : []));
|
|
@@ -5087,13 +5623,16 @@ class TnCardComponent {
|
|
|
5087
5623
|
return ['tn-card', elevationClass, paddingClass, contentPaddingClass, borderedClass, backgroundClass].filter(Boolean);
|
|
5088
5624
|
}, ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
5089
5625
|
hasHeader = computed(() => {
|
|
5090
|
-
return !!(this.projectedHeader() || this.title() || this.headerStatus()
|
|
5626
|
+
return !!(this.projectedHeader() || this.title() || this.headerStatus()
|
|
5627
|
+
|| this.headerControl() || this.headerMenu() || this.headerActions());
|
|
5091
5628
|
}, ...(ngDevMode ? [{ debugName: "hasHeader" }] : []));
|
|
5092
5629
|
hasHeaderRight = computed(() => {
|
|
5093
|
-
return !!(this.headerStatus() || this.headerControl()
|
|
5630
|
+
return !!(this.headerStatus() || this.headerControl()
|
|
5631
|
+
|| this.headerMenu()?.length || this.headerActions());
|
|
5094
5632
|
}, ...(ngDevMode ? [{ debugName: "hasHeaderRight" }] : []));
|
|
5095
5633
|
hasFooter = computed(() => {
|
|
5096
|
-
return !!(this.primaryAction() || this.secondaryAction()
|
|
5634
|
+
return !!(this.primaryAction() || this.secondaryAction()
|
|
5635
|
+
|| this.footerLink() || this.footerActions());
|
|
5097
5636
|
}, ...(ngDevMode ? [{ debugName: "hasFooter" }] : []));
|
|
5098
5637
|
onTitleClick() {
|
|
5099
5638
|
const link = this.titleLink();
|
|
@@ -5114,7 +5653,7 @@ class TnCardComponent {
|
|
|
5114
5653
|
return type ? `tn-card__status--${type}` : 'tn-card__status--neutral';
|
|
5115
5654
|
}
|
|
5116
5655
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5117
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCardComponent, isStandalone: true, selector: "tn-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, titleLink: { classPropertyName: "titleLink", publicName: "titleLink", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, padContent: { classPropertyName: "padContent", publicName: "padContent", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, headerStatus: { classPropertyName: "headerStatus", publicName: "headerStatus", isSignal: true, isRequired: false, transformFunction: null }, headerControl: { classPropertyName: "headerControl", publicName: "headerControl", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, headerMenuTriggerTestId: { classPropertyName: "headerMenuTriggerTestId", publicName: "headerMenuTriggerTestId", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null }, footerLink: { classPropertyName: "footerLink", publicName: "footerLink", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedHeader", first: true, predicate: TnCardHeaderDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n tnTestIdType=\"link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "type", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSlideToggleComponent, selector: "tn-slide-toggle", inputs: ["labelPosition", "label", "disabled", "required", "color", "testId", "ariaLabel", "ariaLabelledby", "checked"], outputs: ["change", "toggleChange"] }, { kind: "component", type: TnMenuComponent, selector: "tn-menu", inputs: ["items", "contextMenu", "testId"], outputs: ["menuItemClick", "menuOpen", "menuClose"] }, { kind: "directive", type: TnMenuTriggerDirective, selector: "[tnMenuTriggerFor]", inputs: ["tnMenuTriggerFor", "tnMenuPosition"], exportAs: ["tnMenuTrigger"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
5656
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCardComponent, isStandalone: true, selector: "tn-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, titleLink: { classPropertyName: "titleLink", publicName: "titleLink", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, padContent: { classPropertyName: "padContent", publicName: "padContent", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, headerStatus: { classPropertyName: "headerStatus", publicName: "headerStatus", isSignal: true, isRequired: false, transformFunction: null }, headerControl: { classPropertyName: "headerControl", publicName: "headerControl", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, headerMenuTriggerTestId: { classPropertyName: "headerMenuTriggerTestId", publicName: "headerMenuTriggerTestId", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null }, footerLink: { classPropertyName: "footerLink", publicName: "footerLink", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedHeader", first: true, predicate: TnCardHeaderDirective, descendants: true, isSignal: true }, { propertyName: "headerActions", first: true, predicate: TnCardHeaderActionsDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "footerActions", first: true, predicate: TnCardFooterActionsDirective, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Projected header actions (escape hatch for custom/permission-gated controls) -->\n @if (headerActions(); as actions) {\n <ng-container [ngTemplateOutlet]=\"actions\" />\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n tnTestIdType=\"link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n <!-- Projected footer actions (escape hatch for custom/permission-gated controls) -->\n @if (footerActions(); as actions) {\n <ng-container [ngTemplateOutlet]=\"actions\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TnButtonComponent, selector: "tn-button", inputs: ["primary", "color", "variant", "backgroundColor", "label", "disabled", "type", "testId", "href", "routerLink", "queryParams", "fragment", "target", "rel", "ariaLabel"], outputs: ["onClick"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "library", "iconClass"], outputs: ["onClick"] }, { kind: "component", type: TnSlideToggleComponent, selector: "tn-slide-toggle", inputs: ["labelPosition", "label", "disabled", "required", "color", "testId", "ariaLabel", "ariaLabelledby", "checked"], outputs: ["change", "toggleChange"] }, { kind: "component", type: TnMenuComponent, selector: "tn-menu", inputs: ["items", "contextMenu", "testId"], outputs: ["menuItemClick", "menuOpen", "menuClose"] }, { kind: "directive", type: TnMenuTriggerDirective, selector: "[tnMenuTriggerFor]", inputs: ["tnMenuTriggerFor", "tnMenuPosition"], exportAs: ["tnMenuTrigger"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
5118
5657
|
}
|
|
5119
5658
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCardComponent, decorators: [{
|
|
5120
5659
|
type: Component,
|
|
@@ -5127,8 +5666,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
5127
5666
|
TnMenuComponent,
|
|
5128
5667
|
TnMenuTriggerDirective,
|
|
5129
5668
|
TnTestIdDirective,
|
|
5130
|
-
], template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n tnTestIdType=\"link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"] }]
|
|
5131
|
-
}], ctorParameters: () => [], propDecorators: { projectedHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnCardHeaderDirective), { isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], titleLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleLink", required: false }] }], elevation: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevation", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], padContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "padContent", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], headerStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerStatus", required: false }] }], headerControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerControl", required: false }] }], headerMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenu", required: false }] }], headerMenuTriggerTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenuTriggerTestId", required: false }] }], primaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryAction", required: false }] }], secondaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryAction", required: false }] }], footerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLink", required: false }] }] } });
|
|
5669
|
+
], template: "<div [ngClass]=\"classes()\">\n <!-- Header section -->\n @if (hasHeader()) {\n <div class=\"tn-card__header\">\n <div class=\"tn-card__header-left\">\n <ng-content select=\"[tnCardHeader]\" />\n @if (!projectedHeader() && title()) {\n <h3\n class=\"tn-card__title\"\n [class.tn-card__title--link]=\"titleLink()\"\n [attr.tabindex]=\"titleLink() ? 0 : null\"\n [attr.role]=\"titleLink() ? 'button' : null\"\n (click)=\"onTitleClick()\"\n (keydown.enter)=\"onTitleClick()\"\n (keydown.space)=\"onTitleClick()\">\n {{ title() }}\n </h3>\n }\n </div>\n\n @if (hasHeaderRight()) {\n <div class=\"tn-card__header-right\">\n <!-- Header Status -->\n @if (headerStatus(); as status) {\n <div\n class=\"tn-card__status\"\n [ngClass]=\"getStatusClass(status?.type)\"\n [tnTestId]=\"status.testId\">\n {{ status.label }}\n </div>\n }\n\n <!-- Header Control (Slide Toggle) -->\n @if (headerControl(); as control) {\n <div class=\"tn-card__control\">\n <tn-slide-toggle\n [label]=\"control.label\"\n [checked]=\"control.checked\"\n [disabled]=\"control.disabled || false\"\n [testId]=\"control.testId\"\n (change)=\"onControlChange($event)\" />\n </div>\n }\n\n <!-- Projected header actions (escape hatch for custom/permission-gated controls) -->\n @if (headerActions(); as actions) {\n <ng-container [ngTemplateOutlet]=\"actions\" />\n }\n\n <!-- Header Menu -->\n @if (headerMenu(); as menu) {\n @if (menu.length) {\n <div class=\"tn-card__menu\">\n <tn-icon-button\n name=\"dots-vertical\"\n library=\"mdi\"\n size=\"md\"\n ariaLabel=\"Card menu\"\n [testId]=\"headerMenuTriggerTestId()\"\n [tnMenuTriggerFor]=\"cardMenu\" />\n <tn-menu\n #cardMenu\n [items]=\"menu\"\n (menuItemClick)=\"onHeaderMenuItemClick($event)\" />\n </div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Content section -->\n <div class=\"tn-card__content\">\n <ng-content />\n </div>\n\n <!-- Footer section -->\n @if (hasFooter()) {\n <div class=\"tn-card__footer\">\n <div class=\"tn-card__footer-left\">\n @if (footerLink(); as link) {\n <button\n type=\"button\"\n class=\"tn-card__footer-link\"\n tnTestIdType=\"link\"\n [tnTestId]=\"link.testId\"\n (click)=\"link.handler()\">\n {{ link.label }}\n </button>\n }\n </div>\n\n <div class=\"tn-card__footer-right\">\n @if (secondaryAction(); as action) {\n <tn-button\n variant=\"outline\"\n color=\"default\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n @if (primaryAction(); as action) {\n <tn-button\n variant=\"filled\"\n color=\"primary\"\n [label]=\"action.label\"\n [disabled]=\"action.disabled || false\"\n [testId]=\"action.testId\"\n (click)=\"action.handler()\" />\n }\n\n <!-- Projected footer actions (escape hatch for custom/permission-gated controls) -->\n @if (footerActions(); as actions) {\n <ng-container [ngTemplateOutlet]=\"actions\" />\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-card{height:100%;display:flex;flex-direction:column;border-radius:8px;transition:box-shadow .3s ease;overflow:hidden}.tn-card--elevation-none{box-shadow:none}.tn-card--elevation-low{box-shadow:0 1px 3px #0000001a}.tn-card--elevation-medium{box-shadow:0 4px 6px #0000001a}.tn-card--elevation-high{box-shadow:0 10px 15px #0000001a}.tn-card--bordered{border:1px solid var(--tn-lines, #e5e7eb)}.tn-card--background{background-color:var(--tn-bg2, #ffffff)}.tn-card--padding-small .tn-card__header{padding:12px 16px}.tn-card--padding-medium .tn-card__header{padding:16px 24px}.tn-card--padding-large .tn-card__header{padding:24px 32px}.tn-card--content-padding-none .tn-card__content{padding:0}.tn-card--content-padding-small .tn-card__content{padding:16px}.tn-card--content-padding-medium .tn-card__content{padding:24px}.tn-card--content-padding-large .tn-card__content{padding:32px}.tn-card__content{flex:1;min-height:0;font-size:14px}.tn-card__header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--tn-lines, #e5e7eb)}.tn-card__header-left{flex:1;min-width:0}.tn-card__header-right{display:flex;align-items:center;gap:12px;flex-shrink:0}.tn-card__title{margin:0;font-size:1.125rem;font-weight:600;color:var(--tn-fg1, #1f2937);line-height:1.5}.tn-card__title--link{cursor:pointer;transition:color .2s ease}.tn-card__title--link:hover{color:var(--tn-primary, #2563eb)}.tn-card__status{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.tn-card__status--success{background-color:#10b9811a;color:var(--tn-success, #10b981)}.tn-card__status--warning{background-color:#f59e0b1a;color:var(--tn-warning, #f59e0b)}.tn-card__status--error{background-color:#ef44441a;color:var(--tn-error, #ef4444)}.tn-card__status--info{background-color:#3b82f61a;color:var(--tn-info, #3b82f6)}.tn-card__status--neutral{background-color:#6b72801a;color:var(--tn-fg2, #6b7280)}.tn-card__control,.tn-card__menu{display:flex;align-items:center}.tn-card__footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--tn-lines, #e5e7eb);padding:16px 24px}.tn-card--padding-small .tn-card__footer{padding:12px 16px}.tn-card--padding-large .tn-card__footer{padding:24px 32px}.tn-card__footer-left{flex:1;min-width:0}.tn-card__footer-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.tn-card__footer-link{border:none;background:transparent;color:var(--tn-primary, #2563eb);font-size:1rem;font-weight:600;cursor:pointer;padding:0;text-decoration:none;transition:color .2s ease}.tn-card__footer-link:hover{color:var(--tn-primary-dark, #1d4ed8);text-decoration:underline}.tn-card__footer-link:focus{outline:2px solid var(--tn-primary, #2563eb);outline-offset:2px;border-radius:2px}\n"] }]
|
|
5670
|
+
}], ctorParameters: () => [], propDecorators: { projectedHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnCardHeaderDirective), { isSignal: true }] }], headerActions: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnCardHeaderActionsDirective), { ...{ read: TemplateRef }, isSignal: true }] }], footerActions: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TnCardFooterActionsDirective), { ...{ read: TemplateRef }, isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], titleLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleLink", required: false }] }], elevation: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevation", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], padContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "padContent", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], headerStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerStatus", required: false }] }], headerControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerControl", required: false }] }], headerMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenu", required: false }] }], headerMenuTriggerTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerMenuTriggerTestId", required: false }] }], primaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryAction", required: false }] }], secondaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryAction", required: false }] }], footerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLink", required: false }] }] } });
|
|
5132
5671
|
|
|
5133
5672
|
class TnExpansionPanelComponent {
|
|
5134
5673
|
title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
@@ -16442,5 +16981,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
16442
16981
|
* Generated bundle index. Do not edit.
|
|
16443
16982
|
*/
|
|
16444
16983
|
|
|
16445
|
-
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, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, 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 };
|
|
16984
|
+
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 };
|
|
16446
16985
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|