@truenas/ui-components 0.3.4 → 0.3.6
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.
|
@@ -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,495 @@ 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
|
+
* Whether free text not present in `suggestions` may be committed. Defaults to
|
|
4414
|
+
* `true` — any typed value becomes a chip. Set `false` to restrict the field
|
|
4415
|
+
* to its suggestion list (a "pick from the list" control): a commit only
|
|
4416
|
+
* succeeds when the text matches a suggestion (case-insensitively, committing
|
|
4417
|
+
* the suggestion's canonical casing); unmatched text is discarded. Mirrors
|
|
4418
|
+
* `tn-autocomplete`'s `allowCustomValue`. With no `suggestions`, nothing can be
|
|
4419
|
+
* added.
|
|
4420
|
+
*/
|
|
4421
|
+
allowCustomValue = input(true, ...(ngDevMode ? [{ debugName: "allowCustomValue" }] : []));
|
|
4422
|
+
/**
|
|
4423
|
+
* Allow the same value to be added more than once. Off by default.
|
|
4424
|
+
* Duplicate detection is exact-match (case-sensitive), so with this off
|
|
4425
|
+
* `Angular` and `angular` are still distinct values — only suggestion
|
|
4426
|
+
* *filtering* is case-insensitive.
|
|
4427
|
+
*/
|
|
4428
|
+
allowDuplicates = input(false, ...(ngDevMode ? [{ debugName: "allowDuplicates" }] : []));
|
|
4429
|
+
/** Hard cap on the number of chips; `undefined` means no limit. */
|
|
4430
|
+
maxChips = input(undefined, ...(ngDevMode ? [{ debugName: "maxChips" }] : []));
|
|
4431
|
+
/**
|
|
4432
|
+
* Optional suggestion list. When non-empty, a dropdown offers entries that
|
|
4433
|
+
* match the typed text and are not already selected. For async sources,
|
|
4434
|
+
* update this in response to `(searchChange)`.
|
|
4435
|
+
*/
|
|
4436
|
+
suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
4437
|
+
/** Accessible name for the text field. Leave unset inside a `tn-form-field`. */
|
|
4438
|
+
ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
4439
|
+
/**
|
|
4440
|
+
* Semantic test-id base. The library prepends the `chip-input` element type
|
|
4441
|
+
* (e.g. `testId="tags"` → `chip-input-tags`); each chip and suggestion is
|
|
4442
|
+
* scoped beneath it.
|
|
4443
|
+
*/
|
|
4444
|
+
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
4445
|
+
/** Emits the committed value whenever a chip is added. */
|
|
4446
|
+
chipAdded = output();
|
|
4447
|
+
/** Emits the removed value whenever a chip is removed. */
|
|
4448
|
+
chipRemoved = output();
|
|
4449
|
+
/**
|
|
4450
|
+
* Emits the current text as the user types (not on programmatic writes or
|
|
4451
|
+
* chip commits). Drive server-side suggestion lookups from this; debounce in
|
|
4452
|
+
* the consumer if the lookup is expensive.
|
|
4453
|
+
*/
|
|
4454
|
+
searchChange = output();
|
|
4455
|
+
container = viewChild.required('container');
|
|
4456
|
+
inputEl = viewChild.required('inputEl');
|
|
4457
|
+
dropdownTemplate = viewChild.required('dropdownTemplate');
|
|
4458
|
+
/** Committed chip values — the form model. */
|
|
4459
|
+
values = signal([], ...(ngDevMode ? [{ debugName: "values" }] : []));
|
|
4460
|
+
/** Current text in the field. */
|
|
4461
|
+
inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : []));
|
|
4462
|
+
/** Whether the suggestion dropdown is open. */
|
|
4463
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
4464
|
+
/** Index of the keyboard-highlighted suggestion, or -1. */
|
|
4465
|
+
highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
4466
|
+
/** Whether the text field currently holds focus — gates async re-opening. */
|
|
4467
|
+
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
4468
|
+
/** CVA disabled state pushed by the form. */
|
|
4469
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
|
|
4470
|
+
/** Combined disabled state from the input and the form. */
|
|
4471
|
+
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
4472
|
+
/** Whether another chip may still be added under `maxChips`. */
|
|
4473
|
+
canAddMore = computed(() => {
|
|
4474
|
+
const max = this.maxChips();
|
|
4475
|
+
return max === undefined || this.values().length < max;
|
|
4476
|
+
}, ...(ngDevMode ? [{ debugName: "canAddMore" }] : []));
|
|
4477
|
+
/** Suggestions that match the typed text and are not already selected. */
|
|
4478
|
+
filteredSuggestions = computed(() => {
|
|
4479
|
+
const selected = new Set(this.values());
|
|
4480
|
+
const term = this.inputValue().trim().toLowerCase();
|
|
4481
|
+
return this.suggestions().filter((suggestion) => {
|
|
4482
|
+
if (selected.has(suggestion)) {
|
|
4483
|
+
return false;
|
|
4484
|
+
}
|
|
4485
|
+
return term === '' || suggestion.toLowerCase().includes(term);
|
|
4486
|
+
});
|
|
4487
|
+
}, ...(ngDevMode ? [{ debugName: "filteredSuggestions" }] : []));
|
|
4488
|
+
onChange = () => { };
|
|
4489
|
+
onTouched = () => { };
|
|
4490
|
+
overlayRef;
|
|
4491
|
+
overlaySubs = [];
|
|
4492
|
+
constructor() {
|
|
4493
|
+
// Async suggestions: when the user types, onInput runs syncDropdown()
|
|
4494
|
+
// against the still-stale list and leaves the panel closed; results land a
|
|
4495
|
+
// tick later via [suggestions]. Re-open the panel once fresh matches arrive
|
|
4496
|
+
// while the field is focused and actively searching. This only ever opens
|
|
4497
|
+
// (never closes), so it doesn't fight Escape, blur, or the post-commit close
|
|
4498
|
+
// — those stay shut until the suggestion set next changes.
|
|
4499
|
+
effect(() => {
|
|
4500
|
+
const hasMatches = this.filteredSuggestions().length > 0;
|
|
4501
|
+
untracked(() => {
|
|
4502
|
+
const activelySearching = this.focused()
|
|
4503
|
+
&& this.inputValue().trim() !== ''
|
|
4504
|
+
&& this.canAddMore()
|
|
4505
|
+
&& !this.isDisabled();
|
|
4506
|
+
if (hasMatches && activelySearching) {
|
|
4507
|
+
this.open();
|
|
4508
|
+
}
|
|
4509
|
+
});
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
ngOnDestroy() {
|
|
4513
|
+
this.detachOverlay();
|
|
4514
|
+
}
|
|
4515
|
+
// ── ControlValueAccessor ──
|
|
4516
|
+
writeValue(value) {
|
|
4517
|
+
// Reflect the model verbatim — deliberately NOT clamped to maxChips. A form
|
|
4518
|
+
// may legitimately seed more values than the cap; silently dropping them
|
|
4519
|
+
// would lose data. The cap only blocks further user-driven additions.
|
|
4520
|
+
this.values.set(Array.isArray(value) ? [...value] : []);
|
|
4521
|
+
}
|
|
4522
|
+
registerOnChange(fn) {
|
|
4523
|
+
this.onChange = fn;
|
|
4524
|
+
}
|
|
4525
|
+
registerOnTouched(fn) {
|
|
4526
|
+
this.onTouched = fn;
|
|
4527
|
+
}
|
|
4528
|
+
setDisabledState(isDisabled) {
|
|
4529
|
+
this.formDisabled.set(isDisabled);
|
|
4530
|
+
if (isDisabled) {
|
|
4531
|
+
this.close();
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
// ── Template handlers ──
|
|
4535
|
+
/** Clicking anywhere in the container focuses the text field. */
|
|
4536
|
+
focusInput() {
|
|
4537
|
+
if (!this.isDisabled()) {
|
|
4538
|
+
this.inputEl().nativeElement.focus();
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
onInput(event) {
|
|
4542
|
+
const value = event.target.value;
|
|
4543
|
+
this.inputValue.set(value);
|
|
4544
|
+
this.searchChange.emit(value);
|
|
4545
|
+
this.highlightedIndex.set(-1);
|
|
4546
|
+
this.syncDropdown();
|
|
4547
|
+
}
|
|
4548
|
+
onFocus() {
|
|
4549
|
+
this.focused.set(true);
|
|
4550
|
+
this.syncDropdown();
|
|
4551
|
+
}
|
|
4552
|
+
onBlur() {
|
|
4553
|
+
this.focused.set(false);
|
|
4554
|
+
if (this.addOnBlur()) {
|
|
4555
|
+
this.addChip(this.inputValue());
|
|
4556
|
+
}
|
|
4557
|
+
this.close();
|
|
4558
|
+
this.onTouched();
|
|
4559
|
+
}
|
|
4560
|
+
onKeydown(event) {
|
|
4561
|
+
// Mid-IME-composition (Japanese/Chinese/Korean), the Enter that confirms a
|
|
4562
|
+
// candidate also fires keydown with isComposing=true — committing here would
|
|
4563
|
+
// swallow the confirmation and chip a half-composed value. Let it through.
|
|
4564
|
+
if (event.isComposing) {
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
const suggestions = this.filteredSuggestions();
|
|
4568
|
+
if (event.key === 'ArrowDown') {
|
|
4569
|
+
if (suggestions.length && this.canAddMore()) {
|
|
4570
|
+
event.preventDefault();
|
|
4571
|
+
this.open();
|
|
4572
|
+
this.highlightedIndex.set((this.highlightedIndex() + 1) % suggestions.length);
|
|
4573
|
+
this.scrollToHighlighted();
|
|
4574
|
+
}
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
if (event.key === 'ArrowUp') {
|
|
4578
|
+
if (suggestions.length && this.isOpen()) {
|
|
4579
|
+
event.preventDefault();
|
|
4580
|
+
const next = this.highlightedIndex() - 1;
|
|
4581
|
+
this.highlightedIndex.set(next < 0 ? suggestions.length - 1 : next);
|
|
4582
|
+
this.scrollToHighlighted();
|
|
4583
|
+
}
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
if (event.key === 'Escape') {
|
|
4587
|
+
if (this.isOpen()) {
|
|
4588
|
+
event.preventDefault();
|
|
4589
|
+
this.close();
|
|
4590
|
+
}
|
|
4591
|
+
return;
|
|
4592
|
+
}
|
|
4593
|
+
if (this.isCommitKey(event)) {
|
|
4594
|
+
event.preventDefault();
|
|
4595
|
+
const idx = this.highlightedIndex();
|
|
4596
|
+
if (this.isOpen() && idx >= 0 && idx < suggestions.length) {
|
|
4597
|
+
this.addChip(suggestions[idx]);
|
|
4598
|
+
}
|
|
4599
|
+
else {
|
|
4600
|
+
this.addChip(this.inputValue());
|
|
4601
|
+
}
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
// Backspace on an empty field removes the last chip — the standard
|
|
4605
|
+
// chip-input affordance for quick correction.
|
|
4606
|
+
if (event.key === 'Backspace' && this.inputValue() === '' && this.values().length > 0) {
|
|
4607
|
+
event.preventDefault();
|
|
4608
|
+
this.removeChip(this.values().length - 1);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
onSuggestionClick(suggestion) {
|
|
4612
|
+
this.addChip(suggestion);
|
|
4613
|
+
this.inputEl().nativeElement.focus();
|
|
4614
|
+
}
|
|
4615
|
+
/** Prevents the option `mousedown` from blurring the input before the click lands. */
|
|
4616
|
+
onSuggestionMousedown(event) {
|
|
4617
|
+
event.preventDefault();
|
|
4618
|
+
}
|
|
4619
|
+
removeChip(index) {
|
|
4620
|
+
if (this.isDisabled()) {
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
const removed = this.values()[index];
|
|
4624
|
+
if (removed === undefined) {
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
this.values.update((values) => values.filter((_, i) => i !== index));
|
|
4628
|
+
this.onChange(this.values());
|
|
4629
|
+
this.onTouched();
|
|
4630
|
+
this.chipRemoved.emit(removed);
|
|
4631
|
+
// Removing via a chip's close button leaves focus on the (now-destroyed)
|
|
4632
|
+
// button; return it to the field so keyboard users stay oriented. The
|
|
4633
|
+
// Backspace path is already focused here, so this is a harmless no-op there.
|
|
4634
|
+
this.inputEl().nativeElement.focus();
|
|
4635
|
+
this.syncDropdown();
|
|
4636
|
+
}
|
|
4637
|
+
/** Scopes a per-chip test id beneath the component's base. */
|
|
4638
|
+
chipTestId(value) {
|
|
4639
|
+
const base = this.testId();
|
|
4640
|
+
if (base === undefined) {
|
|
4641
|
+
return undefined;
|
|
4642
|
+
}
|
|
4643
|
+
return [...(Array.isArray(base) ? base : [base]), value];
|
|
4644
|
+
}
|
|
4645
|
+
// ── Internal ──
|
|
4646
|
+
isCommitKey(event) {
|
|
4647
|
+
return event.key === 'Enter' || this.separatorKeys().includes(event.key);
|
|
4648
|
+
}
|
|
4649
|
+
addChip(raw) {
|
|
4650
|
+
let value = (raw ?? '').trim();
|
|
4651
|
+
if (!value || this.isDisabled() || !this.canAddMore()) {
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
if (!this.allowCustomValue()) {
|
|
4655
|
+
// Restricted to the suggestion list: commit the canonical suggestion when
|
|
4656
|
+
// the text matches one (case-insensitively), otherwise discard it.
|
|
4657
|
+
const match = this.suggestions().find((suggestion) => suggestion.toLowerCase() === value.toLowerCase());
|
|
4658
|
+
if (!match) {
|
|
4659
|
+
this.clearInput();
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4662
|
+
value = match;
|
|
4663
|
+
}
|
|
4664
|
+
if (!this.allowDuplicates() && this.values().includes(value)) {
|
|
4665
|
+
this.clearInput();
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
this.values.update((values) => [...values, value]);
|
|
4669
|
+
this.onChange(this.values());
|
|
4670
|
+
this.onTouched();
|
|
4671
|
+
this.chipAdded.emit(value);
|
|
4672
|
+
this.clearInput();
|
|
4673
|
+
}
|
|
4674
|
+
clearInput() {
|
|
4675
|
+
this.inputValue.set('');
|
|
4676
|
+
this.inputEl().nativeElement.value = '';
|
|
4677
|
+
this.close();
|
|
4678
|
+
}
|
|
4679
|
+
/**
|
|
4680
|
+
* Opens the dropdown when there is something to show, closes it otherwise.
|
|
4681
|
+
* Stays closed once the chip cap is reached — suggesting rows that
|
|
4682
|
+
* `addChip()` would reject is misleading.
|
|
4683
|
+
*/
|
|
4684
|
+
syncDropdown() {
|
|
4685
|
+
if (this.filteredSuggestions().length > 0 && this.canAddMore() && !this.isDisabled()) {
|
|
4686
|
+
this.open();
|
|
4687
|
+
}
|
|
4688
|
+
else {
|
|
4689
|
+
this.close();
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
/** Keeps the keyboard-highlighted suggestion visible within the scrolling panel. */
|
|
4693
|
+
scrollToHighlighted() {
|
|
4694
|
+
const idx = this.highlightedIndex();
|
|
4695
|
+
const options = this.overlayRef?.overlayElement
|
|
4696
|
+
?.querySelectorAll('.tn-chip-input__option');
|
|
4697
|
+
options?.[idx]?.scrollIntoView({ block: 'nearest' });
|
|
4698
|
+
}
|
|
4699
|
+
open() {
|
|
4700
|
+
if (this.isOpen() || this.isDisabled()) {
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
this.isOpen.set(true);
|
|
4704
|
+
this.attachOverlay();
|
|
4705
|
+
}
|
|
4706
|
+
close() {
|
|
4707
|
+
this.isOpen.set(false);
|
|
4708
|
+
this.highlightedIndex.set(-1);
|
|
4709
|
+
this.detachOverlay();
|
|
4710
|
+
}
|
|
4711
|
+
attachOverlay() {
|
|
4712
|
+
const anchor = this.container().nativeElement;
|
|
4713
|
+
const positionStrategy = this.overlay
|
|
4714
|
+
.position()
|
|
4715
|
+
.flexibleConnectedTo(anchor)
|
|
4716
|
+
.withPositions([
|
|
4717
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
4718
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
4719
|
+
]);
|
|
4720
|
+
this.overlayRef = this.overlay.create({
|
|
4721
|
+
positionStrategy,
|
|
4722
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
4723
|
+
hasBackdrop: false,
|
|
4724
|
+
width: anchor.offsetWidth,
|
|
4725
|
+
});
|
|
4726
|
+
this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
|
|
4727
|
+
this.overlaySubs.push(this.overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
4728
|
+
const target = event.target;
|
|
4729
|
+
if (target && anchor.contains(target)) {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
this.close();
|
|
4733
|
+
}));
|
|
4734
|
+
}
|
|
4735
|
+
detachOverlay() {
|
|
4736
|
+
this.overlaySubs.forEach((sub) => sub.unsubscribe());
|
|
4737
|
+
this.overlaySubs = [];
|
|
4738
|
+
this.overlayRef?.dispose();
|
|
4739
|
+
this.overlayRef = undefined;
|
|
4740
|
+
}
|
|
4741
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnChipInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4742
|
+
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 }, allowCustomValue: { classPropertyName: "allowCustomValue", publicName: "allowCustomValue", 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: [
|
|
4743
|
+
{
|
|
4744
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4745
|
+
useExisting: forwardRef(() => TnChipInputComponent),
|
|
4746
|
+
multi: true,
|
|
4747
|
+
},
|
|
4748
|
+
], 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"] }] });
|
|
4749
|
+
}
|
|
4750
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnChipInputComponent, decorators: [{
|
|
4751
|
+
type: Component,
|
|
4752
|
+
args: [{ selector: 'tn-chip-input', standalone: true, imports: [TnChipComponent, TnTestIdDirective], providers: [
|
|
4753
|
+
{
|
|
4754
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4755
|
+
useExisting: forwardRef(() => TnChipInputComponent),
|
|
4756
|
+
multi: true,
|
|
4757
|
+
},
|
|
4758
|
+
], 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"] }]
|
|
4759
|
+
}], 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 }] }], 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 }] }] } });
|
|
4760
|
+
|
|
4761
|
+
/**
|
|
4762
|
+
* Harness for interacting with `tn-chip-input` in tests. Reads the committed
|
|
4763
|
+
* chips and drives the text field — adding values (optionally via the
|
|
4764
|
+
* suggestion dropdown) and removing them.
|
|
4765
|
+
*
|
|
4766
|
+
* @example
|
|
4767
|
+
* ```typescript
|
|
4768
|
+
* const tags = await loader.getHarness(TnChipInputHarness.with({ testId: 'chip-input-tags' }));
|
|
4769
|
+
* await tags.addChip('production');
|
|
4770
|
+
* expect(await tags.getChips()).toEqual(['production']);
|
|
4771
|
+
* await tags.removeChip('production');
|
|
4772
|
+
* ```
|
|
4773
|
+
*/
|
|
4774
|
+
class TnChipInputHarness extends ComponentHarness {
|
|
4775
|
+
static hostSelector = 'tn-chip-input';
|
|
4776
|
+
_input = this.locatorFor('.tn-chip-input__field');
|
|
4777
|
+
getChipHarnesses = this.locatorForAll(TnChipHarness);
|
|
4778
|
+
/** Suggestion options live in a CDK overlay on the document root. */
|
|
4779
|
+
documentRoot = this.documentRootLocatorFactory();
|
|
4780
|
+
static with(options = {}) {
|
|
4781
|
+
return new HarnessPredicate(TnChipInputHarness, options)
|
|
4782
|
+
.addOption('testId', options.testId, async (harness, testId) => (await (await harness._input()).getAttribute('data-testid')) === testId
|
|
4783
|
+
|| (await (await harness._input()).getAttribute('data-test')) === testId);
|
|
4784
|
+
}
|
|
4785
|
+
/** Labels of the currently committed chips, in order. */
|
|
4786
|
+
async getChips() {
|
|
4787
|
+
const chips = await this.getChipHarnesses();
|
|
4788
|
+
const labels = [];
|
|
4789
|
+
for (const chip of chips) {
|
|
4790
|
+
labels.push(await chip.getLabel());
|
|
4791
|
+
}
|
|
4792
|
+
return labels;
|
|
4793
|
+
}
|
|
4794
|
+
/** Types `value` into the field and commits it with Enter. */
|
|
4795
|
+
async addChip(value) {
|
|
4796
|
+
const input = await this._input();
|
|
4797
|
+
await input.focus();
|
|
4798
|
+
await input.setInputValue(value);
|
|
4799
|
+
await input.dispatchEvent('input');
|
|
4800
|
+
await input.sendKeys(TestKey.ENTER);
|
|
4801
|
+
}
|
|
4802
|
+
/** Types `value` into the field without committing it. */
|
|
4803
|
+
async typeText(value) {
|
|
4804
|
+
const input = await this._input();
|
|
4805
|
+
await input.focus();
|
|
4806
|
+
await input.setInputValue(value);
|
|
4807
|
+
await input.dispatchEvent('input');
|
|
4808
|
+
}
|
|
4809
|
+
/** Whether the text field currently holds DOM focus. */
|
|
4810
|
+
async isInputFocused() {
|
|
4811
|
+
return (await this._input()).isFocused();
|
|
4812
|
+
}
|
|
4813
|
+
/** Focuses the text field (opens the dropdown when there are suggestions). */
|
|
4814
|
+
async focus() {
|
|
4815
|
+
await (await this._input()).focus();
|
|
4816
|
+
}
|
|
4817
|
+
/** Blurs the text field. */
|
|
4818
|
+
async blur() {
|
|
4819
|
+
await (await this._input()).blur();
|
|
4820
|
+
}
|
|
4821
|
+
/** Sends a key (or `TestKey`) to the focused field — e.g. Backspace, arrows, a separator. */
|
|
4822
|
+
async pressKey(key) {
|
|
4823
|
+
await (await this._input()).sendKeys(key);
|
|
4824
|
+
}
|
|
4825
|
+
/** Types `value`, then commits the matching suggestion from the dropdown. */
|
|
4826
|
+
async selectSuggestion(value) {
|
|
4827
|
+
const input = await this._input();
|
|
4828
|
+
await input.focus();
|
|
4829
|
+
await input.setInputValue(value);
|
|
4830
|
+
await input.dispatchEvent('input');
|
|
4831
|
+
const options = await this.documentRoot.locatorForAll('.tn-chip-input__option')();
|
|
4832
|
+
for (const option of options) {
|
|
4833
|
+
if ((await option.text()).trim() === value) {
|
|
4834
|
+
await option.click();
|
|
4835
|
+
return;
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
throw new Error(`No suggestion matching "${value}" was found.`);
|
|
4839
|
+
}
|
|
4840
|
+
/** Removes the chip with the given label via its close button. */
|
|
4841
|
+
async removeChip(value) {
|
|
4842
|
+
const chip = await this.locatorFor(TnChipHarness.with({ label: value }))();
|
|
4843
|
+
await chip.close();
|
|
4844
|
+
}
|
|
4845
|
+
/** Visible suggestion option texts, or `[]` when the dropdown is closed. */
|
|
4846
|
+
async getSuggestions() {
|
|
4847
|
+
const options = await this.documentRoot.locatorForAll('.tn-chip-input__option')();
|
|
4848
|
+
const texts = [];
|
|
4849
|
+
for (const option of options) {
|
|
4850
|
+
texts.push((await option.text()).trim());
|
|
4851
|
+
}
|
|
4852
|
+
return texts;
|
|
4853
|
+
}
|
|
4854
|
+
async isDisabled() {
|
|
4855
|
+
const input = await this._input();
|
|
4856
|
+
return (await input.getProperty('disabled')) === true;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4371
4860
|
/**
|
|
4372
4861
|
* Marks an `<ng-template>` whose content is rendered as the card's footer actions,
|
|
4373
4862
|
* bottom-right in the footer after any declarative `secondaryAction`/`primaryAction`
|
|
@@ -16512,5 +17001,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
16512
17001
|
* Generated bundle index. Do not edit.
|
|
16513
17002
|
*/
|
|
16514
17003
|
|
|
16515
|
-
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, 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 };
|
|
17004
|
+
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 };
|
|
16516
17005
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|