@truenas/ui-components 0.5.3 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/truenas-ui-components.mjs +6380 -686
- package/fesm2022/truenas-ui-components.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/harness-docs/jest.config.ts +14 -0
- package/scripts/harness-docs/markdown-table.spec.ts +151 -0
- package/scripts/harness-docs/markdown-table.ts +77 -0
- package/scripts/harness-docs/tsconfig.json +14 -0
- package/scripts/jest.config.ts +18 -0
- package/src/styles/themes.css +447 -36
- package/types/truenas-ui-components.d.ts +3404 -185
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _truenas_ui_components from '@truenas/ui-components';
|
|
2
2
|
import * as _angular_core from '@angular/core';
|
|
3
|
-
import { Signal, InjectionToken, Renderer2, OnDestroy, ElementRef, AfterViewInit, TemplateRef, AfterContentInit, Provider, ChangeDetectorRef, OnInit, TrackByFunction, OnChanges,
|
|
4
|
-
import { ControlValueAccessor, NgControl, AbstractControl } from '@angular/forms';
|
|
3
|
+
import { Signal, InjectionToken, Renderer2, OnDestroy, ElementRef, AfterViewInit, TemplateRef, AfterContentInit, Provider, DoCheck, ChangeDetectorRef, OnInit, TrackByFunction, OnChanges, SimpleChanges, PipeTransform, ViewContainerRef, AfterViewChecked, ComponentRef } from '@angular/core';
|
|
4
|
+
import { ControlValueAccessor, NgControl, AbstractControl, ValidationErrors } from '@angular/forms';
|
|
5
5
|
import { ComponentHarness, BaseHarnessFilters, HarnessPredicate, TestKey, HarnessLoader, ModifierKeys as ModifierKeys$1 } from '@angular/cdk/testing';
|
|
6
6
|
import { SafeHtml, SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
|
|
7
7
|
import { ComponentFixture } from '@angular/core/testing';
|
|
@@ -30,9 +30,16 @@ type TnTestIdValue = string | number | (string | number | null | undefined)[] |
|
|
|
30
30
|
* collapse any run of non-alphanumeric characters to a single hyphen, and trim
|
|
31
31
|
* leading/trailing hyphens.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `
|
|
33
|
+
* Follows webui's legacy `ixTest` directive (lodash `kebabCase`) for the cases
|
|
34
|
+
* that matter to migrated values — `sshPort` → `ssh-port`, `addr_trtype` →
|
|
35
|
+
* `addr-trtype`, `'My Label'` → `my-label` — but deliberately not for the
|
|
36
|
+
* letter↔digit boundary, which lodash splits and this does not: `nvme0n1` stays
|
|
37
|
+
* `nvme0n1` rather than becoming `nvme-0-n-1`, and `ipv4` stays `ipv4`. Device
|
|
38
|
+
* and protocol names read better whole, and they are exactly the strings that
|
|
39
|
+
* end up in ids. A consumer that must reproduce a legacy lodash-derived id
|
|
40
|
+
* byte-for-byte pre-normalizes the dynamic part itself before handing it over
|
|
41
|
+
* (webui's `normalizeTestIdString` does this); the result then passes through
|
|
42
|
+
* here unchanged.
|
|
36
43
|
*/
|
|
37
44
|
declare function kebabTestSegment(part: string | number): string;
|
|
38
45
|
/**
|
|
@@ -133,14 +140,29 @@ interface TnOptionTestIdSource {
|
|
|
133
140
|
* `[tnTestId]` with `tnTestIdType="option"`. The component's resolved base
|
|
134
141
|
* (explicit `testId`, else the bound control name) scopes a per-option
|
|
135
142
|
* discriminator so ids stay unique across instances: base `user` + option
|
|
136
|
-
*
|
|
143
|
+
* label `Jane Doe` → `option-user-jane-doe`; with no base → `option-jane-doe`.
|
|
137
144
|
*
|
|
138
145
|
* The discriminator comes from `extractor` when provided (a component's
|
|
139
|
-
* `optionTestIdKey` input), else the option's
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
146
|
+
* `optionTestIdKey` input), else the option's **`label`** — the text actually
|
|
147
|
+
* on screen — falling back to a primitive `value` only for the labelless
|
|
148
|
+
* option. Shared by `tn-select`, `tn-autocomplete` and `tn-chip-input` so the
|
|
149
|
+
* derivation rules can't drift between dropdown components; synthetic rows with
|
|
150
|
+
* fixed discriminators (e.g. select's `allowEmpty` option) are handled by the
|
|
143
151
|
* caller before delegating here.
|
|
152
|
+
*
|
|
153
|
+
* **Why the label and not the value.** An option's value is frequently opaque
|
|
154
|
+
* to everyone but the code that owns it — an enum ordinal, a record id, a
|
|
155
|
+
* protocol constant — so keying ids off it yields `option-sshconnectmode-0` or
|
|
156
|
+
* `option-user-1734`: not unique in any way a test author can predict, and
|
|
157
|
+
* silently renumbered whenever the enum or the records change. The label is the
|
|
158
|
+
* one part of an option that both the person writing the test and the person
|
|
159
|
+
* reading the page can see, so it is the useful default. What it trades away is
|
|
160
|
+
* uniqueness by construction: a value is unique within a control (it is the
|
|
161
|
+
* model value the control round-trips) where a label is not, so an option set
|
|
162
|
+
* with a repeated display name now produces repeated ids, silently — nothing
|
|
163
|
+
* validates label uniqueness. Where that happens, or an id must be stable
|
|
164
|
+
* across locales, or an id-per-record is genuinely wanted, `extractor`
|
|
165
|
+
* (`[optionTestIdKey]`) still overrides it — that is what the input is for.
|
|
144
166
|
*/
|
|
145
167
|
declare function optionTestId<O extends TnOptionTestIdSource>(base: TnTestIdValue, option: O, extractor?: (option: O) => string | number | null | undefined): (string | number | null | undefined)[];
|
|
146
168
|
|
|
@@ -236,6 +258,33 @@ interface TnSelectOptionGroup<T = unknown> {
|
|
|
236
258
|
options: TnSelectOption<T>[];
|
|
237
259
|
disabled?: boolean;
|
|
238
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Copy rendered inside `tn-select` that is the same for every select in an app.
|
|
263
|
+
*
|
|
264
|
+
* Binding these per call site means repeating the identical string on every
|
|
265
|
+
* `<tn-select>` in the codebase — an app with translated copy ends up with two
|
|
266
|
+
* extra attribute rows on each one. Provide {@link TN_SELECT_LABELS} at the app
|
|
267
|
+
* root instead; inputs on `<tn-select>` still win where a particular select
|
|
268
|
+
* needs its own wording.
|
|
269
|
+
*/
|
|
270
|
+
interface TnSelectLabels {
|
|
271
|
+
/** Trigger text shown while nothing is selected. */
|
|
272
|
+
placeholder: string;
|
|
273
|
+
/** Message shown inside the dropdown when there are no options to list. */
|
|
274
|
+
noOptions: string;
|
|
275
|
+
/** Label of the "select all" row rendered when `showSelectAll` is set. */
|
|
276
|
+
selectAll: string;
|
|
277
|
+
}
|
|
278
|
+
/** English defaults used when no `TN_SELECT_LABELS` provider is registered. */
|
|
279
|
+
declare const TN_SELECT_DEFAULT_LABELS: TnSelectLabels;
|
|
280
|
+
/**
|
|
281
|
+
* DI token for app-wide default labels. Provide either a static object or a
|
|
282
|
+
* `Signal<TnSelectLabels>` — the latter lets every select react to language
|
|
283
|
+
* changes when the consumer wires it up to an i18n service.
|
|
284
|
+
*
|
|
285
|
+
* Explicit input bindings on `<tn-select>` still win over these defaults.
|
|
286
|
+
*/
|
|
287
|
+
declare const TN_SELECT_LABELS: InjectionToken<TnSelectLabels | Signal<TnSelectLabels>>;
|
|
239
288
|
/**
|
|
240
289
|
* A keyboard-navigable row in the open dropdown. Either the synthetic
|
|
241
290
|
* "select all" action (multiple mode with `showSelectAll`) or a real option.
|
|
@@ -252,7 +301,13 @@ type TnSelectNavEntry<T> = {
|
|
|
252
301
|
declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, OnDestroy {
|
|
253
302
|
options: _angular_core.InputSignal<TnSelectOption<T>[]>;
|
|
254
303
|
optionGroups: _angular_core.InputSignal<TnSelectOptionGroup<T>[]>;
|
|
255
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Label inputs are nullable on purpose: the component reads the resolved
|
|
306
|
+
* `resolved*` computeds below, which fall back to the DI-provided defaults
|
|
307
|
+
* (a signal — so language changes propagate live). An explicit input binding
|
|
308
|
+
* always wins.
|
|
309
|
+
*/
|
|
310
|
+
placeholder: _angular_core.InputSignal<string | undefined>;
|
|
256
311
|
/**
|
|
257
312
|
* Explicit accessible label for the select trigger. When set, this is used as
|
|
258
313
|
* the trigger's `aria-label` instead of the visible `placeholder` — useful in
|
|
@@ -268,18 +323,23 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
268
323
|
* invalid, required). All-null when standalone or when `ariaLabel` overrides.
|
|
269
324
|
*/
|
|
270
325
|
protected readonly fieldAria: _truenas_ui_components.TnFormFieldAriaBindings;
|
|
326
|
+
private readonly defaultLabels;
|
|
327
|
+
/** Resolved labels: an explicit input takes precedence over the DI default. */
|
|
328
|
+
protected readonly resolvedPlaceholder: Signal<string>;
|
|
329
|
+
protected readonly resolvedNoOptionsLabel: Signal<string>;
|
|
330
|
+
protected readonly resolvedSelectAllLabel: Signal<string>;
|
|
271
331
|
/**
|
|
272
332
|
* `aria-label` for the trigger. An explicit `ariaLabel` always wins; the
|
|
273
333
|
* `placeholder` fallback only applies while no form-field label is wired via
|
|
274
334
|
* `aria-labelledby`, so the trigger never advertises two names at once.
|
|
275
335
|
*/
|
|
276
|
-
protected triggerAriaLabel:
|
|
336
|
+
protected triggerAriaLabel: Signal<string | null>;
|
|
277
337
|
/**
|
|
278
338
|
* Message shown inside the dropdown when no options (and no option groups)
|
|
279
|
-
* are available.
|
|
280
|
-
*
|
|
339
|
+
* are available. Falls back to {@link TN_SELECT_LABELS}, so an app with i18n
|
|
340
|
+
* wiring provides it once rather than per call site.
|
|
281
341
|
*/
|
|
282
|
-
noOptionsLabel: _angular_core.InputSignal<string>;
|
|
342
|
+
noOptionsLabel: _angular_core.InputSignal<string | undefined>;
|
|
283
343
|
/**
|
|
284
344
|
* When `true` (single-select mode only), prepends a synthetic "empty"
|
|
285
345
|
* option to the dropdown so users can unset a chosen value: picking it
|
|
@@ -301,15 +361,15 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
301
361
|
*/
|
|
302
362
|
showSelectAll: _angular_core.InputSignal<boolean>;
|
|
303
363
|
/** Label of the select-all row rendered when `showSelectAll` is set. */
|
|
304
|
-
selectAllLabel: _angular_core.InputSignal<string>;
|
|
364
|
+
selectAllLabel: _angular_core.InputSignal<string | undefined>;
|
|
305
365
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
306
366
|
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
307
|
-
protected resolvedTestId:
|
|
367
|
+
protected resolvedTestId: Signal<TnTestIdValue>;
|
|
308
368
|
multiple: _angular_core.InputSignal<boolean>;
|
|
309
369
|
/**
|
|
310
|
-
* Optional extractor for the per-option test-id discriminator. Defaults to
|
|
311
|
-
*
|
|
312
|
-
*
|
|
370
|
+
* Optional extractor for the per-option test-id discriminator. Defaults to the
|
|
371
|
+
* option's `label`, the text actually on screen — provide this to key off a
|
|
372
|
+
* locale-independent field instead, or where an id per record is wanted —
|
|
313
373
|
* mirrors webui's `[ixTest]="[controlName, option.<field>]"` discriminator.
|
|
314
374
|
*
|
|
315
375
|
* @example
|
|
@@ -355,17 +415,17 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
355
415
|
* instances; otherwise falls back to a per-instance counter so two
|
|
356
416
|
* `<tn-select>`s on the same page never collide on `aria-controls`/group ids.
|
|
357
417
|
*/
|
|
358
|
-
protected idNamespace:
|
|
359
|
-
isDisabled:
|
|
418
|
+
protected idNamespace: Signal<string>;
|
|
419
|
+
isDisabled: Signal<boolean>;
|
|
360
420
|
/**
|
|
361
421
|
* The synthetic clear-selection option (`allowEmpty`, single mode only).
|
|
362
422
|
* Its value is `null` cast to `T` so it flows through the same selection
|
|
363
423
|
* path as real options — `selectedValue`/`writeValue` already model "no
|
|
364
424
|
* selection" as `null`, so picking it clears the field for free.
|
|
365
425
|
*/
|
|
366
|
-
protected emptyOption:
|
|
426
|
+
protected emptyOption: Signal<TnSelectOption<T> | null>;
|
|
367
427
|
/** Ungrouped options as rendered: the empty option (when enabled) first. */
|
|
368
|
-
protected displayOptions:
|
|
428
|
+
protected displayOptions: Signal<TnSelectOption<T>[]>;
|
|
369
429
|
/** Whether `option` is the synthetic `allowEmpty` clear option. */
|
|
370
430
|
protected isEmptyOption(option: TnSelectOption<T>): boolean;
|
|
371
431
|
/**
|
|
@@ -373,9 +433,9 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
373
433
|
* then groups). Used by keyboard navigation so we can skip disabled
|
|
374
434
|
* entries and group headers without a separate filter pass.
|
|
375
435
|
*/
|
|
376
|
-
navigableOptions:
|
|
436
|
+
navigableOptions: Signal<TnSelectNavEntry<T>[]>;
|
|
377
437
|
/** Stable DOM id of the currently-highlighted option, for aria-activedescendant. */
|
|
378
|
-
focusedOptionId:
|
|
438
|
+
focusedOptionId: Signal<string | null>;
|
|
379
439
|
/** Stable DOM id for an option; matches what navigableOptions() assigns. */
|
|
380
440
|
optionId(option: TnSelectOption<T>): string | null;
|
|
381
441
|
/** Whether `option` is the keyboard-highlighted item. */
|
|
@@ -437,9 +497,9 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
437
497
|
selectOption(option: TnSelectOption<T>): void;
|
|
438
498
|
private toggleOption;
|
|
439
499
|
isOptionSelected(option: TnSelectOption<T>): boolean;
|
|
440
|
-
protected displayText:
|
|
500
|
+
protected displayText: Signal<string>;
|
|
441
501
|
private findOptionByValue;
|
|
442
|
-
protected hasAnyOptions:
|
|
502
|
+
protected hasAnyOptions: Signal<boolean>;
|
|
443
503
|
/**
|
|
444
504
|
* Values of every selectable (non-disabled) option, across ungrouped options
|
|
445
505
|
* and enabled groups. This is the set the select-all row operates on —
|
|
@@ -450,15 +510,15 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
450
510
|
* ungrouped and inside a group isn't pushed twice — that would make
|
|
451
511
|
* select-all diverge from `toggleOption()`, which never produces duplicates.
|
|
452
512
|
*/
|
|
453
|
-
protected selectableValues:
|
|
513
|
+
protected selectableValues: Signal<T[]>;
|
|
454
514
|
/** Whether the select-all row is shown (multiple mode, opted in, with options). */
|
|
455
|
-
protected showSelectAllRow:
|
|
515
|
+
protected showSelectAllRow: Signal<boolean>;
|
|
456
516
|
/** Stable DOM id of the select-all row, for aria-activedescendant. */
|
|
457
|
-
protected selectAllId:
|
|
517
|
+
protected selectAllId: Signal<string>;
|
|
458
518
|
/** True when every selectable option is currently selected. */
|
|
459
|
-
protected allSelected:
|
|
519
|
+
protected allSelected: Signal<boolean>;
|
|
460
520
|
/** True when some — but not all — selectable options are selected. */
|
|
461
|
-
protected selectAllIndeterminate:
|
|
521
|
+
protected selectAllIndeterminate: Signal<boolean>;
|
|
462
522
|
/** Test-id segments for the select-all row; mirrors ix-select's `[name, 'select-all']`. */
|
|
463
523
|
protected selectAllTestIdParts(): (string | number | null | undefined)[];
|
|
464
524
|
/**
|
|
@@ -523,6 +583,30 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
|
|
|
523
583
|
* sources feed both dropdown components.
|
|
524
584
|
*/
|
|
525
585
|
type TnAutocompleteOption<T = unknown> = TnSelectOption<T>;
|
|
586
|
+
/**
|
|
587
|
+
* Copy rendered inside `tn-autocomplete` that is the same for every instance in
|
|
588
|
+
* an app. Provide {@link TN_AUTOCOMPLETE_LABELS} at the app root rather than
|
|
589
|
+
* repeating the identical strings on each call site; inputs on
|
|
590
|
+
* `<tn-autocomplete>` still win where one instance needs its own wording.
|
|
591
|
+
*/
|
|
592
|
+
interface TnAutocompleteLabels {
|
|
593
|
+
/** Placeholder shown in the text field while it is empty. */
|
|
594
|
+
placeholder: string;
|
|
595
|
+
/** Text shown next to the spinner while `loading` is set. */
|
|
596
|
+
loading: string;
|
|
597
|
+
/** Text shown when no option matches the search term. */
|
|
598
|
+
noResults: string;
|
|
599
|
+
}
|
|
600
|
+
/** English defaults used when no `TN_AUTOCOMPLETE_LABELS` provider is registered. */
|
|
601
|
+
declare const TN_AUTOCOMPLETE_DEFAULT_LABELS: TnAutocompleteLabels;
|
|
602
|
+
/**
|
|
603
|
+
* DI token for app-wide default labels. Provide either a static object or a
|
|
604
|
+
* `Signal<TnAutocompleteLabels>` — the latter lets every autocomplete react to
|
|
605
|
+
* language changes when the consumer wires it up to an i18n service.
|
|
606
|
+
*
|
|
607
|
+
* Explicit input bindings on `<tn-autocomplete>` still win over these defaults.
|
|
608
|
+
*/
|
|
609
|
+
declare const TN_AUTOCOMPLETE_LABELS: InjectionToken<TnAutocompleteLabels | Signal<TnAutocompleteLabels>>;
|
|
526
610
|
declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccessor, OnDestroy {
|
|
527
611
|
private readonly elementRef;
|
|
528
612
|
private readonly overlay;
|
|
@@ -543,8 +627,13 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
543
627
|
* Mirrors `tn-select`'s `compareWith`.
|
|
544
628
|
*/
|
|
545
629
|
compareWith: _angular_core.InputSignal<((a: T | null, b: T | null) => boolean) | undefined>;
|
|
546
|
-
/**
|
|
547
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Placeholder text for the input. Label inputs are nullable on purpose: the
|
|
632
|
+
* component reads the `resolved*` computeds below, which fall back to
|
|
633
|
+
* {@link TN_AUTOCOMPLETE_LABELS} (a signal — so language changes propagate
|
|
634
|
+
* live). An explicit input binding always wins.
|
|
635
|
+
*/
|
|
636
|
+
placeholder: _angular_core.InputSignal<string | undefined>;
|
|
548
637
|
/** Whether the input is disabled */
|
|
549
638
|
disabled: _angular_core.InputSignal<boolean>;
|
|
550
639
|
/** Require the user to select from the dropdown — reverts on blur if no match */
|
|
@@ -563,11 +652,11 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
563
652
|
*/
|
|
564
653
|
loading: _angular_core.InputSignal<boolean>;
|
|
565
654
|
/** Text shown next to the spinner while `loading` is set. */
|
|
566
|
-
loadingText: _angular_core.InputSignal<string>;
|
|
655
|
+
loadingText: _angular_core.InputSignal<string | undefined>;
|
|
567
656
|
/** Custom filter function. Defaults to case-insensitive includes on the option label */
|
|
568
657
|
filterFn: _angular_core.InputSignal<((option: TnAutocompleteOption<T>, searchTerm: string) => boolean) | undefined>;
|
|
569
658
|
/** Text shown when no options match the search */
|
|
570
|
-
noResultsText: _angular_core.InputSignal<string>;
|
|
659
|
+
noResultsText: _angular_core.InputSignal<string | undefined>;
|
|
571
660
|
/**
|
|
572
661
|
* Maximum number of options to render.
|
|
573
662
|
*
|
|
@@ -594,11 +683,11 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
594
683
|
/** Test ID attribute */
|
|
595
684
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
596
685
|
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
597
|
-
protected resolvedTestId:
|
|
686
|
+
protected resolvedTestId: Signal<TnTestIdValue>;
|
|
598
687
|
/**
|
|
599
|
-
* Optional extractor for the per-option test-id discriminator. Defaults to
|
|
600
|
-
*
|
|
601
|
-
*
|
|
688
|
+
* Optional extractor for the per-option test-id discriminator. Defaults to the
|
|
689
|
+
* option's `label`, the text actually on screen — provide this to key off a
|
|
690
|
+
* locale-independent field instead, or where an id per record is wanted —
|
|
602
691
|
* mirrors `tn-select`'s input of the same name.
|
|
603
692
|
*
|
|
604
693
|
* @example
|
|
@@ -612,6 +701,11 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
612
701
|
* invalid, required). All-null when standalone or when `ariaLabel` overrides.
|
|
613
702
|
*/
|
|
614
703
|
protected readonly fieldAria: _truenas_ui_components.TnFormFieldAriaBindings;
|
|
704
|
+
private readonly defaultLabels;
|
|
705
|
+
/** Resolved labels: an explicit input takes precedence over the DI default. */
|
|
706
|
+
protected readonly resolvedPlaceholder: Signal<string>;
|
|
707
|
+
protected readonly resolvedLoadingText: Signal<string>;
|
|
708
|
+
protected readonly resolvedNoResultsText: Signal<string>;
|
|
615
709
|
/** Emits the full option (label + value) when one is selected */
|
|
616
710
|
optionSelected: _angular_core.OutputEmitterRef<TnAutocompleteOption<T>>;
|
|
617
711
|
/**
|
|
@@ -638,11 +732,11 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
638
732
|
*/
|
|
639
733
|
opened: _angular_core.OutputEmitterRef<void>;
|
|
640
734
|
/** Reference to the input element */
|
|
641
|
-
inputEl:
|
|
735
|
+
inputEl: Signal<ElementRef<HTMLInputElement> | undefined>;
|
|
642
736
|
/** Template for the dropdown panel, portaled into a CDK overlay on open. */
|
|
643
737
|
private dropdownTemplate;
|
|
644
738
|
/** Normalized panel max-height as a CSS length string. */
|
|
645
|
-
protected panelMaxHeightValue:
|
|
739
|
+
protected panelMaxHeightValue: Signal<string>;
|
|
646
740
|
/** Current search term typed by the user */
|
|
647
741
|
protected searchTerm: _angular_core.WritableSignal<string>;
|
|
648
742
|
/** Whether the dropdown is open */
|
|
@@ -654,11 +748,11 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
|
|
|
654
748
|
/** CVA disabled state from the form */
|
|
655
749
|
private formDisabled;
|
|
656
750
|
/** Combined disabled state */
|
|
657
|
-
isDisabled:
|
|
751
|
+
isDisabled: Signal<boolean>;
|
|
658
752
|
/** Filtered and capped options */
|
|
659
|
-
protected filteredOptions:
|
|
753
|
+
protected filteredOptions: Signal<TnAutocompleteOption<T>[]>;
|
|
660
754
|
/** Whether there are any results to show */
|
|
661
|
-
protected hasResults:
|
|
755
|
+
protected hasResults: Signal<boolean>;
|
|
662
756
|
private onChange;
|
|
663
757
|
private onTouched;
|
|
664
758
|
/** Live overlay holding the dropdown panel, or undefined when closed. */
|
|
@@ -961,8 +1055,55 @@ interface AutocompleteHarnessFilters extends BaseHarnessFilters {
|
|
|
961
1055
|
|
|
962
1056
|
type TnDrawerMode = 'side' | 'over';
|
|
963
1057
|
type TnDrawerPosition = 'start' | 'end';
|
|
1058
|
+
/**
|
|
1059
|
+
* The accessible name a drawer falls back to when the caller names neither
|
|
1060
|
+
* `ariaLabel` nor `ariaLabelledby` (#214).
|
|
1061
|
+
*
|
|
1062
|
+
* `ariaLabel` defaults to `undefined`, so the DEFAULT rendering in `over` mode
|
|
1063
|
+
* was a `role="dialog"` with `aria-modal="true"` and no name — measured as an
|
|
1064
|
+
* `aria-dialog-name` violation. In `side` mode the same omission leaves a
|
|
1065
|
+
* `role="navigation"` landmark unnamed, which axe does not report while there is
|
|
1066
|
+
* only one of them on the page, and which stops telling them apart the moment
|
|
1067
|
+
* there are two.
|
|
1068
|
+
*
|
|
1069
|
+
* One fallback for both modes rather than one per mode: the drawer is the same
|
|
1070
|
+
* surface either way, the name answers the same question ("what is this?"), and
|
|
1071
|
+
* a rule that changes with the mode is one more thing for a caller to be wrong
|
|
1072
|
+
* about. A generic name is still a poor one, so it is paired with the dev-mode
|
|
1073
|
+
* warning `tnAccessibleName` raises.
|
|
1074
|
+
*
|
|
1075
|
+
* Exported so specs assert against it by name rather than by a copied literal.
|
|
1076
|
+
*/
|
|
1077
|
+
declare const TN_DRAWER_DEFAULT_LABEL = "Drawer";
|
|
1078
|
+
/**
|
|
1079
|
+
* A drawer, which is two different things by `mode`: in `side` it is
|
|
1080
|
+
* persistent navigation beside the page's content, and in `over` it is a modal
|
|
1081
|
+
* dialog with focus trapped in it.
|
|
1082
|
+
*
|
|
1083
|
+
* FOCUS ON OPEN, IN `over` MODE ONLY
|
|
1084
|
+
* ----------------------------------
|
|
1085
|
+
* An `over` drawer moves focus to the panel container when it opens, whatever
|
|
1086
|
+
* you projected into it, so that a screen reader announces the dialog it has
|
|
1087
|
+
* just entered before any control in it. A `side` drawer does not: navigation
|
|
1088
|
+
* that appears beside the content must not take focus from the page.
|
|
1089
|
+
*
|
|
1090
|
+
* **`[cdkFocusInitial]` is not honoured** (#227). It used to be, through the
|
|
1091
|
+
* CDK auto-capture this replaced, and `cdkTrapFocus` is still on the panel — so
|
|
1092
|
+
* the marker looks like it should work and does not. To focus a control of your
|
|
1093
|
+
* own, focus it yourself once the drawer is open; the component leaves focus
|
|
1094
|
+
* alone as soon as it is inside the panel. `lib/a11y/initial-focus.ts` holds
|
|
1095
|
+
* the reasoning for capturing the container rather than a control.
|
|
1096
|
+
*/
|
|
964
1097
|
declare class TnDrawerComponent implements OnDestroy {
|
|
965
1098
|
private readonly document;
|
|
1099
|
+
/**
|
|
1100
|
+
* The host, which contains the `side`-mode panel. The `over`-mode one is
|
|
1101
|
+
* portaled out to `document.body`, so "does this drawer hold focus" is a
|
|
1102
|
+
* question about both this and `overlayRef`.
|
|
1103
|
+
*/
|
|
1104
|
+
private readonly hostRef;
|
|
1105
|
+
/** For the `afterNextRender` an effect below schedules, outside injection context. */
|
|
1106
|
+
private readonly injector;
|
|
966
1107
|
/** Whether the drawer sits alongside content ('side') or overlays it ('over') */
|
|
967
1108
|
mode: _angular_core.InputSignal<TnDrawerMode>;
|
|
968
1109
|
/** Whether the drawer is open. Two-way bindable via [(opened)] */
|
|
@@ -975,29 +1116,100 @@ declare class TnDrawerComponent implements OnDestroy {
|
|
|
975
1116
|
position: _angular_core.InputSignal<TnDrawerPosition>;
|
|
976
1117
|
/** Accessible label for the drawer panel */
|
|
977
1118
|
ariaLabel: _angular_core.InputSignal<string | undefined>;
|
|
1119
|
+
/** IDREF naming the drawer panel from visible text elsewhere on the page */
|
|
1120
|
+
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
978
1121
|
/**
|
|
979
1122
|
* Test-id applied to the drawer panel. Rendered under whichever attribute name is
|
|
980
1123
|
* configured via `TN_TEST_ATTR` (default `data-testid`).
|
|
981
1124
|
*/
|
|
982
1125
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
983
|
-
/**
|
|
1126
|
+
/**
|
|
1127
|
+
* Fires once the drawer has finished opening.
|
|
1128
|
+
*
|
|
1129
|
+
* "Finished" means the open transition ended, OR that it was going to take
|
|
1130
|
+
* longer than `TN_TRANSITION_FALLBACK_MS` to say so — which is what a user
|
|
1131
|
+
* with `prefers-reduced-motion: reduce` gets, since this component's own
|
|
1132
|
+
* stylesheet removes the transition for them and one that does not run fires
|
|
1133
|
+
* no `transitionend` (#218). A consumer may assume the drawer has reached its
|
|
1134
|
+
* open state and that `opened()` is true; it may NOT assume the animation is
|
|
1135
|
+
* visually complete, because for that user there was none.
|
|
1136
|
+
*/
|
|
984
1137
|
openedComplete: _angular_core.OutputEmitterRef<void>;
|
|
985
|
-
/**
|
|
1138
|
+
/**
|
|
1139
|
+
* Fires once the drawer has finished closing. Same guarantee as
|
|
1140
|
+
* `openedComplete`, and the same caveat: it reports the state, not the
|
|
1141
|
+
* animation.
|
|
1142
|
+
*
|
|
1143
|
+
* Focus restoration does NOT hang off this — it happens as soon as the drawer
|
|
1144
|
+
* closes (#214). See the effect in the constructor.
|
|
1145
|
+
*/
|
|
986
1146
|
closed: _angular_core.OutputEmitterRef<void>;
|
|
987
1147
|
/** Whether the component has rendered (prevents transition flash on load) */
|
|
988
1148
|
protected initialized: _angular_core.WritableSignal<boolean>;
|
|
989
1149
|
/** Reference to the overlay element (portaled to body in over mode) */
|
|
990
1150
|
protected overlayRef: _angular_core.Signal<ElementRef<any> | undefined>;
|
|
1151
|
+
/**
|
|
1152
|
+
* The `over`-mode panel, which is what focus moves to when a modal drawer
|
|
1153
|
+
* opens. Optional rather than required: it lives inside an `@if` on the mode,
|
|
1154
|
+
* so a `side` drawer never renders it.
|
|
1155
|
+
*/
|
|
1156
|
+
private overPanelRef;
|
|
1157
|
+
/**
|
|
1158
|
+
* Whichever panel is currently rendered — the `side` one or the `over` one.
|
|
1159
|
+
*
|
|
1160
|
+
* Both template branches carry `#panel` and the `@if` on `mode` renders
|
|
1161
|
+
* exactly one, so this is "the drawer's panel" without the caller having to
|
|
1162
|
+
* ask which mode it is in. A drawer whose `mode` changes at runtime destroys
|
|
1163
|
+
* one element and builds the other, and this query re-answers with it.
|
|
1164
|
+
*/
|
|
1165
|
+
private panelRef;
|
|
1166
|
+
/**
|
|
1167
|
+
* Whether the panel carries a real tab stop rather than its resting `-1`
|
|
1168
|
+
* (#270).
|
|
1169
|
+
*
|
|
1170
|
+
* `.tn-drawer__panel` is the element with `overflow-y: auto`, so a drawer
|
|
1171
|
+
* whose projected content is taller than it is scrolls here — and until this
|
|
1172
|
+
* ticket nothing about it was reachable from a keyboard unless the caller
|
|
1173
|
+
* happened to project a tabbable control. The measurement, the observers that
|
|
1174
|
+
* keep it current and the rule that decides when the tab stop may be given
|
|
1175
|
+
* back are `tnScrollableRegion`'s; see `../a11y/scrollable-region.ts`, which
|
|
1176
|
+
* is also where the reasoning for holding it on while the panel has focus is
|
|
1177
|
+
* set out.
|
|
1178
|
+
*
|
|
1179
|
+
* A field initializer rather than the constructor, because it registers an
|
|
1180
|
+
* `effect` and so needs an injection context.
|
|
1181
|
+
*/
|
|
1182
|
+
protected panelKeyboardReachable: _angular_core.Signal<boolean>;
|
|
991
1183
|
/** Focus trap should be active only in 'over' mode when open */
|
|
992
1184
|
protected trapFocus: _angular_core.Signal<boolean>;
|
|
993
1185
|
/** Role depends on mode: navigation for side, dialog for over */
|
|
994
1186
|
protected panelRole: _angular_core.Signal<"dialog" | "navigation">;
|
|
1187
|
+
/**
|
|
1188
|
+
* The name to render as `aria-label`, or `null` to render none — and the
|
|
1189
|
+
* dev-mode warning when the caller named neither input.
|
|
1190
|
+
*
|
|
1191
|
+
* Both halves live in `../a11y/accessible-name`, shared with `tn-side-panel`
|
|
1192
|
+
* and the three progressbars, where the reasoning for each branch is set out:
|
|
1193
|
+
* why an explicit `ariaLabel` always survives, and why the generic fallback is
|
|
1194
|
+
* withheld beside an `ariaLabelledby`.
|
|
1195
|
+
*
|
|
1196
|
+
* A field initializer rather than the constructor, because it registers an
|
|
1197
|
+
* `effect` and so needs an injection context.
|
|
1198
|
+
*/
|
|
1199
|
+
protected resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
995
1200
|
/** Whether to show the backdrop */
|
|
996
1201
|
protected showBackdrop: _angular_core.Signal<boolean>;
|
|
997
1202
|
/** CSS classes for the drawer panel */
|
|
998
1203
|
protected drawerClasses: _angular_core.Signal<string[]>;
|
|
999
1204
|
/** Previous focus element for restoration (only captured in over mode) */
|
|
1000
1205
|
private previousFocus;
|
|
1206
|
+
/**
|
|
1207
|
+
* Decides when an open or a close counts as finished, so that the outputs
|
|
1208
|
+
* above fire exactly once per change whether or not a transition ran. A field
|
|
1209
|
+
* initializer rather than the constructor, because it registers an `effect`
|
|
1210
|
+
* and so needs an injection context.
|
|
1211
|
+
*/
|
|
1212
|
+
private lifecycle;
|
|
1001
1213
|
constructor();
|
|
1002
1214
|
ngOnDestroy(): void;
|
|
1003
1215
|
/** Open the drawer */
|
|
@@ -1014,21 +1226,116 @@ declare class TnDrawerComponent implements OnDestroy {
|
|
|
1014
1226
|
* to dismiss them. The header toggle button is the intended control.
|
|
1015
1227
|
*/
|
|
1016
1228
|
protected onKeydown(event: KeyboardEvent): void;
|
|
1017
|
-
/**
|
|
1229
|
+
/**
|
|
1230
|
+
* Handle transition end — report the open/close early, since the animation is
|
|
1231
|
+
* demonstrably over. Focus restoration is NOT here; it happens as soon as the
|
|
1232
|
+
* drawer closes, because this event does not fire under
|
|
1233
|
+
* `prefers-reduced-motion` — and neither, for the same reason, does the
|
|
1234
|
+
* emission depend on it any more (#218).
|
|
1235
|
+
*
|
|
1236
|
+
* Which output to emit is `lifecycle`'s to decide, from the state the change
|
|
1237
|
+
* it is tracking settled into — NOT from `opened()` read here. The two differ
|
|
1238
|
+
* exactly when this event is late: a drawer reopened while the close was still
|
|
1239
|
+
* animating reads `opened() === true` on the stale close's event.
|
|
1240
|
+
*/
|
|
1018
1241
|
protected onTransitionEnd(event: TransitionEvent): void;
|
|
1019
1242
|
private restoreFocus;
|
|
1020
1243
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDrawerComponent, never>;
|
|
1021
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDrawerComponent, "tn-drawer", never, { "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "opened": { "alias": "opened"; "required": false; "isSignal": true; }; "disableClose": { "alias": "disableClose"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "opened": "openedChange"; "openedComplete": "openedComplete"; "closed": "closed"; }, never, ["*"], true, never>;
|
|
1244
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDrawerComponent, "tn-drawer", never, { "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "opened": { "alias": "opened"; "required": false; "isSignal": true; }; "disableClose": { "alias": "disableClose"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "opened": "openedChange"; "openedComplete": "openedComplete"; "closed": "closed"; }, never, ["*"], true, never>;
|
|
1022
1245
|
}
|
|
1023
1246
|
|
|
1247
|
+
/**
|
|
1248
|
+
* The flex row that lays a `tn-drawer` out beside a `tn-drawer-content`.
|
|
1249
|
+
*
|
|
1250
|
+
* WHY IT CARRIES NO ROLE, NO LANDMARK AND NO NAME (#214)
|
|
1251
|
+
* -----------------------------------------------------
|
|
1252
|
+
* #214 reported axe evaluating ZERO rules against this component, which is true
|
|
1253
|
+
* and is not a defect in it: the container renders `<ng-content />` and a
|
|
1254
|
+
* stylesheet, so scanned on its own there is nothing there to have a role. The
|
|
1255
|
+
* scan in the report was the childless case, the same way #204's turned out to
|
|
1256
|
+
* be a stepper with no steps.
|
|
1257
|
+
*
|
|
1258
|
+
* The surface a user perceives is `tn-drawer`, and that is where the model is
|
|
1259
|
+
* declared — `role="navigation"` in `side` mode, `role="dialog"` with
|
|
1260
|
+
* `aria-modal` and a focus trap in `over` mode. Giving the container a role of
|
|
1261
|
+
* its own would put a second, unnamed thing in the accessibility tree between a
|
|
1262
|
+
* listener and that surface, describing a layout box.
|
|
1263
|
+
*
|
|
1264
|
+
* `role="main"` on `tn-drawer-content` is the other tempting one, and it belongs
|
|
1265
|
+
* to the application rather than to this library: a page decides where its main
|
|
1266
|
+
* landmark is, and a component library that claims it makes two `main`s the
|
|
1267
|
+
* moment an app has its own.
|
|
1268
|
+
*
|
|
1269
|
+
* Guarded by `drawer-a11y.spec.ts`, which asserts the drawer inside the
|
|
1270
|
+
* container is what axe attributes its results to.
|
|
1271
|
+
*/
|
|
1024
1272
|
declare class TnDrawerContainerComponent {
|
|
1025
1273
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDrawerContainerComponent, never>;
|
|
1026
1274
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDrawerContainerComponent, "tn-drawer-container", never, {}, {}, never, ["*"], true, never>;
|
|
1027
1275
|
}
|
|
1028
1276
|
|
|
1277
|
+
/**
|
|
1278
|
+
* The name this region takes when it becomes focusable (#270).
|
|
1279
|
+
*
|
|
1280
|
+
* A focusable element with no accessible name is announced as a bare "group",
|
|
1281
|
+
* which tells a listener that something has been reached and nothing about what
|
|
1282
|
+
* it is.
|
|
1283
|
+
*
|
|
1284
|
+
* "Content" rather than "Main content", and `role="group"` rather than
|
|
1285
|
+
* `role="main"`: the landmark belongs to the application, not to this library —
|
|
1286
|
+
* `drawer-container.component.ts` sets out why, and a component that claimed it
|
|
1287
|
+
* would give a page two `main`s the moment the page declared its own. A group
|
|
1288
|
+
* is not summarised in a landmarks list, so this names the scroll region
|
|
1289
|
+
* without competing with the page's own structure.
|
|
1290
|
+
*
|
|
1291
|
+
* Overridable through `ariaLabel`, on the same reasoning as
|
|
1292
|
+
* `TN_SIDE_PANEL_CONTENT_LABEL`: a string this library renders into a
|
|
1293
|
+
* consumer's UI has to be translatable, and a consumer who knows what the
|
|
1294
|
+
* region holds can say so. Exported so specs assert against it by name rather
|
|
1295
|
+
* than by a copied literal.
|
|
1296
|
+
*/
|
|
1297
|
+
declare const TN_DRAWER_CONTENT_LABEL = "Content";
|
|
1298
|
+
/**
|
|
1299
|
+
* The page content that sits beside a `tn-drawer` inside a
|
|
1300
|
+
* `tn-drawer-container`.
|
|
1301
|
+
*
|
|
1302
|
+
* WHY IT CARRIES A TAB STOP SOMETIMES AND NO LANDMARK EVER (#270)
|
|
1303
|
+
* --------------------------------------------------------------
|
|
1304
|
+
* The host is `overflow: auto`, so everything an application puts beside its
|
|
1305
|
+
* drawer scrolls in this element — and axe's `scrollable-region-focusable`
|
|
1306
|
+
* reports a scroll container that is neither in the tab order nor holds
|
|
1307
|
+
* anything that is. That is not a technicality here: this is the element that
|
|
1308
|
+
* holds a page, so content below its fold is most of the page, and a keyboard
|
|
1309
|
+
* user with no pointer could not reach it.
|
|
1310
|
+
*
|
|
1311
|
+
* The tab stop follows the measurement rather than being permanent, because
|
|
1312
|
+
* this component wraps every page that uses a drawer and a stop that announces
|
|
1313
|
+
* a group and does nothing would land on all of them. `tnScrollableRegion`
|
|
1314
|
+
* holds the measurement, the observers that keep it current, and the rule that
|
|
1315
|
+
* decides when it may be taken away again; see `../a11y/scrollable-region.ts`.
|
|
1316
|
+
*
|
|
1317
|
+
* The role is still not a landmark. `drawer-container.component.ts` explains
|
|
1318
|
+
* why `role="main"` belongs to the application rather than to this library, and
|
|
1319
|
+
* nothing about needing a tab stop changes that — see
|
|
1320
|
+
* `TN_DRAWER_CONTENT_LABEL`.
|
|
1321
|
+
*/
|
|
1029
1322
|
declare class TnDrawerContentComponent {
|
|
1323
|
+
private readonly hostRef;
|
|
1324
|
+
/**
|
|
1325
|
+
* Accessible name for the scrolling region, which is named only while it is
|
|
1326
|
+
* focusable — see `TN_DRAWER_CONTENT_LABEL`. Override it to translate it, or
|
|
1327
|
+
* to say what the region holds ("Pool details").
|
|
1328
|
+
*/
|
|
1329
|
+
ariaLabel: _angular_core.InputSignal<string>;
|
|
1330
|
+
/**
|
|
1331
|
+
* Whether the region carries the tab stop, its role and its name — which is
|
|
1332
|
+
* NOT the same question as whether it currently overflows. All three are
|
|
1333
|
+
* gated together, and held on while the region has focus; the reasoning for
|
|
1334
|
+
* both is in `../a11y/scrollable-region.ts`.
|
|
1335
|
+
*/
|
|
1336
|
+
protected keyboardReachable: _angular_core.Signal<boolean>;
|
|
1030
1337
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDrawerContentComponent, never>;
|
|
1031
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDrawerContentComponent, "tn-drawer-content", never, {}, {}, never, ["*"], true, never>;
|
|
1338
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDrawerContentComponent, "tn-drawer-content", never, { "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
1032
1339
|
}
|
|
1033
1340
|
|
|
1034
1341
|
/**
|
|
@@ -1152,11 +1459,15 @@ declare class TnBannerComponent {
|
|
|
1152
1459
|
/**
|
|
1153
1460
|
* Get the appropriate icon name based on banner type
|
|
1154
1461
|
*/
|
|
1155
|
-
iconName: _angular_core.Signal<"
|
|
1462
|
+
iconName: _angular_core.Signal<"alert" | "information" | "alert-circle" | "check-circle">;
|
|
1156
1463
|
/**
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1464
|
+
* The live-region role, which is also the only thing declaring how urgently
|
|
1465
|
+
* the banner is announced: `alert` implies `aria-live="assertive"` and
|
|
1466
|
+
* `status` implies `polite`. The template carries no `aria-live`, because an
|
|
1467
|
+
* explicit one would override this.
|
|
1468
|
+
*
|
|
1469
|
+
* The mapping is shared with toast rather than restated here — see
|
|
1470
|
+
* `../a11y/live-region.ts` for which severities interrupt and why.
|
|
1160
1471
|
*/
|
|
1161
1472
|
ariaRole: _angular_core.Signal<"alert" | "status">;
|
|
1162
1473
|
/**
|
|
@@ -1167,9 +1478,101 @@ declare class TnBannerComponent {
|
|
|
1167
1478
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnBannerComponent, "tn-banner", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; }, {}, ["actionContent"], ["*", "[tnBannerAction]"], true, never>;
|
|
1168
1479
|
}
|
|
1169
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* Harness for a single action projected into a `tn-banner`'s action slot.
|
|
1483
|
+
*
|
|
1484
|
+
* Reach these through `TnBannerHarness.getActions()` / `clickAction()` rather
|
|
1485
|
+
* than loading them directly — on their own they would also match a
|
|
1486
|
+
* `[tnBannerAction]` element that some other component projects.
|
|
1487
|
+
*
|
|
1488
|
+
* `[tnBannerAction]` is an attribute directive with no element restriction, so
|
|
1489
|
+
* an action is whatever the caller projected: a `tn-button`, a plain `<a>`, a
|
|
1490
|
+
* native `<button>`. This harness covers all of them rather than assuming
|
|
1491
|
+
* `tn-button`, which is why `TnBannerHarness` does not simply locate
|
|
1492
|
+
* `TnButtonHarness` the way `TnDialogHarness` does — a dialog's footer takes
|
|
1493
|
+
* `tn-button`s, a banner's action slot takes anything.
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* ```typescript
|
|
1497
|
+
* const banner = await loader.getHarness(TnBannerHarness);
|
|
1498
|
+
* const actions = await banner.getActions();
|
|
1499
|
+
* expect(await actions[0].getLabel()).toBe('Retry');
|
|
1500
|
+
* await actions[0].click();
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
declare class TnBannerActionHarness extends ComponentHarness {
|
|
1504
|
+
/**
|
|
1505
|
+
* The selector for the host element of a banner action.
|
|
1506
|
+
*/
|
|
1507
|
+
static hostSelector: string;
|
|
1508
|
+
private _buttonLabel;
|
|
1509
|
+
private _innerControl;
|
|
1510
|
+
/**
|
|
1511
|
+
* Gets a `HarnessPredicate` that can be used to search for a banner action
|
|
1512
|
+
* with a specific label.
|
|
1513
|
+
*
|
|
1514
|
+
* Scope it to the action slot when composing a locator by hand — unscoped, it
|
|
1515
|
+
* matches any `[tnBannerAction]` element on the page, which is why
|
|
1516
|
+
* `TnBannerHarness` always passes an `ancestor`.
|
|
1517
|
+
*
|
|
1518
|
+
* @param options Options for filtering which action instances are considered a match.
|
|
1519
|
+
* @returns A `HarnessPredicate` configured with the given options.
|
|
1520
|
+
*
|
|
1521
|
+
* @example
|
|
1522
|
+
* ```typescript
|
|
1523
|
+
* const retry = await loader.getHarness(
|
|
1524
|
+
* TnBannerActionHarness.with({ label: 'Retry', ancestor: '.tn-banner__action' })
|
|
1525
|
+
* );
|
|
1526
|
+
* ```
|
|
1527
|
+
*/
|
|
1528
|
+
static with(options?: BannerActionHarnessFilters): HarnessPredicate<TnBannerActionHarness>;
|
|
1529
|
+
/**
|
|
1530
|
+
* Gets the action's label text.
|
|
1531
|
+
*
|
|
1532
|
+
* For a projected `tn-button` this is the button's own label, read from its
|
|
1533
|
+
* label span so a rendered icon's sprite-fallback glyphs never leak in — the
|
|
1534
|
+
* same rule `TnButtonHarness.getLabel()` follows. For anything else it is the
|
|
1535
|
+
* element's trimmed text content.
|
|
1536
|
+
*
|
|
1537
|
+
* @returns Promise resolving to the action's label, trimmed of whitespace.
|
|
1538
|
+
*
|
|
1539
|
+
* @example
|
|
1540
|
+
* ```typescript
|
|
1541
|
+
* const [action] = await banner.getActions();
|
|
1542
|
+
* expect(await action.getLabel()).toBe('Learn more');
|
|
1543
|
+
* ```
|
|
1544
|
+
*/
|
|
1545
|
+
getLabel(): Promise<string>;
|
|
1546
|
+
/**
|
|
1547
|
+
* Clicks the action.
|
|
1548
|
+
*
|
|
1549
|
+
* Clicks the inner `button`/`a` when the action is a component that renders
|
|
1550
|
+
* one — a `tn-button` listens on that inner element, and a click dispatched
|
|
1551
|
+
* at the `tn-button` host would not reach it. Otherwise clicks the projected
|
|
1552
|
+
* element itself.
|
|
1553
|
+
*
|
|
1554
|
+
* @returns Promise that resolves when the click action is complete.
|
|
1555
|
+
*
|
|
1556
|
+
* @example
|
|
1557
|
+
* ```typescript
|
|
1558
|
+
* const [action] = await banner.getActions();
|
|
1559
|
+
* await action.click();
|
|
1560
|
+
* ```
|
|
1561
|
+
*/
|
|
1562
|
+
click(): Promise<void>;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* A set of criteria that can be used to filter a list of `TnBannerActionHarness` instances.
|
|
1566
|
+
*/
|
|
1567
|
+
interface BannerActionHarnessFilters extends BaseHarnessFilters {
|
|
1568
|
+
/** Filters by the action's label text. Supports string or regex matching. */
|
|
1569
|
+
label?: string | RegExp;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1170
1572
|
/**
|
|
1171
1573
|
* Harness for interacting with tn-banner in tests.
|
|
1172
|
-
* Provides
|
|
1574
|
+
* Provides text-based querying for existence checks, and access to the actions
|
|
1575
|
+
* the banner projects into its action slot.
|
|
1173
1576
|
*
|
|
1174
1577
|
* @example
|
|
1175
1578
|
* ```typescript
|
|
@@ -1185,6 +1588,9 @@ declare class TnBannerComponent {
|
|
|
1185
1588
|
* const hasBanner = await loader.hasHarness(
|
|
1186
1589
|
* TnBannerHarness.with({ textContains: /success/i })
|
|
1187
1590
|
* );
|
|
1591
|
+
*
|
|
1592
|
+
* // Press one of the banner's actions
|
|
1593
|
+
* await errorBanner.clickAction('Retry');
|
|
1188
1594
|
* ```
|
|
1189
1595
|
*/
|
|
1190
1596
|
declare class TnBannerHarness extends ComponentHarness {
|
|
@@ -1192,6 +1598,7 @@ declare class TnBannerHarness extends ComponentHarness {
|
|
|
1192
1598
|
* The selector for the host element of an `TnBannerComponent` instance.
|
|
1193
1599
|
*/
|
|
1194
1600
|
static hostSelector: string;
|
|
1601
|
+
private _actions;
|
|
1195
1602
|
/**
|
|
1196
1603
|
* Gets a `HarnessPredicate` that can be used to search for a banner
|
|
1197
1604
|
* with specific text content.
|
|
@@ -1226,6 +1633,47 @@ declare class TnBannerHarness extends ComponentHarness {
|
|
|
1226
1633
|
* ```
|
|
1227
1634
|
*/
|
|
1228
1635
|
getText(): Promise<string>;
|
|
1636
|
+
/**
|
|
1637
|
+
* Gets every action the banner projects into its action slot, in DOM order.
|
|
1638
|
+
*
|
|
1639
|
+
* Actions are not necessarily `tn-button`s — `[tnBannerAction]` is an
|
|
1640
|
+
* attribute directive that takes any element — so these come back as
|
|
1641
|
+
* `TnBannerActionHarness`, which reads a label and clicks whatever was
|
|
1642
|
+
* projected. Nothing is filtered out by element type.
|
|
1643
|
+
*
|
|
1644
|
+
* @returns Promise resolving to an array of `TnBannerActionHarness` instances.
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* ```typescript
|
|
1648
|
+
* const banner = await loader.getHarness(TnBannerHarness);
|
|
1649
|
+
* const actions = await banner.getActions();
|
|
1650
|
+
* expect(actions).toHaveLength(2);
|
|
1651
|
+
* expect(await actions[0].getLabel()).toBe('Retry');
|
|
1652
|
+
* ```
|
|
1653
|
+
*/
|
|
1654
|
+
getActions(): Promise<TnBannerActionHarness[]>;
|
|
1655
|
+
/**
|
|
1656
|
+
* Clicks one of the banner's actions by its label, matching the first in DOM
|
|
1657
|
+
* order. Only matches inside the `.tn-banner__action` slot, not controls in
|
|
1658
|
+
* the banner's default content.
|
|
1659
|
+
*
|
|
1660
|
+
* Named `clickAction` rather than `TnDialogHarness`'s `clickActionButton`
|
|
1661
|
+
* because a banner action need not be a button: a projected `<a
|
|
1662
|
+
* tnBannerAction>` is reached by this method exactly as a `tn-button` is.
|
|
1663
|
+
*
|
|
1664
|
+
* @param label The action label to match. Supports string or regex.
|
|
1665
|
+
* @throws Error naming the label, and the labels actually present, if nothing matches.
|
|
1666
|
+
*
|
|
1667
|
+
* @example
|
|
1668
|
+
* ```typescript
|
|
1669
|
+
* const banner = await loader.getHarness(
|
|
1670
|
+
* TnBannerHarness.with({ textContains: 'network error' })
|
|
1671
|
+
* );
|
|
1672
|
+
* await banner.clickAction('Retry');
|
|
1673
|
+
* await banner.clickAction(/learn more/i);
|
|
1674
|
+
* ```
|
|
1675
|
+
*/
|
|
1676
|
+
clickAction(label: string | RegExp): Promise<void>;
|
|
1229
1677
|
}
|
|
1230
1678
|
/**
|
|
1231
1679
|
* A set of criteria that can be used to filter a list of `TnBannerHarness` instances.
|
|
@@ -1266,7 +1714,7 @@ declare class TnButtonComponent implements AfterViewInit {
|
|
|
1266
1714
|
* form's `(submit)`/`(ngSubmit)` handler; a `(onClick)` binding alone does
|
|
1267
1715
|
* not. Ignored in anchor mode (`href`/`routerLink`).
|
|
1268
1716
|
*/
|
|
1269
|
-
type: _angular_core.InputSignal<"button" | "
|
|
1717
|
+
type: _angular_core.InputSignal<"button" | "reset" | "submit">;
|
|
1270
1718
|
/**
|
|
1271
1719
|
* Semantic test-id base for the rendered element. The library prepends the
|
|
1272
1720
|
* element type (`button`) and renders the result under whichever attribute
|
|
@@ -1514,12 +1962,61 @@ declare class TnTooltipDirective implements AfterViewInit, OnDestroy {
|
|
|
1514
1962
|
showDelay: _angular_core.InputSignal<number>;
|
|
1515
1963
|
hideDelay: _angular_core.InputSignal<number>;
|
|
1516
1964
|
tooltipClass: _angular_core.InputSignal<string>;
|
|
1965
|
+
/**
|
|
1966
|
+
* Allows the tooltip to be pinned ("stuck") open so its content can be interacted with —
|
|
1967
|
+
* needed for tooltips that contain links or other controls.
|
|
1968
|
+
*
|
|
1969
|
+
* UPGRADING: this defaults to `true`, so an existing tooltip whose message happens to contain a
|
|
1970
|
+
* link changes behaviour with no code change on your side — it stops appearing on hover and on
|
|
1971
|
+
* keyboard focus, and the host's click opens it instead. Nothing else about the host changes.
|
|
1972
|
+
* Anything that forwards a caller-supplied message to a `<button>` is a candidate: inside this
|
|
1973
|
+
* library that is `<tn-form-field [tooltip]>`, `<tn-form-section [tooltip]>`, `<tn-card>`'s
|
|
1974
|
+
* title and action tooltips and `<tn-icon-button [tooltip]>`, all of which render a button and
|
|
1975
|
+
* pass the message straight through. Each re-exports this as a `tooltipSticky` input, so
|
|
1976
|
+
* `[tooltipSticky]="false"` on any of them keeps the old hover behaviour, at the cost of the
|
|
1977
|
+
* link staying out of reach.
|
|
1978
|
+
*
|
|
1979
|
+
* A host whose click is already spoken for wants that opt-out permanently, not on upgrade: a
|
|
1980
|
+
* `tnMenuTrigger` would raise the panel over the menu the same click opens, and lose the hover
|
|
1981
|
+
* hint doing it. `<tn-card>`'s kebab-menu trigger passes `false` for exactly that reason, which
|
|
1982
|
+
* is why it is not in the list above.
|
|
1983
|
+
*
|
|
1984
|
+
* This only narrows the rule in `_isPinnable`, it cannot widen it: plain help text is never
|
|
1985
|
+
* pinnable however this is set, and neither is a message on a host that cannot deliver the
|
|
1986
|
+
* click - see `_isHostClickBlocked`. Setting it to false forces a message that does hold a
|
|
1987
|
+
* link back into plain hover behaviour, where the link is unreachable.
|
|
1988
|
+
*
|
|
1989
|
+
* Pinning is not a second stage layered on hover: a tooltip that can be pinned is opened by
|
|
1990
|
+
* clicking the host and by nothing else, because a tooltip that appeared on hover and then had
|
|
1991
|
+
* to be clicked made the user chase a target that was already on screen. See `_isPinnable`
|
|
1992
|
+
* for which tooltips this applies to.
|
|
1993
|
+
*
|
|
1994
|
+
* While pinned the tooltip renders a dismiss button and ignores `mouseleave` and blur; it
|
|
1995
|
+
* closes on a second click of the host, on the dismiss button, on an outside click, or on
|
|
1996
|
+
* Escape.
|
|
1997
|
+
*
|
|
1998
|
+
* The pinning click is additive, not exclusive: the host's own click handler still runs, so a
|
|
1999
|
+
* `<tn-button (click)="save()">` carrying a message with a link both saves and pins. Suppressing
|
|
2000
|
+
* the host's action would be worse — a menu trigger or a toggle that silently stopped working
|
|
2001
|
+
* because someone put a link in its tooltip — but a host that navigates away should either keep
|
|
2002
|
+
* its tooltip plain or set this to false.
|
|
2003
|
+
*/
|
|
2004
|
+
stickyEnabled: _angular_core.InputSignal<boolean>;
|
|
2005
|
+
/** Accessible name for the dismiss button rendered in sticky mode. */
|
|
2006
|
+
closeAriaLabel: _angular_core.InputSignal<string>;
|
|
2007
|
+
/** Accessible name for the panel itself once pinned, where it is announced as a dialog. */
|
|
2008
|
+
panelAriaLabel: _angular_core.InputSignal<string>;
|
|
1517
2009
|
private _overlayRef;
|
|
1518
2010
|
private _tooltipInstance;
|
|
1519
2011
|
private _showTimeout;
|
|
1520
2012
|
private _hideTimeout;
|
|
1521
2013
|
private _isTooltipVisible;
|
|
2014
|
+
private _isSticky;
|
|
1522
2015
|
private _positionSub;
|
|
2016
|
+
private _repositionSub;
|
|
2017
|
+
private _escapeSub;
|
|
2018
|
+
private _outsideClickSub;
|
|
2019
|
+
private _dismissSub;
|
|
1523
2020
|
private _focusSub;
|
|
1524
2021
|
private _tooltipId;
|
|
1525
2022
|
private _overlay;
|
|
@@ -1527,18 +2024,58 @@ declare class TnTooltipDirective implements AfterViewInit, OnDestroy {
|
|
|
1527
2024
|
private _viewContainerRef;
|
|
1528
2025
|
private _overlayPositionBuilder;
|
|
1529
2026
|
private _ariaDescriber;
|
|
2027
|
+
private _scrollDispatcher;
|
|
2028
|
+
private _viewportRuler;
|
|
1530
2029
|
private _focusMonitor;
|
|
1531
2030
|
private _ngZone;
|
|
1532
2031
|
private _viewInitialized;
|
|
1533
2032
|
private _describedTarget;
|
|
1534
2033
|
private _describedMessage;
|
|
1535
2034
|
private _innerObserver;
|
|
2035
|
+
private _popupStateTarget;
|
|
2036
|
+
private _popupStateWritten;
|
|
2037
|
+
private _popupStateSelfWrites;
|
|
1536
2038
|
/**
|
|
1537
|
-
*
|
|
2039
|
+
* Which disclosure attributes the host has been seen writing for itself, per element.
|
|
2040
|
+
*
|
|
2041
|
+
* Per element for the same reason `_popupStateSelfWrites` is, and then some: ownership is a
|
|
2042
|
+
* fact about the element that carries the attribute, and the target moves (`<tn-button>`
|
|
2043
|
+
* swapping between `<a>` and `<button>` through an `@if`). Held as bare names it outlived the
|
|
2044
|
+
* element it was learned from and denied the disclosure state to a replacement carrying no
|
|
2045
|
+
* attributes of its own — permanently, since the yielded state leaves `_popupStateTarget`
|
|
2046
|
+
* null and so hid the target change from the reset in `_writeHostPopupState`. Keyed by
|
|
2047
|
+
* element, the replacement starts owning nothing because it does, and the fact survives for
|
|
2048
|
+
* as long as the element it is about.
|
|
2049
|
+
*/
|
|
2050
|
+
private _popupStateHostOwned;
|
|
2051
|
+
private _cachedArrowInset;
|
|
2052
|
+
/**
|
|
2053
|
+
* Whether this tooltip is opened by a click and pinned, rather than shown on hover.
|
|
2054
|
+
*
|
|
2055
|
+
* Only messages carrying content the reader can reach earn the click interaction, because they
|
|
2056
|
+
* are the ones a hover tooltip cannot serve — it disappears on the way to the link. Plain help
|
|
2057
|
+
* text, which is the overwhelming majority, keeps the hover behaviour and never pins: pinning
|
|
2058
|
+
* it would cost a click and buy the reader nothing.
|
|
2059
|
+
*
|
|
2060
|
+
* `hasInteractiveContent` is deliberately strict about what counts, since the message is
|
|
2061
|
+
* sanitized before it renders — see `REACHABLE_CONTENT_SELECTOR`.
|
|
2062
|
+
*/
|
|
2063
|
+
private readonly _isPinnable;
|
|
2064
|
+
/**
|
|
2065
|
+
* Keeps a panel that is already on screen in step with its inputs.
|
|
2066
|
+
*
|
|
2067
|
+
* `_attachTooltip` seeds these once, which was enough while every panel was a hover panel that
|
|
2068
|
+
* lived for a second. A pinned panel stays up until the user dismisses it, so a message that
|
|
2069
|
+
* changes underneath it would leave the rendered text — and the link the user is about to
|
|
2070
|
+
* click — disagreeing with the description `_syncAriaDescription` has already moved on to.
|
|
2071
|
+
*/
|
|
2072
|
+
private readonly _syncPanelInputs;
|
|
2073
|
+
/**
|
|
2074
|
+
* Re-sync the ARIA attributes (see ngAfterViewInit) when the inputs change. The initial
|
|
1538
2075
|
* write cannot happen here: on the first run the host's child components (e.g.
|
|
1539
2076
|
* tn-button's inner `<button>`) have not rendered yet.
|
|
1540
2077
|
*/
|
|
1541
|
-
private readonly
|
|
2078
|
+
private readonly _syncAriaOnInputChange;
|
|
1542
2079
|
/**
|
|
1543
2080
|
* Expose the message to assistive tech via CDK's AriaDescriber: it keeps the text in
|
|
1544
2081
|
* a persistent visually-hidden element (so `aria-describedby` never dangles — the
|
|
@@ -1549,31 +2086,295 @@ declare class TnTooltipDirective implements AfterViewInit, OnDestroy {
|
|
|
1549
2086
|
* because screen readers read descriptions off the focused control, not wrappers.
|
|
1550
2087
|
*/
|
|
1551
2088
|
ngAfterViewInit(): void;
|
|
2089
|
+
private _syncAria;
|
|
2090
|
+
/**
|
|
2091
|
+
* Watches the host for the things `_syncAria` reads out of the DOM rather than out of a signal,
|
|
2092
|
+
* for as long as any of them can still change the answer.
|
|
2093
|
+
*
|
|
2094
|
+
* Three things move underneath it. The inner control can render after view init (`@if` branches
|
|
2095
|
+
* inside the wrapper swapping, deferred content). `disabled`/`aria-disabled` can be toggled on
|
|
2096
|
+
* it at any time, which decides whether the pinning click can arrive at all, and so whether the
|
|
2097
|
+
* host advertises itself as a disclosure control; `tabindex` and `href` read the same way,
|
|
2098
|
+
* through `_isHostKeyboardOperable` and through `_ariaTarget`'s `INTERACTIVE_SELECTOR` match —
|
|
2099
|
+
* both selectors say `a[href]`, so an anchor dropping its `href` stops being activatable
|
|
2100
|
+
* exactly as one taking `tabindex="-1"` does, and the disclosure state has to come off it. The
|
|
2101
|
+
* disclosure attributes are watched for a different reason: to notice the host writing one of
|
|
2102
|
+
* them itself. See `_absorbPopupStateRecords`, which also keeps our own writes of them from
|
|
2103
|
+
* retriggering this. `aria-describedby` stays outside the filter, so `AriaDescriber` cannot
|
|
2104
|
+
* loop it.
|
|
2105
|
+
*
|
|
2106
|
+
* None of that can matter on a host that is itself the control (`<button tnTooltip>`,
|
|
2107
|
+
* `tn-icon-button`'s inner button — the overwhelming majority) while its tooltip is a plain
|
|
2108
|
+
* hover one: `_ariaTarget` is the host whatever the subtree does, and the rest is read only on
|
|
2109
|
+
* the way to pinning something. A table rendering hundreds of tooltip'd action buttons would
|
|
2110
|
+
* otherwise carry hundreds of live subtree observers with nothing to report, so those hosts go
|
|
2111
|
+
* unobserved until their message turns pinnable — or until `stick()` pins one anyway, which it
|
|
2112
|
+
* will do on a message the host click never would have.
|
|
2113
|
+
*/
|
|
2114
|
+
private _observeHost;
|
|
2115
|
+
private _disconnectHostObserver;
|
|
2116
|
+
/**
|
|
2117
|
+
* The element the tooltip's ARIA attributes belong on: the host when it is itself a control,
|
|
2118
|
+
* otherwise its single interactive descendant (e.g. `<tn-button>`'s inner `<button>`), because
|
|
2119
|
+
* screen readers read descriptions and states off the focused control, not off wrappers.
|
|
2120
|
+
*
|
|
2121
|
+
* A container holding several controls keeps them on the host — annotating an arbitrary first
|
|
2122
|
+
* control would attach the text to the wrong element.
|
|
2123
|
+
*/
|
|
2124
|
+
private _ariaTarget;
|
|
2125
|
+
/**
|
|
2126
|
+
* Whether the host is in a state where the click that pins the tooltip cannot be relied on.
|
|
2127
|
+
*
|
|
2128
|
+
* A disabled control is: a native disabled `<button>` fires no click at all, and
|
|
2129
|
+
* `<tn-button [disabled]>` swallows the retargeted one in a capture-phase listener before this
|
|
2130
|
+
* directive's host binding runs. `aria-disabled` is the exception that keeps this a rule about
|
|
2131
|
+
* intent rather than about event plumbing — it is advisory, so the element still dispatches
|
|
2132
|
+
* clicks normally. `_onClick` therefore declines to pin for any host this reports, rather than
|
|
2133
|
+
* relying on the click not showing up. Suppressing hover for a pinnable message would then leave the
|
|
2134
|
+
* tooltip with no way in whatsoever — and a disabled control with a tooltip explaining why,
|
|
2135
|
+
* docs link included, is a normal thing to build. Those fall back to plain hover behaviour,
|
|
2136
|
+
* which is what they did before pinning existed: the link stays out of reach, but the
|
|
2137
|
+
* explanation does not.
|
|
2138
|
+
*/
|
|
2139
|
+
private _isHostClickBlocked;
|
|
2140
|
+
/**
|
|
2141
|
+
* Whether the element carrying the tooltip's ARIA state can be operated from the keyboard.
|
|
2142
|
+
*
|
|
2143
|
+
* The click is the only way into a pinned panel, so a host that cannot produce one from the
|
|
2144
|
+
* keyboard would put the tooltip out of reach of keyboard users entirely — and would write
|
|
2145
|
+
* `aria-expanded`, advertising a disclosure they cannot operate. Two host shapes fail that way:
|
|
2146
|
+
*
|
|
2147
|
+
* - `_ariaTarget` falls back to the bare host when it is not a control and holds no single
|
|
2148
|
+
* interactive descendant — `<span [tnTooltip]="'… <a href>…'">` is exactly that. It can be
|
|
2149
|
+
* clicked with a pointer, so nothing in `_isHostClickBlocked` catches it, but it cannot be
|
|
2150
|
+
* focused or activated at all, and `aria-expanded` is invalid on its implicit `generic` role.
|
|
2151
|
+
* A host wearing `role="button"` is the same case: the role renames it for assistive tech
|
|
2152
|
+
* without making the browser synthesise a click for it.
|
|
2153
|
+
* - A text control (`<input>`, `<select>`, `<textarea>`) is focusable but not *activatable*:
|
|
2154
|
+
* Enter submits the form and Space types a space, so no click ever arrives. On top of that,
|
|
2155
|
+
* every pointer click into the field — placing the caret — would toggle the panel.
|
|
2156
|
+
*
|
|
2157
|
+
* The question is therefore "does a click on this element mean *activate me*", which is what
|
|
2158
|
+
* `KEYBOARD_ACTIVATABLE_SELECTOR` answers; `INTERACTIVE_SELECTOR` answers the broader "is this
|
|
2159
|
+
* the element ARIA belongs on" and is too wide to stand in for it. Both shapes fall back to
|
|
2160
|
+
* plain hover, which is what they did before pinning existed.
|
|
2161
|
+
*
|
|
2162
|
+
* `tabindex="-1"` does not count: it makes an element a focus target without putting it in the
|
|
2163
|
+
* tab order, and `_restoreFocusTarget` leaves one behind on hosts it had to focus by hand.
|
|
2164
|
+
*/
|
|
2165
|
+
private _isHostKeyboardOperable;
|
|
2166
|
+
/**
|
|
2167
|
+
* Whether this tooltip is opened by clicking its host, rather than on hover.
|
|
2168
|
+
*
|
|
2169
|
+
* `_isPinnable` is the message's half of that decision; the host has the other half, and both
|
|
2170
|
+
* have to agree. A host that cannot deliver the pinning click, or cannot be operated from the
|
|
2171
|
+
* keyboard at all, goes back to being a hover tooltip.
|
|
2172
|
+
*/
|
|
2173
|
+
private _pinsOnClick;
|
|
2174
|
+
/**
|
|
2175
|
+
* Marks a pinnable host as the disclosure control for its tooltip.
|
|
2176
|
+
*
|
|
2177
|
+
* Nothing about a plain button says "clicking me reveals something", so the host has to carry
|
|
2178
|
+
* the state that does: `aria-expanded` for whether the panel is currently up, `aria-haspopup`
|
|
2179
|
+
* for what kind of thing it opens (a pinned panel is a `dialog` — see `TnTooltipComponent`'s
|
|
2180
|
+
* `sticky`), and `aria-controls` pointing at the panel while it exists, so assistive tech can
|
|
2181
|
+
* jump to it. A hover tooltip reveals nothing on activation and carries none of this.
|
|
2182
|
+
*/
|
|
2183
|
+
private _syncHostPopupState;
|
|
2184
|
+
/**
|
|
2185
|
+
* Writes the disclosure attributes, remembering the exact value written for each.
|
|
2186
|
+
*
|
|
2187
|
+
* A host that carries any of them keeps all three, and this directive never removes or
|
|
2188
|
+
* overwrites a value it did not itself put there. Hosts own these legitimately and mean
|
|
2189
|
+
* something else by them: a `tnMenuTrigger` is `aria-haspopup="menu"`, a `<tn-select>` points
|
|
2190
|
+
* `aria-controls` at its own listbox, and `<tn-icon-button [ariaExpanded]>` binds
|
|
2191
|
+
* `aria-expanded` to the same inner `<button>` a `tnTooltip` on it would land on. There is only
|
|
2192
|
+
* one of each attribute to go around, and the host's click is what they describe — a tooltip is
|
|
2193
|
+
* the lesser claim.
|
|
2194
|
+
*
|
|
2195
|
+
* Ownership is not re-derived from the current value each time, because a value comparison
|
|
2196
|
+
* cannot tell "the host wrote nothing" from "the host wrote the same string we did" — and
|
|
2197
|
+
* `aria-expanded="false"` is exactly that string. `<tn-icon-button [ariaExpanded]="expanded()">`
|
|
2198
|
+
* with `expanded()` starting `undefined` walks straight into it: the attribute is absent at the
|
|
2199
|
+
* first sync so the tooltip claims it, the consumer later sets `false`, Angular writes the same
|
|
2200
|
+
* `"false"` the tooltip wrote, and on pin the host's collapsed popup would be announced as
|
|
2201
|
+
* expanded. So a write the tooltip did not make is recorded as a fact when it happens — see
|
|
2202
|
+
* `_absorbPopupStateRecords` — and the host keeps the attribute from then on.
|
|
2203
|
+
*
|
|
2204
|
+
* And it is decided for the group, not per attribute: the three describe one popup between
|
|
2205
|
+
* them, so yielding them one at a time would leave the host describing two. A menu trigger
|
|
2206
|
+
* carrying its own `aria-expanded="true"` would keep that value and still take
|
|
2207
|
+
* `aria-haspopup="dialog"` and an `aria-controls` pointing at the tooltip panel, announcing
|
|
2208
|
+
* "expanded dialog controlling tn-tooltip-xxx" for a panel that may well be closed. So if any
|
|
2209
|
+
* one of them is spoken for, the tooltip writes none of them and the host keeps the coherent
|
|
2210
|
+
* state it owns. Nothing about reaching the panel depends on them: it still opens on the host
|
|
2211
|
+
* click and closes on Escape, an outside click or the dismiss button.
|
|
2212
|
+
*/
|
|
2213
|
+
private _writeHostPopupState;
|
|
2214
|
+
/**
|
|
2215
|
+
* Whether the attribute is free to write: never written by the host, and either absent or still
|
|
2216
|
+
* holding the value written for it.
|
|
2217
|
+
*/
|
|
2218
|
+
private _ownsHostAttribute;
|
|
2219
|
+
/**
|
|
2220
|
+
* Separates the observer records caused by this directive's own writes from the rest, and
|
|
2221
|
+
* returns whether what is left is worth a re-sync.
|
|
2222
|
+
*
|
|
2223
|
+
* Every mutation of a disclosure attribute arrives here, including the ones `_writeHostPopupState`
|
|
2224
|
+
* just made — which is why `attributeFilter` can list them without looping. Each write registers
|
|
2225
|
+
* itself in `_popupStateSelfWrites` first, and one write produces exactly one record, so the
|
|
2226
|
+
* counter cancels them out one for one. Anything left over came from the host, and that is the
|
|
2227
|
+
* fact worth keeping: the value it wrote may well be the one already there.
|
|
2228
|
+
*/
|
|
2229
|
+
private _absorbPopupStateRecords;
|
|
2230
|
+
private _setPopupStateAttribute;
|
|
2231
|
+
/** Removing an attribute that is not there mutates nothing, so it must not be counted either. */
|
|
2232
|
+
private _removePopupStateAttribute;
|
|
2233
|
+
/**
|
|
2234
|
+
* Registers a write of our own so `_absorbPopupStateRecords` can cancel out the record it
|
|
2235
|
+
* produces. Only writes made while the observer is connected produce one — the first
|
|
2236
|
+
* `_syncAria` runs before `ngAfterViewInit` has created it, and counting that one would leave a
|
|
2237
|
+
* credit behind for the host's first write to spend.
|
|
2238
|
+
*
|
|
2239
|
+
* Kept per element rather than per attribute name, because a write and the record it produces
|
|
2240
|
+
* can come apart when the element does. A wrapper swapping its inner control (`<tn-button>`
|
|
2241
|
+
* moving between `<a>` and `<button>` through an `@if`) has `_clearHostPopupState` write to the
|
|
2242
|
+
* outgoing element, which is detached by then and so is no longer observed: no record is ever
|
|
2243
|
+
* queued for it. A single shared ledger would carry those credits over to the incoming element
|
|
2244
|
+
* and absorb the host's own writes there as if they were ours. Per element, they are stranded
|
|
2245
|
+
* on a node nothing consults again, and the WeakMap lets it go.
|
|
2246
|
+
*/
|
|
2247
|
+
private _countPopupStateWrite;
|
|
2248
|
+
private _clearHostPopupState;
|
|
1552
2249
|
private _syncAriaDescription;
|
|
1553
|
-
/**
|
|
1554
|
-
* The overlay tooltip renders its message as HTML (`[innerHTML]`), but AriaDescriber
|
|
1555
|
-
* writes the description as plain text — strip any markup so screen readers never
|
|
1556
|
-
* announce literal tags. DOMParser parses inert markup (no script execution).
|
|
1557
|
-
*/
|
|
1558
|
-
private _plainTextMessage;
|
|
1559
2250
|
private _removeAriaDescription;
|
|
1560
2251
|
ngOnDestroy(): void;
|
|
1561
2252
|
_onMouseEnter(): void;
|
|
1562
2253
|
_onMouseLeave(): void;
|
|
2254
|
+
_onClick(event: MouseEvent): void;
|
|
1563
2255
|
_onKeydown(event: KeyboardEvent): void;
|
|
1564
2256
|
/** Shows the tooltip */
|
|
1565
2257
|
show(delay?: number): void;
|
|
1566
|
-
/** Hides the tooltip */
|
|
2258
|
+
/** Hides the tooltip, unpinning it if it was sticky */
|
|
1567
2259
|
hide(delay?: number): void;
|
|
1568
|
-
/**
|
|
2260
|
+
/**
|
|
2261
|
+
* Toggles the tooltip, opening it the way its host's own click would.
|
|
2262
|
+
*
|
|
2263
|
+
* Routed through `_pinsOnClick` for the same reason `_onClick` is: `show()` on a pinnable
|
|
2264
|
+
* message puts up a decorative, `aria-hidden` panel with `pointer-events: none`, so the link
|
|
2265
|
+
* inside it cannot be clicked and `mouseleave` takes it away again — the unreachable state pinning exists
|
|
2266
|
+
* to replace, which a public method should not be able to produce either.
|
|
2267
|
+
*/
|
|
1569
2268
|
toggle(): void;
|
|
2269
|
+
/** Whether the tooltip is currently pinned open */
|
|
2270
|
+
isSticky(): boolean;
|
|
2271
|
+
/**
|
|
2272
|
+
* Pins the tooltip open. Shows it first if it isn't visible yet, so it works both as a
|
|
2273
|
+
* follow-up to hover and on its own (e.g. a keyboard-activated host).
|
|
2274
|
+
*
|
|
2275
|
+
* This is the imperative escape hatch: it pins any tooltip with a message, ignoring both
|
|
2276
|
+
* `tnTooltipSticky` and the interactive-content rule that decide whether the *host click* pins
|
|
2277
|
+
* one. A tooltip pinned this way behaves like any other pinned tooltip — dismiss button,
|
|
2278
|
+
* Escape, outside click, and a host click all close it again.
|
|
2279
|
+
*
|
|
2280
|
+
* @param options.focusTooltip Move focus into the tooltip, so its content is reachable
|
|
2281
|
+
* without a pointer.
|
|
2282
|
+
*/
|
|
2283
|
+
stick(options?: {
|
|
2284
|
+
focusTooltip?: boolean;
|
|
2285
|
+
}): void;
|
|
2286
|
+
/**
|
|
2287
|
+
* Unpins and hides the tooltip.
|
|
2288
|
+
*
|
|
2289
|
+
* @param restoreFocus Move focus back to the host. Used when the tooltip is dismissed from
|
|
2290
|
+
* the keyboard, where focus would otherwise be lost with the removed element.
|
|
2291
|
+
*/
|
|
2292
|
+
unstick(restoreFocus?: boolean): void;
|
|
2293
|
+
/**
|
|
2294
|
+
* Drops the pinned flag and re-advertises the host without it.
|
|
2295
|
+
*
|
|
2296
|
+
* Shared by `unstick()` and `_destroyTooltip` rather than done in either, so the flag and the
|
|
2297
|
+
* disclosure state it drives cannot come apart: whichever gets there first does both halves,
|
|
2298
|
+
* and the other finds nothing left to do. Closing routes that do not go through `unstick()` —
|
|
2299
|
+
* Escape on the host, a disabled input, `ngOnDestroy` — still arrive here through the teardown.
|
|
2300
|
+
*/
|
|
2301
|
+
private _releaseSticky;
|
|
2302
|
+
/**
|
|
2303
|
+
* Where focus goes when a pinned tooltip is dismissed from the keyboard.
|
|
2304
|
+
*
|
|
2305
|
+
* A non-focusable host never pins on its own click (see `_isHostKeyboardOperable`), but
|
|
2306
|
+
* `stick()` pins whatever it is called on — `<span tnTooltip="… <a href>…">` included — so
|
|
2307
|
+
* focusing the host blindly is a no-op there, and tearing the panel down straight after drops
|
|
2308
|
+
* focus to `<body>`. Prefer the element that would carry the tooltip's
|
|
2309
|
+
* ARIA state, and if even that cannot hold focus, make the host able to: `tabindex="-1"` keeps
|
|
2310
|
+
* it out of the tab order while letting it be a focus target, and is left in place because
|
|
2311
|
+
* removing it again would drop the focus it was added to catch.
|
|
2312
|
+
*/
|
|
2313
|
+
private _restoreFocusTarget;
|
|
1570
2314
|
private _createOverlay;
|
|
2315
|
+
/**
|
|
2316
|
+
* Points the speech-bubble arrow at the host rather than at the panel's own centre.
|
|
2317
|
+
*
|
|
2318
|
+
* The two only coincide when the panel is perfectly centred on its origin. They come apart
|
|
2319
|
+
* whenever the panel is nudged sideways to stay inside the viewport, or when it is resized
|
|
2320
|
+
* after being placed (entering sticky mode), which used to leave the arrow pointing at empty
|
|
2321
|
+
* space next to the control it belongs to.
|
|
2322
|
+
*/
|
|
2323
|
+
private _updateArrowOffset;
|
|
2324
|
+
/**
|
|
2325
|
+
* How far the arrow has to stay from the panel's edge: its own half-base plus the corner
|
|
2326
|
+
* radius. Both are read off the rendered panel, so a change in the stylesheet cannot leave a
|
|
2327
|
+
* stale number behind here.
|
|
2328
|
+
*
|
|
2329
|
+
* Cached for as long as the panel is attached. Only the stylesheet can move these, while the
|
|
2330
|
+
* caller runs on every reposition — which, with the reposition scroll strategy, is every 20ms
|
|
2331
|
+
* for as long as the user keeps scrolling; a `getComputedStyle` per frame to re-read two
|
|
2332
|
+
* constants is not worth it.
|
|
2333
|
+
*/
|
|
2334
|
+
private _arrowInset;
|
|
1571
2335
|
private _resolvePosition;
|
|
1572
2336
|
private _attachTooltip;
|
|
2337
|
+
/**
|
|
2338
|
+
* Keeps the arrow pointing at the host across re-placements that `positionChanges` does not
|
|
2339
|
+
* report.
|
|
2340
|
+
*
|
|
2341
|
+
* CDK emits `positionChanges` from `_applyPosition` behind
|
|
2342
|
+
* `position !== this._lastPosition || scrollVisibility changed` (cdk 21.1.0), so a panel
|
|
2343
|
+
* re-placed at the *same* position with different coordinates — viewport clamping, which is the
|
|
2344
|
+
* case the arrow offset exists for — emits nothing at all. Listening to the events that drive
|
|
2345
|
+
* those re-placements covers it; the side-placement specs in `tooltip.directive.spec.ts` fail
|
|
2346
|
+
* without this. On a genuine flip both paths run, which is two rect reads either way since the
|
|
2347
|
+
* inset is cached.
|
|
2348
|
+
*
|
|
2349
|
+
* Subscribed here rather than in `_createOverlay`, and deliberately after
|
|
2350
|
+
* `this._overlayRef.attach()`, because the arrow has to be measured against the pane's *new*
|
|
2351
|
+
* position. `RepositionScrollStrategy.enable()` reaches `_scrollDispatcher.scrolled()` through
|
|
2352
|
+
* the same shared subject and the same audit window that this does, so the two run in
|
|
2353
|
+
* subscription order — and `enable()` is called from inside `attach()`. Subscribing before it
|
|
2354
|
+
* put `_updateArrowOffset` first on every scroll tick, reading the pane rect from before the
|
|
2355
|
+
* re-placement: on a pinned side-placed panel, a vertical scroll moved `hostRect.top` while
|
|
2356
|
+
* `panelRect.top` still held the previous value, and since a same-position re-placement emits
|
|
2357
|
+
* no `positionChanges` nothing came along to correct it.
|
|
2358
|
+
*
|
|
2359
|
+
* Re-attaching re-runs the CDK's subscriptions too, so this has to be renewed per attach rather
|
|
2360
|
+
* than held for the lifetime of the overlay - hence the teardown in `_destroyTooltip`.
|
|
2361
|
+
*/
|
|
2362
|
+
private _subscribeToReposition;
|
|
2363
|
+
/**
|
|
2364
|
+
* Listens for Escape only while the tooltip is pinned. The CDK keyboard dispatcher hands the
|
|
2365
|
+
* event to the top-most overlay that has subscribers and stops there, so a permanent
|
|
2366
|
+
* subscription would let a plain hover tooltip swallow the Escape meant for the dialog it
|
|
2367
|
+
* sits in.
|
|
2368
|
+
*/
|
|
2369
|
+
private _subscribeToEscape;
|
|
2370
|
+
/** Dismisses a pinned tooltip when the user clicks anything else on the page. */
|
|
2371
|
+
private _subscribeToOutsideClicks;
|
|
2372
|
+
private _destroyTooltip;
|
|
2373
|
+
private _isFocusInsideTooltip;
|
|
1573
2374
|
private _getPositions;
|
|
1574
2375
|
private _clearTimeouts;
|
|
1575
2376
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTooltipDirective, never>;
|
|
1576
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnTooltipDirective, "[tnTooltip]", never, { "message": { "alias": "tnTooltip"; "required": false; "isSignal": true; }; "position": { "alias": "tnTooltipPosition"; "required": false; "isSignal": true; }; "disabled": { "alias": "tnTooltipDisabled"; "required": false; "isSignal": true; }; "showDelay": { "alias": "tnTooltipShowDelay"; "required": false; "isSignal": true; }; "hideDelay": { "alias": "tnTooltipHideDelay"; "required": false; "isSignal": true; }; "tooltipClass": { "alias": "tnTooltipClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
2377
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnTooltipDirective, "[tnTooltip]", never, { "message": { "alias": "tnTooltip"; "required": false; "isSignal": true; }; "position": { "alias": "tnTooltipPosition"; "required": false; "isSignal": true; }; "disabled": { "alias": "tnTooltipDisabled"; "required": false; "isSignal": true; }; "showDelay": { "alias": "tnTooltipShowDelay"; "required": false; "isSignal": true; }; "hideDelay": { "alias": "tnTooltipHideDelay"; "required": false; "isSignal": true; }; "tooltipClass": { "alias": "tnTooltipClass"; "required": false; "isSignal": true; }; "stickyEnabled": { "alias": "tnTooltipSticky"; "required": false; "isSignal": true; }; "closeAriaLabel": { "alias": "tnTooltipCloseAriaLabel"; "required": false; "isSignal": true; }; "panelAriaLabel": { "alias": "tnTooltipAriaLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
1577
2378
|
}
|
|
1578
2379
|
|
|
1579
2380
|
declare class TnIconButtonComponent implements AfterViewInit {
|
|
@@ -1596,6 +2397,16 @@ declare class TnIconButtonComponent implements AfterViewInit {
|
|
|
1596
2397
|
tooltip: _angular_core.InputSignal<string | undefined>;
|
|
1597
2398
|
/** Position of the styled tooltip relative to the button. */
|
|
1598
2399
|
tooltipPosition: _angular_core.InputSignal<TooltipPosition>;
|
|
2400
|
+
/**
|
|
2401
|
+
* Whether a tooltip message holding a link may be pinned open by clicking the button (see
|
|
2402
|
+
* `tnTooltipSticky`). On by default, like the directive.
|
|
2403
|
+
*
|
|
2404
|
+
* It does not make plain tooltips pinnable — an icon button's tooltip is nearly always a label
|
|
2405
|
+
* for its action, and those keep hovering and never touch the click. Set it to false only to
|
|
2406
|
+
* force a message that does hold a link back to hover behaviour, accepting that the link is
|
|
2407
|
+
* then unreachable.
|
|
2408
|
+
*/
|
|
2409
|
+
tooltipSticky: _angular_core.InputSignal<boolean>;
|
|
1599
2410
|
library: _angular_core.InputSignal<IconLibraryType | undefined>;
|
|
1600
2411
|
/** Extra class(es) applied to the inner icon, e.g. for animations or state colors. */
|
|
1601
2412
|
iconClass: _angular_core.InputSignal<string>;
|
|
@@ -1612,7 +2423,7 @@ declare class TnIconButtonComponent implements AfterViewInit {
|
|
|
1612
2423
|
focus(options?: FocusOptions): void;
|
|
1613
2424
|
ngAfterViewInit(): void;
|
|
1614
2425
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnIconButtonComponent, never>;
|
|
1615
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnIconButtonComponent, "tn-icon-button", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dense": { "alias": "dense"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaExpanded": { "alias": "ariaExpanded"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "library": { "alias": "library"; "required": false; "isSignal": true; }; "iconClass": { "alias": "iconClass"; "required": false; "isSignal": true; }; }, { "onClick": "onClick"; }, never, ["*"], true, never>;
|
|
2426
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnIconButtonComponent, "tn-icon-button", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dense": { "alias": "dense"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaExpanded": { "alias": "ariaExpanded"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "tooltipSticky": { "alias": "tooltipSticky"; "required": false; "isSignal": true; }; "library": { "alias": "library"; "required": false; "isSignal": true; }; "iconClass": { "alias": "iconClass"; "required": false; "isSignal": true; }; }, { "onClick": "onClick"; }, never, ["*"], true, never>;
|
|
1616
2427
|
}
|
|
1617
2428
|
|
|
1618
2429
|
/**
|
|
@@ -1764,20 +2575,6 @@ interface IconButtonHarnessFilters extends BaseHarnessFilters {
|
|
|
1764
2575
|
size?: string;
|
|
1765
2576
|
}
|
|
1766
2577
|
|
|
1767
|
-
declare enum InputType {
|
|
1768
|
-
Email = "email",
|
|
1769
|
-
Number = "number",
|
|
1770
|
-
Password = "password",
|
|
1771
|
-
PlainText = "text",
|
|
1772
|
-
/**
|
|
1773
|
-
* Data-size field: the form model holds a raw byte count (a number), while the
|
|
1774
|
-
* field displays/accepts a human-readable string (e.g. `2 GiB`, `500M`, `2 TB`).
|
|
1775
|
-
* See `tn-input`'s `sizeStandard` / `sizeDefaultUnit` inputs to tune formatting
|
|
1776
|
-
* and bare-number parsing.
|
|
1777
|
-
*/
|
|
1778
|
-
Size = "size"
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
2578
|
/**
|
|
1782
2579
|
* Unit standard used to format and parse data sizes.
|
|
1783
2580
|
*
|
|
@@ -1813,20 +2610,112 @@ declare function formatSize(bytes: number | string | null | undefined, standard?
|
|
|
1813
2610
|
* @param standard Unit standard (defaults to IEC base-2).
|
|
1814
2611
|
*/
|
|
1815
2612
|
declare function parseSize(raw: string | number | null | undefined, defaultUnit?: string, standard?: SizeStandard): number | null;
|
|
2613
|
+
/**
|
|
2614
|
+
* The pieces {@link parseSize} recognized, for a caller that needs the unit the
|
|
2615
|
+
* text was written in and not just the byte count it denotes.
|
|
2616
|
+
*
|
|
2617
|
+
* @internal
|
|
2618
|
+
*/
|
|
2619
|
+
interface ParsedSize {
|
|
2620
|
+
/** The byte count the text denotes. */
|
|
2621
|
+
bytes: number;
|
|
2622
|
+
/** The number as it was written, without the unit — `'1500'`, `'1.755'`. */
|
|
2623
|
+
digits: string;
|
|
2624
|
+
/** Power of the base the unit carries (B = 0, K = 1, M = 2, ...). */
|
|
2625
|
+
exponent: number;
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* {@link parseSize}, keeping the parts rather than collapsing them to a byte
|
|
2629
|
+
* count. The unit matters when the text has to be re-rendered: the number the
|
|
2630
|
+
* user wrote and the unit they wrote it in always denote their byte count
|
|
2631
|
+
* exactly, which a rounded rendering may not.
|
|
2632
|
+
*
|
|
2633
|
+
* @internal
|
|
2634
|
+
*/
|
|
2635
|
+
declare function parseSizeParts(raw: string | number | null | undefined, defaultUnit?: string, standard?: SizeStandard): ParsedSize | null;
|
|
2636
|
+
/**
|
|
2637
|
+
* Renders a byte count in the largest unit that expresses it EXACTLY within
|
|
2638
|
+
* `round` decimals, or `null` when no suitable unit does.
|
|
2639
|
+
*
|
|
2640
|
+
* The point is a field the user types into. `formatSize` always picks the
|
|
2641
|
+
* natural unit and rounds to fit — 1 572 864 000 becomes `1.46 GiB`, which is a
|
|
2642
|
+
* fine way to READ a size but a lie to hand back to someone who typed
|
|
2643
|
+
* `1500 MiB`: it no longer denotes the value the field holds. One unit down
|
|
2644
|
+
* usually does denote it exactly, so this looks there.
|
|
2645
|
+
*
|
|
2646
|
+
* It looks exactly one step down and no further, which is what keeps the result
|
|
2647
|
+
* readable: a value that is exact in neither its own unit nor the next is
|
|
2648
|
+
* always exact in bytes, and `1567663063 B` helps nobody. Such a value has no
|
|
2649
|
+
* exact spelling worth showing, so this returns `null` and the caller falls
|
|
2650
|
+
* back to the rounded rendering.
|
|
2651
|
+
*
|
|
2652
|
+
* @param bytes The byte count to render. Non-integer and unsafe magnitudes
|
|
2653
|
+
* return `null` — a byte count is a whole number, and past
|
|
2654
|
+
* `MAX_SAFE_INTEGER` "exactly" stops meaning anything.
|
|
2655
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
2656
|
+
* @param round Decimal places the rendering may use (defaults to 2).
|
|
2657
|
+
* @internal
|
|
2658
|
+
*/
|
|
2659
|
+
declare function formatSizeExact(bytes: number, standard?: SizeStandard, round?: number): string | null;
|
|
2660
|
+
/**
|
|
2661
|
+
* Formats a byte count for a field the user edits: {@link formatSizeExact} when
|
|
2662
|
+
* some unit states the value exactly, and {@link formatSize}'s rounded
|
|
2663
|
+
* rendering when none does.
|
|
2664
|
+
*
|
|
2665
|
+
* @param bytes The byte count to format.
|
|
2666
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
2667
|
+
* @param round Decimal places to round to (defaults to 2).
|
|
2668
|
+
* @internal
|
|
2669
|
+
*/
|
|
2670
|
+
declare function formatSizeForEditing(bytes: number | string | null | undefined, standard?: SizeStandard, round?: number): string;
|
|
2671
|
+
/**
|
|
2672
|
+
* Tidies a size the user typed into its canonical spelling, without ever
|
|
2673
|
+
* restating it as a value it does not denote.
|
|
2674
|
+
*
|
|
2675
|
+
* `2048 KiB` becomes `2 MiB` and `200tib` becomes `200 TiB`, because both
|
|
2676
|
+
* renderings are exact. `1500` becomes `1500 MiB` rather than `1.46 GiB`, and
|
|
2677
|
+
* `1.755 GiB` is left at `1.755 GiB`: where no unit expresses the value within
|
|
2678
|
+
* `round` decimals, the number and unit the user wrote do, so those are kept
|
|
2679
|
+
* and only the spelling is normalized.
|
|
2680
|
+
*
|
|
2681
|
+
* Returns `null` for text {@link parseSize} cannot read, which the caller leaves
|
|
2682
|
+
* on screen for its validators to flag.
|
|
2683
|
+
*
|
|
2684
|
+
* @param raw The text to canonicalize.
|
|
2685
|
+
* @param defaultUnit Unit assumed when the input carries no unit (defaults to `MiB`).
|
|
2686
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
2687
|
+
* @param round Decimal places a rendering may use (defaults to 2).
|
|
2688
|
+
* @internal
|
|
2689
|
+
*/
|
|
2690
|
+
declare function canonicalizeSize(raw: string | number | null | undefined, defaultUnit?: string, standard?: SizeStandard, round?: number): string | null;
|
|
1816
2691
|
|
|
1817
|
-
declare
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
/**
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
2692
|
+
declare enum InputType {
|
|
2693
|
+
Email = "email",
|
|
2694
|
+
Number = "number",
|
|
2695
|
+
Password = "password",
|
|
2696
|
+
PlainText = "text",
|
|
2697
|
+
/**
|
|
2698
|
+
* Data-size field: the form model holds a raw byte count (a number), while the
|
|
2699
|
+
* field displays/accepts a human-readable string (e.g. `2 GiB`, `500M`, `2 TB`).
|
|
2700
|
+
* See `tn-input`'s `sizeStandard` / `sizeDefaultUnit` inputs to tune formatting
|
|
2701
|
+
* and bare-number parsing.
|
|
2702
|
+
*/
|
|
2703
|
+
Size = "size"
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
declare class TnInputComponent implements AfterViewInit, OnDestroy, ControlValueAccessor {
|
|
2707
|
+
inputEl: _angular_core.Signal<ElementRef<HTMLInputElement | HTMLTextAreaElement>>;
|
|
2708
|
+
inputType: _angular_core.InputSignal<InputType>;
|
|
2709
|
+
placeholder: _angular_core.InputSignal<string>;
|
|
2710
|
+
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
2711
|
+
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
2712
|
+
protected resolvedTestId: _angular_core.Signal<TnTestIdValue>;
|
|
2713
|
+
disabled: _angular_core.InputSignal<boolean>;
|
|
2714
|
+
multiline: _angular_core.InputSignal<boolean>;
|
|
2715
|
+
rows: _angular_core.InputSignal<number>;
|
|
2716
|
+
/**
|
|
2717
|
+
* Explicit accessible name for the control. Rendered as `aria-label` on the
|
|
2718
|
+
* input/textarea. Inside a `tn-form-field` the field's label is associated
|
|
1830
2719
|
* automatically (via `aria-labelledby`), so leave this unset there unless the
|
|
1831
2720
|
* announced name must differ from the visible label — when set, it wins. Set
|
|
1832
2721
|
* it for standalone usage so the control isn't unnamed in the a11y tree.
|
|
@@ -1894,6 +2783,12 @@ declare class TnInputComponent implements AfterViewInit, OnDestroy, ControlValue
|
|
|
1894
2783
|
/**
|
|
1895
2784
|
* Decimal places used when formatting a `Size` field's value for display
|
|
1896
2785
|
* (e.g. `1.5 GiB`). Ignored unless `inputType` is `Size`.
|
|
2786
|
+
*
|
|
2787
|
+
* Display only: the form model always holds the exact byte count the typed
|
|
2788
|
+
* text denotes, and is never re-read from the display. The display avoids
|
|
2789
|
+
* rounding where it can — `1500M` shows as `1500 MiB`, not as a `1.46 GiB`
|
|
2790
|
+
* that means 1 567 663 063 — and where a value fits no unit exactly (a size
|
|
2791
|
+
* read back from the server, say) it is rounded for reading only.
|
|
1897
2792
|
*/
|
|
1898
2793
|
sizeRound: _angular_core.InputSignal<number>;
|
|
1899
2794
|
/**
|
|
@@ -2598,6 +3493,16 @@ declare class TnChipComponent implements AfterViewInit, OnDestroy {
|
|
|
2598
3493
|
classes: _angular_core.Signal<string[]>;
|
|
2599
3494
|
handleClick(event: MouseEvent): void;
|
|
2600
3495
|
handleClose(event: MouseEvent): void;
|
|
3496
|
+
/**
|
|
3497
|
+
* Handles the chip's Delete/Backspace dismiss shortcut. Bound to both the
|
|
3498
|
+
* body and the close button so the shortcut works wherever focus sits inside
|
|
3499
|
+
* the chip; the wrapper between them carries no role and is not focusable,
|
|
3500
|
+
* so it is not a legitimate place to hang a key handler.
|
|
3501
|
+
*
|
|
3502
|
+
* Enter and Space are deliberately absent: the body is a native `<button>`,
|
|
3503
|
+
* which already turns both into a `click`. Handling them here as well would
|
|
3504
|
+
* emit `onClick` twice per keypress.
|
|
3505
|
+
*/
|
|
2601
3506
|
handleKeyDown(event: KeyboardEvent): void;
|
|
2602
3507
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnChipComponent, never>;
|
|
2603
3508
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnChipComponent, "tn-chip", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "closable": { "alias": "closable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "onClose": "onClose"; "onClick": "onClick"; }, never, never, true, never>;
|
|
@@ -2627,6 +3532,7 @@ declare class TnChipHarness extends ComponentHarness {
|
|
|
2627
3532
|
*/
|
|
2628
3533
|
static hostSelector: string;
|
|
2629
3534
|
private _chip;
|
|
3535
|
+
private _body;
|
|
2630
3536
|
private _label;
|
|
2631
3537
|
private _icon;
|
|
2632
3538
|
private _closeButton;
|
|
@@ -2863,6 +3769,20 @@ declare class TnChipInputComponent<T = string> implements ControlValueAccessor,
|
|
|
2863
3769
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
2864
3770
|
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
2865
3771
|
protected resolvedTestId: _angular_core.Signal<TnTestIdValue>;
|
|
3772
|
+
/**
|
|
3773
|
+
* Optional extractor for the per-option test-id discriminator, applied to both
|
|
3774
|
+
* a suggestion row and the chip it becomes. Defaults to the option's `label`,
|
|
3775
|
+
* the text actually on screen — provide this to key off a locale-independent
|
|
3776
|
+
* field instead, or where two options share a display name and the derived ids
|
|
3777
|
+
* would otherwise collide. Free-text chips have no option to extract from and
|
|
3778
|
+
* stay named by their own value.
|
|
3779
|
+
*
|
|
3780
|
+
* @example
|
|
3781
|
+
* ```html
|
|
3782
|
+
* <tn-chip-input testId="users" [optionTestIdKey]="(o) => o.value.id" ... />
|
|
3783
|
+
* ```
|
|
3784
|
+
*/
|
|
3785
|
+
optionTestIdKey: _angular_core.InputSignal<((option: TnChipInputOption<T>) => string | number | null | undefined) | undefined>;
|
|
2866
3786
|
/** Emits the committed value whenever a chip is added. */
|
|
2867
3787
|
chipAdded: _angular_core.OutputEmitterRef<T>;
|
|
2868
3788
|
/** Emits the removed value whenever a chip is removed. */
|
|
@@ -2924,19 +3844,27 @@ declare class TnChipInputComponent<T = string> implements ControlValueAccessor,
|
|
|
2924
3844
|
/**
|
|
2925
3845
|
* Scopes a per-chip test id beneath the component's base.
|
|
2926
3846
|
*
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
2929
|
-
*
|
|
2930
|
-
*
|
|
2931
|
-
*
|
|
2932
|
-
*
|
|
2933
|
-
*
|
|
3847
|
+
* A chip backed by an option goes through the same {@link optionTestId}
|
|
3848
|
+
* derivation as the suggestion row that created it, so the two carry the same
|
|
3849
|
+
* discriminator by construction rather than one naming the label and the other
|
|
3850
|
+
* the value — including whatever fallback that shared rule settles on, and any
|
|
3851
|
+
* `optionTestIdKey` override.
|
|
3852
|
+
*
|
|
3853
|
+
* A value with no matching option is named by itself when it is a primitive:
|
|
3854
|
+
* either a free-text chip, which is its own text, or an option-backed value
|
|
3855
|
+
* whose options have not arrived yet — an async `[options]` load moves such a
|
|
3856
|
+
* chip's id from the value to the resolved label once it does. An object value
|
|
3857
|
+
* with no match cannot stand in for itself, because `String(value)` on an
|
|
3858
|
+
* object is `[object Object]` and would stamp an identical id on every chip.
|
|
3859
|
+
* Duplicates are worse than absence for automation, so that chip stays
|
|
3860
|
+
* attribute-free rather than colliding. A primitive that normalizes away is
|
|
3861
|
+
* dropped for the same reason — see {@link discriminatedTestId}.
|
|
2934
3862
|
*/
|
|
2935
3863
|
protected chipTestId(value: T): TnTestIdValue;
|
|
2936
3864
|
/**
|
|
2937
3865
|
* Scopes a per-suggestion test id beneath the component's base, via the shared
|
|
2938
3866
|
* dropdown-option derivation. Unlike `tn-select` / `tn-autocomplete`, which
|
|
2939
|
-
* emit an unscoped `option-<
|
|
3867
|
+
* emit an unscoped `option-<label>` when they have no base, an unidentified
|
|
2940
3868
|
* chip-input stays attribute-free — its rows carry no page-unique id, so
|
|
2941
3869
|
* emitting one would invite collisions between inputs.
|
|
2942
3870
|
*/
|
|
@@ -2958,6 +3886,24 @@ declare class TnChipInputComponent<T = string> implements ControlValueAccessor,
|
|
|
2958
3886
|
private commitText;
|
|
2959
3887
|
/** Commits a resolved value, honouring duplicate and cap rules. */
|
|
2960
3888
|
private commitValue;
|
|
3889
|
+
/**
|
|
3890
|
+
* The option a committed value came from, or `undefined` for a free-text chip.
|
|
3891
|
+
*
|
|
3892
|
+
* Both the chip's label and its test id need this lookup, and both are called
|
|
3893
|
+
* from the template — once per chip per change-detection cycle — so a linear
|
|
3894
|
+
* scan of the options would be quadratic in (chips × options) on every cycle.
|
|
3895
|
+
* With the default identity comparator, {@link optionIndex} answers in constant
|
|
3896
|
+
* time; a custom `compareWith` can't be indexed (only it knows what equality
|
|
3897
|
+
* means for the value), so that path keeps the scan.
|
|
3898
|
+
*/
|
|
3899
|
+
private optionFor;
|
|
3900
|
+
/**
|
|
3901
|
+
* Value → option, rebuilt only when the option list changes. First entry wins,
|
|
3902
|
+
* matching the `find` it replaces where a value is repeated across options.
|
|
3903
|
+
* Keys compare by `Map` identity, which agrees with the `===` this stands in
|
|
3904
|
+
* for on every value a form control can hold.
|
|
3905
|
+
*/
|
|
3906
|
+
private optionIndex;
|
|
2961
3907
|
private valuesIncludes;
|
|
2962
3908
|
private valueMatches;
|
|
2963
3909
|
private clearInput;
|
|
@@ -2974,7 +3920,7 @@ declare class TnChipInputComponent<T = string> implements ControlValueAccessor,
|
|
|
2974
3920
|
private attachOverlay;
|
|
2975
3921
|
private detachOverlay;
|
|
2976
3922
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnChipInputComponent<any>, never>;
|
|
2977
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnChipInputComponent<any>, "tn-chip-input", never, { "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "separatorKeys": { "alias": "separatorKeys"; "required": false; "isSignal": true; }; "addOnBlur": { "alias": "addOnBlur"; "required": false; "isSignal": true; }; "allowCustomValue": { "alias": "allowCustomValue"; "required": false; "isSignal": true; }; "allowDuplicates": { "alias": "allowDuplicates"; "required": false; "isSignal": true; }; "maxChips": { "alias": "maxChips"; "required": false; "isSignal": true; }; "suggestions": { "alias": "suggestions"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "chipAdded": "chipAdded"; "chipRemoved": "chipRemoved"; "searchChange": "searchChange"; }, never, never, true, never>;
|
|
3923
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnChipInputComponent<any>, "tn-chip-input", never, { "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "separatorKeys": { "alias": "separatorKeys"; "required": false; "isSignal": true; }; "addOnBlur": { "alias": "addOnBlur"; "required": false; "isSignal": true; }; "allowCustomValue": { "alias": "allowCustomValue"; "required": false; "isSignal": true; }; "allowDuplicates": { "alias": "allowDuplicates"; "required": false; "isSignal": true; }; "maxChips": { "alias": "maxChips"; "required": false; "isSignal": true; }; "suggestions": { "alias": "suggestions"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "optionTestIdKey": { "alias": "optionTestIdKey"; "required": false; "isSignal": true; }; }, { "chipAdded": "chipAdded"; "chipRemoved": "chipRemoved"; "searchChange": "searchChange"; }, never, never, true, never>;
|
|
2978
3924
|
}
|
|
2979
3925
|
|
|
2980
3926
|
/**
|
|
@@ -3266,6 +4212,28 @@ declare class TnCardComponent {
|
|
|
3266
4212
|
*/
|
|
3267
4213
|
titleTooltipAriaLabel: _angular_core.InputSignal<string | undefined>;
|
|
3268
4214
|
protected readonly resolvedTitleTooltipAriaLabel: _angular_core.Signal<string>;
|
|
4215
|
+
/**
|
|
4216
|
+
* Whether a tooltip message holding a link may be pinned open by clicking its host (see
|
|
4217
|
+
* `tnTooltipSticky`). On by default, like the directive, and it reaches the title help button
|
|
4218
|
+
* and the footer actions.
|
|
4219
|
+
*
|
|
4220
|
+
* The help button has no other use for the click, so there pinning is all the click does. A
|
|
4221
|
+
* footer action does have one: pinning is additive there, exactly as `tnTooltipSticky` describes
|
|
4222
|
+
* it — one click on a `TnCardAction` whose `tooltip` holds a link both runs `handler()` and pins
|
|
4223
|
+
* the panel. That is fine for a handler that leaves the card in place, and worth a second look
|
|
4224
|
+
* for one that navigates away or closes it, since the panel it pinned goes with the card. Set
|
|
4225
|
+
* this to false for such a card.
|
|
4226
|
+
*
|
|
4227
|
+
* The kebab-menu trigger is deliberately out of scope. Its click already opens the menu, so a
|
|
4228
|
+
* pinnable tooltip there would put a panel over the menu from that same click and take the hint
|
|
4229
|
+
* off hover to do it; `headerMenuTriggerTooltip` therefore always hovers, and this flag does not
|
|
4230
|
+
* reach it.
|
|
4231
|
+
*
|
|
4232
|
+
* It does not make plain tooltips pinnable — card help and action hints are nearly always plain
|
|
4233
|
+
* text, and those keep hovering. Set it to false only to force a message that does hold a link
|
|
4234
|
+
* back to hover behaviour, accepting that the link is then unreachable.
|
|
4235
|
+
*/
|
|
4236
|
+
tooltipSticky: _angular_core.InputSignal<boolean>;
|
|
3269
4237
|
elevation: _angular_core.InputSignal<"none" | "low" | "medium" | "high">;
|
|
3270
4238
|
padding: _angular_core.InputSignal<"small" | "large" | "medium">;
|
|
3271
4239
|
padContent: _angular_core.InputSignal<boolean>;
|
|
@@ -3301,6 +4269,10 @@ declare class TnCardComponent {
|
|
|
3301
4269
|
/**
|
|
3302
4270
|
* Hover tooltip for the kebab-menu trigger. Defaults to `headerMenuTriggerAriaLabel`, so setting
|
|
3303
4271
|
* a single translated string covers both the accessible name and the visible hint.
|
|
4272
|
+
*
|
|
4273
|
+
* Hover is all it ever is: unlike the card's other tooltips this one is never pinnable, because
|
|
4274
|
+
* the trigger's click belongs to the menu — see `tooltipSticky`. A message holding a link works
|
|
4275
|
+
* here, but the link stays out of reach.
|
|
3304
4276
|
*/
|
|
3305
4277
|
headerMenuTriggerTooltip: _angular_core.InputSignal<string | undefined>;
|
|
3306
4278
|
protected readonly resolvedHeaderMenuAriaLabel: _angular_core.Signal<string>;
|
|
@@ -3329,7 +4301,7 @@ declare class TnCardComponent {
|
|
|
3329
4301
|
onHeaderMenuItemClick(_item: TnMenuItem): void;
|
|
3330
4302
|
getStatusClass(type?: string): string;
|
|
3331
4303
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnCardComponent, never>;
|
|
3332
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnCardComponent, "tn-card", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "titleLink": { "alias": "titleLink"; "required": false; "isSignal": true; }; "titleRouterLink": { "alias": "titleRouterLink"; "required": false; "isSignal": true; }; "titleQueryParams": { "alias": "titleQueryParams"; "required": false; "isSignal": true; }; "titleTooltip": { "alias": "titleTooltip"; "required": false; "isSignal": true; }; "titleTooltipAriaLabel": { "alias": "titleTooltipAriaLabel"; "required": false; "isSignal": true; }; "elevation": { "alias": "elevation"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "padContent": { "alias": "padContent"; "required": false; "isSignal": true; }; "fillHeight": { "alias": "fillHeight"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "background": { "alias": "background"; "required": false; "isSignal": true; }; "headerStatus": { "alias": "headerStatus"; "required": false; "isSignal": true; }; "headerControl": { "alias": "headerControl"; "required": false; "isSignal": true; }; "headerMenu": { "alias": "headerMenu"; "required": false; "isSignal": true; }; "headerMenuTriggerTestId": { "alias": "headerMenuTriggerTestId"; "required": false; "isSignal": true; }; "headerMenuTriggerAriaLabel": { "alias": "headerMenuTriggerAriaLabel"; "required": false; "isSignal": true; }; "headerMenuTriggerTooltip": { "alias": "headerMenuTriggerTooltip"; "required": false; "isSignal": true; }; "primaryAction": { "alias": "primaryAction"; "required": false; "isSignal": true; }; "secondaryAction": { "alias": "secondaryAction"; "required": false; "isSignal": true; }; "footerLink": { "alias": "footerLink"; "required": false; "isSignal": true; }; }, {}, ["projectedHeader", "headerActions", "footerActions"], ["[tnCardHeader]", "*"], true, never>;
|
|
4304
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnCardComponent, "tn-card", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "titleLink": { "alias": "titleLink"; "required": false; "isSignal": true; }; "titleRouterLink": { "alias": "titleRouterLink"; "required": false; "isSignal": true; }; "titleQueryParams": { "alias": "titleQueryParams"; "required": false; "isSignal": true; }; "titleTooltip": { "alias": "titleTooltip"; "required": false; "isSignal": true; }; "titleTooltipAriaLabel": { "alias": "titleTooltipAriaLabel"; "required": false; "isSignal": true; }; "tooltipSticky": { "alias": "tooltipSticky"; "required": false; "isSignal": true; }; "elevation": { "alias": "elevation"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "padContent": { "alias": "padContent"; "required": false; "isSignal": true; }; "fillHeight": { "alias": "fillHeight"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "background": { "alias": "background"; "required": false; "isSignal": true; }; "headerStatus": { "alias": "headerStatus"; "required": false; "isSignal": true; }; "headerControl": { "alias": "headerControl"; "required": false; "isSignal": true; }; "headerMenu": { "alias": "headerMenu"; "required": false; "isSignal": true; }; "headerMenuTriggerTestId": { "alias": "headerMenuTriggerTestId"; "required": false; "isSignal": true; }; "headerMenuTriggerAriaLabel": { "alias": "headerMenuTriggerAriaLabel"; "required": false; "isSignal": true; }; "headerMenuTriggerTooltip": { "alias": "headerMenuTriggerTooltip"; "required": false; "isSignal": true; }; "primaryAction": { "alias": "primaryAction"; "required": false; "isSignal": true; }; "secondaryAction": { "alias": "secondaryAction"; "required": false; "isSignal": true; }; "footerLink": { "alias": "footerLink"; "required": false; "isSignal": true; }; }, {}, ["projectedHeader", "headerActions", "footerActions"], ["[tnCardHeader]", "*"], true, never>;
|
|
3333
4305
|
}
|
|
3334
4306
|
|
|
3335
4307
|
/**
|
|
@@ -4265,7 +5237,6 @@ declare class TnSlideToggleComponent implements AfterViewInit, OnDestroy, Contro
|
|
|
4265
5237
|
registerOnTouched(fn: () => void): void;
|
|
4266
5238
|
setDisabledState(isDisabled: boolean): void;
|
|
4267
5239
|
onToggleChange(event: Event): void;
|
|
4268
|
-
onLabelClick(): void;
|
|
4269
5240
|
classes: _angular_core.Signal<string[]>;
|
|
4270
5241
|
effectiveAriaLabel: _angular_core.Signal<string | undefined>;
|
|
4271
5242
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnSlideToggleComponent, never>;
|
|
@@ -4361,6 +5332,20 @@ declare class TnTabComponent implements AfterContentInit {
|
|
|
4361
5332
|
selected: _angular_core.OutputEmitterRef<void>;
|
|
4362
5333
|
iconContent: _angular_core.Signal<TemplateRef<unknown> | undefined>;
|
|
4363
5334
|
index: _angular_core.WritableSignal<number>;
|
|
5335
|
+
/**
|
|
5336
|
+
* Id namespace, set by the parent `tn-tabs` so that both ends of the tab↔panel wiring
|
|
5337
|
+
* agree. The default is unique per instance rather than a constant, so a `tn-tab`
|
|
5338
|
+
* rendered outside a `tn-tabs` still renders an `id` that collides with nothing.
|
|
5339
|
+
*/
|
|
5340
|
+
groupId: _angular_core.WritableSignal<string>;
|
|
5341
|
+
/**
|
|
5342
|
+
* Whether the parent has a panel at this tab's index, which is what decides whether
|
|
5343
|
+
* `aria-controls` is rendered at all. `tn-tabs` walks its tabs and its panels
|
|
5344
|
+
* independently, so a group given more tabs than panels — or a `tn-tab` used outside a
|
|
5345
|
+
* `tn-tabs`, which is why this starts false — would otherwise point `aria-controls` at
|
|
5346
|
+
* an id no element in the document carries.
|
|
5347
|
+
*/
|
|
5348
|
+
hasPanel: _angular_core.WritableSignal<boolean>;
|
|
4364
5349
|
isActive: _angular_core.WritableSignal<boolean>;
|
|
4365
5350
|
tabsComponent?: {
|
|
4366
5351
|
onKeydown: (event: KeyboardEvent, index: number) => void;
|
|
@@ -4373,20 +5358,92 @@ declare class TnTabComponent implements AfterContentInit {
|
|
|
4373
5358
|
onKeydown(event: KeyboardEvent): void;
|
|
4374
5359
|
classes: _angular_core.Signal<string>;
|
|
4375
5360
|
tabIndex: _angular_core.Signal<-1 | 0>;
|
|
5361
|
+
/** This tab's own id, which its panel points back at with `aria-labelledby`. */
|
|
5362
|
+
tabId: _angular_core.Signal<string>;
|
|
5363
|
+
/** The id of the panel this tab controls, for `aria-controls`. */
|
|
5364
|
+
panelId: _angular_core.Signal<string>;
|
|
4376
5365
|
hasIcon: _angular_core.Signal<boolean>;
|
|
4377
5366
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTabComponent, never>;
|
|
4378
5367
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTabComponent, "tn-tab", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconTemplate": { "alias": "iconTemplate"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "selected": "selected"; }, ["iconContent"], ["*"], true, never>;
|
|
4379
5368
|
}
|
|
4380
5369
|
|
|
5370
|
+
/**
|
|
5371
|
+
* The name the scrolling content region takes when it becomes focusable and the
|
|
5372
|
+
* panel has no `label` to name it from (#270).
|
|
5373
|
+
*
|
|
5374
|
+
* A focusable element with no accessible name is announced as a bare "group".
|
|
5375
|
+
* A panel rendered outside a `tn-tabs`, or one whose caller left `label` empty,
|
|
5376
|
+
* has no better name available — so this is the last resort rather than the
|
|
5377
|
+
* usual case, and `contentLabel` prefers the label every time there is one.
|
|
5378
|
+
*
|
|
5379
|
+
* Exported so specs assert against it by name rather than by a copied literal.
|
|
5380
|
+
*/
|
|
5381
|
+
declare const TN_TAB_PANEL_CONTENT_LABEL = "Tab panel content";
|
|
4381
5382
|
declare class TnTabPanelComponent {
|
|
4382
5383
|
label: _angular_core.InputSignal<string>;
|
|
4383
5384
|
lazyLoad: _angular_core.InputSignal<boolean>;
|
|
4384
5385
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
4385
5386
|
content: _angular_core.Signal<TemplateRef<unknown>>;
|
|
5387
|
+
/**
|
|
5388
|
+
* The scrolling content region, which is `.tn-tab-panel__content` — the
|
|
5389
|
+
* element the stylesheet gives `overflow: auto`, not the `role="tabpanel"`
|
|
5390
|
+
* wrapper around it. Optional because it lives inside `@if (shouldRender())`,
|
|
5391
|
+
* so a lazy panel that has never been active does not render it.
|
|
5392
|
+
*
|
|
5393
|
+
* The template ref is `#contentRegion` and not `#content`, which is taken:
|
|
5394
|
+
* the `content` query above asks for a `TemplateRef` under that name, and
|
|
5395
|
+
* naming a real element `#content` would hand it an `ElementRef` instead.
|
|
5396
|
+
*/
|
|
5397
|
+
private contentRef;
|
|
5398
|
+
/**
|
|
5399
|
+
* Whether the content region carries the tab stop, its role and its name —
|
|
5400
|
+
* and, conversely, whether the `role="tabpanel"` wrapper gives its own up.
|
|
5401
|
+
*
|
|
5402
|
+
* `.tn-tab-panel__content` is what scrolls, so it is what a keyboard user
|
|
5403
|
+
* has to be able to stand on to read past the fold (#270). The measurement,
|
|
5404
|
+
* the observers behind it and the rule that holds the answer true while the
|
|
5405
|
+
* region has focus are `tnScrollableRegion`'s; the template says why the two
|
|
5406
|
+
* elements trade one tab stop rather than carrying two.
|
|
5407
|
+
*/
|
|
5408
|
+
protected contentKeyboardReachable: _angular_core.Signal<boolean>;
|
|
4386
5409
|
index: _angular_core.WritableSignal<number>;
|
|
5410
|
+
/**
|
|
5411
|
+
* Id namespace, set by the parent `tn-tabs` so that both ends of the tab↔panel wiring
|
|
5412
|
+
* agree. Unique per instance by default, for the same reason as on `tn-tab`: a panel
|
|
5413
|
+
* rendered outside a `tn-tabs` still renders an `id`, and one colliding with another
|
|
5414
|
+
* group's panel would be worse than one nothing points at.
|
|
5415
|
+
*/
|
|
5416
|
+
groupId: _angular_core.WritableSignal<string>;
|
|
5417
|
+
/**
|
|
5418
|
+
* Whether the parent has a tab at this panel's index, which is what decides whether
|
|
5419
|
+
* `aria-labelledby` is rendered. The mirror of `hasPanel` on `tn-tab`, and for the same
|
|
5420
|
+
* reason: more panels than tabs, or a panel outside a `tn-tabs`, would otherwise leave
|
|
5421
|
+
* this pointing at an id nothing carries — which is what `aria-labelledby="tab-0"` did
|
|
5422
|
+
* unconditionally before #232.
|
|
5423
|
+
*/
|
|
5424
|
+
hasTab: _angular_core.WritableSignal<boolean>;
|
|
4387
5425
|
isActive: _angular_core.WritableSignal<boolean>;
|
|
4388
5426
|
hasBeenActive: _angular_core.WritableSignal<boolean>;
|
|
4389
5427
|
elementRef: ElementRef<any>;
|
|
5428
|
+
/** This panel's own id, which its tab points at with `aria-controls`. */
|
|
5429
|
+
panelId: _angular_core.Signal<string>;
|
|
5430
|
+
/** The id of the tab that labels this panel, for `aria-labelledby`. */
|
|
5431
|
+
tabId: _angular_core.Signal<string>;
|
|
5432
|
+
/**
|
|
5433
|
+
* What the scrolling content region is named while it carries the tab stop.
|
|
5434
|
+
*
|
|
5435
|
+
* The panel's own `label` where there is one, because that is what its tab
|
|
5436
|
+
* says and a listener arriving from the tab hears the same words for the
|
|
5437
|
+
* region it opened. `aria-labelledby` pointing at the tab would be the other
|
|
5438
|
+
* way to say it and is not used here: the tab lives in a sibling component,
|
|
5439
|
+
* so the IDREF resolves only inside a `tn-tabs`, and a panel rendered on its
|
|
5440
|
+
* own would be left unnamed by exactly the route that was supposed to name
|
|
5441
|
+
* it — the `--tn-error-text` hazard in another shape.
|
|
5442
|
+
*
|
|
5443
|
+
* Trimmed, because a whitespace-only label names the region with nothing,
|
|
5444
|
+
* which is the state `TN_TAB_PANEL_CONTENT_LABEL` exists to prevent.
|
|
5445
|
+
*/
|
|
5446
|
+
protected contentLabel: _angular_core.Signal<string>;
|
|
4390
5447
|
classes: _angular_core.Signal<string>;
|
|
4391
5448
|
shouldRender: _angular_core.Signal<boolean>;
|
|
4392
5449
|
onActivate(): void;
|
|
@@ -4407,10 +5464,17 @@ declare class TnTabsComponent implements AfterContentInit, AfterViewInit, OnDest
|
|
|
4407
5464
|
orientation: _angular_core.InputSignal<"horizontal" | "vertical">;
|
|
4408
5465
|
highlightPosition: _angular_core.InputSignal<"bottom" | "top" | "left" | "right">;
|
|
4409
5466
|
/**
|
|
4410
|
-
* Test-id applied to the
|
|
4411
|
-
* is
|
|
5467
|
+
* Test-id applied to the root element — the wrapper around the tablist and the panels,
|
|
5468
|
+
* not the tablist itself, which is the header inside it. Rendered under whichever
|
|
5469
|
+
* attribute name is configured via `TN_TEST_ATTR` (default `data-testid`).
|
|
4412
5470
|
*/
|
|
4413
5471
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
5472
|
+
/**
|
|
5473
|
+
* Namespace for the ids this group hands to its tabs and panels, so that two `tn-tabs`
|
|
5474
|
+
* on one page cannot both mint `tab-0` and cross-wire each other's `aria-controls` and
|
|
5475
|
+
* `aria-labelledby`. See `tab-ids.ts`.
|
|
5476
|
+
*/
|
|
5477
|
+
private readonly groupId;
|
|
4414
5478
|
selectedIndexChange: _angular_core.OutputEmitterRef<number>;
|
|
4415
5479
|
tabChange: _angular_core.OutputEmitterRef<TabChangeEvent>;
|
|
4416
5480
|
private internalSelectedIndex;
|
|
@@ -5045,6 +6109,18 @@ declare class TnKeyboardShortcutComponent {
|
|
|
5045
6109
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnKeyboardShortcutComponent, "tn-keyboard-shortcut", never, { "shortcut": { "alias": "shortcut"; "required": false; "isSignal": true; }; "platform": { "alias": "platform"; "required": false; "isSignal": true; }; "separator": { "alias": "separator"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
5046
6110
|
}
|
|
5047
6111
|
|
|
6112
|
+
/** What a host binds its template and its dismiss handler to. @internal */
|
|
6113
|
+
interface DismissibleErrorState {
|
|
6114
|
+
/** The host's own list when it has one, otherwise the app-wide default. */
|
|
6115
|
+
resolvedDismissibleErrors: Signal<readonly string[]>;
|
|
6116
|
+
/** Whether the shown message renders a close button beside it. */
|
|
6117
|
+
showDismiss: Signal<boolean>;
|
|
6118
|
+
/** Accessible name for that button. */
|
|
6119
|
+
resolvedDismissAriaLabel: Signal<string>;
|
|
6120
|
+
/** Hover hint for that button. */
|
|
6121
|
+
resolvedDismissTooltip: Signal<string>;
|
|
6122
|
+
}
|
|
6123
|
+
|
|
5048
6124
|
/**
|
|
5049
6125
|
* Contract between `tn-form-field` and its projected form control, published
|
|
5050
6126
|
* over DI so the control can wire its ARIA attributes to the field's chrome
|
|
@@ -5143,6 +6219,21 @@ declare class TnFormFieldComponent implements AfterContentInit, TnFormFieldConte
|
|
|
5143
6219
|
tooltip: _angular_core.InputSignal<string>;
|
|
5144
6220
|
/** Placement of the tooltip relative to its help icon. */
|
|
5145
6221
|
tooltipPosition: _angular_core.InputSignal<TooltipPosition>;
|
|
6222
|
+
/**
|
|
6223
|
+
* Whether a tooltip message holding a link may be pinned open by clicking the help button (see
|
|
6224
|
+
* `tnTooltipSticky`). On by default, like the directive.
|
|
6225
|
+
*
|
|
6226
|
+
* It does not make plain tooltips pinnable — field help is nearly always plain text, and that
|
|
6227
|
+
* keeps hovering. Set it to false only to force a message that does hold a link back to hover
|
|
6228
|
+
* behaviour, accepting that the link is then unreachable.
|
|
6229
|
+
*/
|
|
6230
|
+
tooltipSticky: _angular_core.InputSignal<boolean>;
|
|
6231
|
+
/**
|
|
6232
|
+
* Accessible name for the help button, which is icon-only and so has nothing else to be named
|
|
6233
|
+
* by. The message is what the button is for, but it may hold markup — a link is the whole point
|
|
6234
|
+
* of `tooltipSticky` — and `aria-label` takes plain text, so the tags come off first.
|
|
6235
|
+
*/
|
|
6236
|
+
protected readonly tooltipAriaLabel: _angular_core.Signal<string>;
|
|
5146
6237
|
/**
|
|
5147
6238
|
* Per-field overrides for validation messages, keyed by error key. Values may
|
|
5148
6239
|
* be a string or a function that receives the error's detail value. Takes
|
|
@@ -5150,7 +6241,56 @@ declare class TnFormFieldComponent implements AfterContentInit, TnFormFieldConte
|
|
|
5150
6241
|
* built-in defaults.
|
|
5151
6242
|
*/
|
|
5152
6243
|
errorMessages: _angular_core.InputSignal<Partial<Record<string, _truenas_ui_components.TnFormFieldErrorMessage>>>;
|
|
6244
|
+
/**
|
|
6245
|
+
* Error keys whose message renders with a dismiss button beside it — in
|
|
6246
|
+
* practice a failure the user cannot fix by editing the value, so the message
|
|
6247
|
+
* would otherwise stick until the control changes: a server-side rejection an
|
|
6248
|
+
* error handler attached to the control, or an async validator that judged the
|
|
6249
|
+
* value the user already picked.
|
|
6250
|
+
*
|
|
6251
|
+
* Only the error actually being shown gets the button. A control carrying both
|
|
6252
|
+
* a dismissible key and `required` shows the `required` message, undismissable,
|
|
6253
|
+
* because that is the message on screen.
|
|
6254
|
+
*
|
|
6255
|
+
* Dismissing deletes these keys from the control's errors — listing them here
|
|
6256
|
+
* is what grants that, since a message the user can close but that does not go
|
|
6257
|
+
* away would be worse than no button. Every listed key the control carries goes
|
|
6258
|
+
* at once, not just the one behind the message: an app that spreads one failure
|
|
6259
|
+
* across sibling keys (a flag, its message, a legacy alias) would otherwise see
|
|
6260
|
+
* the message reappear from a sibling. Unlisted errors are left alone, and
|
|
6261
|
+
* {@link dismiss} reports which message went.
|
|
6262
|
+
*
|
|
6263
|
+
* Left unset, the app-wide {@link TN_FORM_FIELD_DISMISSIBLE_ERRORS} default
|
|
6264
|
+
* applies; pass `[]` to opt this field out of it.
|
|
6265
|
+
*/
|
|
6266
|
+
dismissibleErrors: _angular_core.InputSignal<readonly string[] | undefined>;
|
|
6267
|
+
/**
|
|
6268
|
+
* Accessible name for the dismiss button, which is icon-only and so has
|
|
6269
|
+
* nothing else to be named by. The library cannot translate its own
|
|
6270
|
+
* `'Dismiss this error'` default, so a consumer with an i18n layer passes an
|
|
6271
|
+
* already-translated string here.
|
|
6272
|
+
*/
|
|
6273
|
+
dismissAriaLabel: _angular_core.InputSignal<string | undefined>;
|
|
6274
|
+
/**
|
|
6275
|
+
* Hover tooltip for the dismiss button. Defaults to the resolved
|
|
6276
|
+
* `dismissAriaLabel`, so one translated string covers both the accessible name
|
|
6277
|
+
* and the visible hint.
|
|
6278
|
+
*/
|
|
6279
|
+
dismissTooltip: _angular_core.InputSignal<string | undefined>;
|
|
6280
|
+
/**
|
|
6281
|
+
* Emits the error key the user dismissed, after it has been removed — the key
|
|
6282
|
+
* of the message that was on screen, so a consumer listing several dismissible
|
|
6283
|
+
* keys knows which one went.
|
|
6284
|
+
*/
|
|
6285
|
+
dismiss: _angular_core.OutputEmitterRef<string>;
|
|
5153
6286
|
control: _angular_core.Signal<NgControl | undefined>;
|
|
6287
|
+
private host;
|
|
6288
|
+
/**
|
|
6289
|
+
* `read: ElementRef` because the ref sits on `tn-icon-button`, and a component
|
|
6290
|
+
* ref resolves to the instance by default — the element is what the focus
|
|
6291
|
+
* check needs.
|
|
6292
|
+
*/
|
|
6293
|
+
private dismissButton;
|
|
5154
6294
|
private destroyRef;
|
|
5155
6295
|
/**
|
|
5156
6296
|
* App-wide message resolver, captured once at construction. Unlike the
|
|
@@ -5187,6 +6327,28 @@ declare class TnFormFieldComponent implements AfterContentInit, TnFormFieldConte
|
|
|
5187
6327
|
protected showInlineExtras: _angular_core.Signal<boolean>;
|
|
5188
6328
|
protected hasError: _angular_core.Signal<boolean>;
|
|
5189
6329
|
protected errorMessage: _angular_core.Signal<string>;
|
|
6330
|
+
/**
|
|
6331
|
+
* The error key the shown message came from. Same pick `resolveErrorMessage`
|
|
6332
|
+
* makes, so the dismiss button can never belong to an error other than the one
|
|
6333
|
+
* being read.
|
|
6334
|
+
*/
|
|
6335
|
+
protected activeError: _angular_core.Signal<string | null>;
|
|
6336
|
+
/**
|
|
6337
|
+
* Which list applies, whether the button shows, and what it is called — shared
|
|
6338
|
+
* with `tn-form-errors` so a field message and a group message beside it can
|
|
6339
|
+
* never decide dismissibility by different rules.
|
|
6340
|
+
*/
|
|
6341
|
+
protected dismissible: DismissibleErrorState;
|
|
6342
|
+
/**
|
|
6343
|
+
* Drops the dismissed error, then puts focus back on the control rather than
|
|
6344
|
+
* letting it fall to `<body>` with the button — dismissing a server-side error
|
|
6345
|
+
* means "let me try again", and the control is where trying again happens.
|
|
6346
|
+
*
|
|
6347
|
+
* Focus only moves if it was on the button to begin with: a dismiss triggered
|
|
6348
|
+
* from anywhere else (a Safari mouse click, which leaves the button unfocused)
|
|
6349
|
+
* has no focus to lose and should not steal any.
|
|
6350
|
+
*/
|
|
6351
|
+
protected dismissError(): void;
|
|
5190
6352
|
ngAfterContentInit(): void;
|
|
5191
6353
|
private syncControlState;
|
|
5192
6354
|
/**
|
|
@@ -5194,14 +6356,12 @@ declare class TnFormFieldComponent implements AfterContentInit, TnFormFieldConte
|
|
|
5194
6356
|
* `errorMessages` input (and the injected resolver), so it is reactive: the
|
|
5195
6357
|
* displayed message updates when either the control errors or the overrides
|
|
5196
6358
|
* change — e.g. a runtime locale switch.
|
|
6359
|
+
*
|
|
6360
|
+
* The ladder itself lives in `./form-field.errors`, shared with
|
|
6361
|
+
* `tn-form-errors` so a group-level message reads exactly like the
|
|
6362
|
+
* field-level one it sits beside.
|
|
5197
6363
|
*/
|
|
5198
6364
|
private resolveErrorMessage;
|
|
5199
|
-
/**
|
|
5200
|
-
* Runs a caller-supplied message provider, swallowing any throw so a buggy
|
|
5201
|
-
* override or resolver cannot break change detection. Logs in dev mode and
|
|
5202
|
-
* returns null so resolution falls through to the next layer.
|
|
5203
|
-
*/
|
|
5204
|
-
private runGuarded;
|
|
5205
6365
|
showError: _angular_core.Signal<boolean>;
|
|
5206
6366
|
showHint: _angular_core.Signal<boolean>;
|
|
5207
6367
|
protected showSubscript: _angular_core.Signal<boolean>;
|
|
@@ -5218,7 +6378,7 @@ declare class TnFormFieldComponent implements AfterContentInit, TnFormFieldConte
|
|
|
5218
6378
|
/** Forced or validator-inferred required state, for `aria-required`. */
|
|
5219
6379
|
requiredState: _angular_core.Signal<boolean>;
|
|
5220
6380
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnFormFieldComponent, never>;
|
|
5221
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormFieldComponent, "tn-form-field", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "subscriptSizing": { "alias": "subscriptSizing"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "errorMessages": { "alias": "errorMessages"; "required": false; "isSignal": true; }; }, {}, ["control"], ["*"], true, never>;
|
|
6381
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormFieldComponent, "tn-form-field", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "subscriptSizing": { "alias": "subscriptSizing"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "tooltipSticky": { "alias": "tooltipSticky"; "required": false; "isSignal": true; }; "errorMessages": { "alias": "errorMessages"; "required": false; "isSignal": true; }; "dismissibleErrors": { "alias": "dismissibleErrors"; "required": false; "isSignal": true; }; "dismissAriaLabel": { "alias": "dismissAriaLabel"; "required": false; "isSignal": true; }; "dismissTooltip": { "alias": "dismissTooltip"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, ["control"], ["*"], true, never>;
|
|
5222
6382
|
}
|
|
5223
6383
|
|
|
5224
6384
|
/**
|
|
@@ -5275,6 +6435,59 @@ type TnFormFieldErrorResolver = (errorKey: string, errorValue: unknown, control:
|
|
|
5275
6435
|
* ```
|
|
5276
6436
|
*/
|
|
5277
6437
|
declare const TN_FORM_FIELD_ERRORS: InjectionToken<TnFormFieldErrorResolver>;
|
|
6438
|
+
/**
|
|
6439
|
+
* App-wide default for which error keys carry a dismiss button, for apps whose
|
|
6440
|
+
* server-side failures always land under the same keys. A field's
|
|
6441
|
+
* `dismissibleErrors` input overrides it — including with `[]`, to opt one field
|
|
6442
|
+
* out of the default.
|
|
6443
|
+
*
|
|
6444
|
+
* Listing a key here grants permission to delete it: dismissing removes it from
|
|
6445
|
+
* the control's errors, since a message the user can close but that will not go
|
|
6446
|
+
* away is worse than no button at all.
|
|
6447
|
+
*
|
|
6448
|
+
* @example
|
|
6449
|
+
* ```ts
|
|
6450
|
+
* providers: [
|
|
6451
|
+
* {
|
|
6452
|
+
* provide: TN_FORM_FIELD_DISMISSIBLE_ERRORS,
|
|
6453
|
+
* useValue: ['manualValidateError', 'manualValidateErrorMsg'],
|
|
6454
|
+
* },
|
|
6455
|
+
* ];
|
|
6456
|
+
* ```
|
|
6457
|
+
*/
|
|
6458
|
+
declare const TN_FORM_FIELD_DISMISSIBLE_ERRORS: InjectionToken<readonly string[]>;
|
|
6459
|
+
/** What {@link resolveErrorMessage} needs to answer for one control. */
|
|
6460
|
+
interface ResolveErrorMessageOptions {
|
|
6461
|
+
/** The control's current errors — the caller has already checked it has some. */
|
|
6462
|
+
errors: ValidationErrors;
|
|
6463
|
+
/** Per-instance overrides, keyed by error key. Consulted first. */
|
|
6464
|
+
errorMessages?: TnFormFieldErrorMessages;
|
|
6465
|
+
/** The app-wide resolver from {@link TN_FORM_FIELD_ERRORS}, if one is provided. */
|
|
6466
|
+
resolver?: TnFormFieldErrorResolver | null;
|
|
6467
|
+
/** The failing control, passed through to the resolver. */
|
|
6468
|
+
control?: AbstractControl | null;
|
|
6469
|
+
/**
|
|
6470
|
+
* Who is asking, used only to attribute the dev-mode `console.error` this
|
|
6471
|
+
* logs when a caller-supplied message provider throws. Optional: the library's
|
|
6472
|
+
* own components pass their selector so the log names the element on screen,
|
|
6473
|
+
* and an outside caller has nothing useful to put here.
|
|
6474
|
+
*/
|
|
6475
|
+
selector?: string;
|
|
6476
|
+
}
|
|
6477
|
+
/**
|
|
6478
|
+
* Resolves a user-facing message for a control's active error, in the order
|
|
6479
|
+
* `tn-form-field` has always used:
|
|
6480
|
+
*
|
|
6481
|
+
* 1. a per-instance `errorMessages` override (string or factory),
|
|
6482
|
+
* 2. the app-wide {@link TN_FORM_FIELD_ERRORS} resolver,
|
|
6483
|
+
* 3. {@link defaultErrorMessage} for Angular's standard validators,
|
|
6484
|
+
* 4. the error value itself, when a custom validator returned its own string,
|
|
6485
|
+
* 5. the raw error key.
|
|
6486
|
+
*
|
|
6487
|
+
* Shared with `tn-form-errors` so a group-level message reads exactly like the
|
|
6488
|
+
* field-level one it sits beside.
|
|
6489
|
+
*/
|
|
6490
|
+
declare function resolveErrorMessage(options: ResolveErrorMessageOptions): string;
|
|
5278
6491
|
|
|
5279
6492
|
/**
|
|
5280
6493
|
* Harness for interacting with `tn-form-field` in tests.
|
|
@@ -5308,6 +6521,7 @@ declare class TnFormFieldHarness extends ComponentHarness {
|
|
|
5308
6521
|
private _error;
|
|
5309
6522
|
private _hint;
|
|
5310
6523
|
private _tooltip;
|
|
6524
|
+
private _dismiss;
|
|
5311
6525
|
/**
|
|
5312
6526
|
* Gets a `HarnessPredicate` that can be used to search for a form field
|
|
5313
6527
|
* with specific attributes.
|
|
@@ -5364,6 +6578,37 @@ declare class TnFormFieldHarness extends ComponentHarness {
|
|
|
5364
6578
|
* ```
|
|
5365
6579
|
*/
|
|
5366
6580
|
hasError(): Promise<boolean>;
|
|
6581
|
+
/**
|
|
6582
|
+
* Checks whether the shown error carries a dismiss button — true only when the
|
|
6583
|
+
* active error key is one of the field's `dismissibleErrors`.
|
|
6584
|
+
*
|
|
6585
|
+
* @returns Promise resolving to true if the dismiss button is present.
|
|
6586
|
+
*
|
|
6587
|
+
* @example
|
|
6588
|
+
* ```typescript
|
|
6589
|
+
* const field = await loader.getHarness(TnFormFieldHarness.with({ label: 'Image' }));
|
|
6590
|
+
* expect(await field.isErrorDismissible()).toBe(true);
|
|
6591
|
+
* ```
|
|
6592
|
+
*/
|
|
6593
|
+
isErrorDismissible(): Promise<boolean>;
|
|
6594
|
+
/**
|
|
6595
|
+
* Clicks the dismiss button, as a user clearing a server-side error would.
|
|
6596
|
+
*
|
|
6597
|
+
* The field clears the error itself — every key in its `dismissibleErrors`
|
|
6598
|
+
* that the control carries — puts focus back on the control, and then emits
|
|
6599
|
+
* `dismiss` with the key that was on screen, so the message is gone by the
|
|
6600
|
+
* time this resolves.
|
|
6601
|
+
*
|
|
6602
|
+
* @throws If the shown error is not dismissible.
|
|
6603
|
+
*
|
|
6604
|
+
* @example
|
|
6605
|
+
* ```typescript
|
|
6606
|
+
* const field = await loader.getHarness(TnFormFieldHarness.with({ label: 'Image' }));
|
|
6607
|
+
* await field.dismissError();
|
|
6608
|
+
* expect(await field.hasError()).toBe(false);
|
|
6609
|
+
* ```
|
|
6610
|
+
*/
|
|
6611
|
+
dismissError(): Promise<void>;
|
|
5367
6612
|
/**
|
|
5368
6613
|
* Gets the hint text, if visible.
|
|
5369
6614
|
*
|
|
@@ -5391,6 +6636,9 @@ declare class TnFormFieldHarness extends ComponentHarness {
|
|
|
5391
6636
|
/**
|
|
5392
6637
|
* Gets the tooltip message (read from the trigger's accessible label).
|
|
5393
6638
|
*
|
|
6639
|
+
* A message holding markup — a link, say — comes back as the text a screen reader hears, since
|
|
6640
|
+
* that is what the label carries; assert against the message's text, not its tags.
|
|
6641
|
+
*
|
|
5394
6642
|
* @returns Promise resolving to the tooltip text, or null if no tooltip.
|
|
5395
6643
|
*
|
|
5396
6644
|
* @example
|
|
@@ -5472,6 +6720,455 @@ interface FormFieldHarnessFilters extends BaseHarnessFilters {
|
|
|
5472
6720
|
testId?: string;
|
|
5473
6721
|
}
|
|
5474
6722
|
|
|
6723
|
+
/**
|
|
6724
|
+
* Renders the validation message for a control that is NOT projected into a
|
|
6725
|
+
* `tn-form-field` — in practice a `FormGroup` or `FormArray`, whose errors
|
|
6726
|
+
* belong to the group as a whole and so have no single field to sit under.
|
|
6727
|
+
*
|
|
6728
|
+
* `tn-form-field` covers the ordinary case and should still be preferred: it
|
|
6729
|
+
* owns the label, the `aria-describedby` wiring and the subscript slot. Reach
|
|
6730
|
+
* for this component only where there is no field to own the message —
|
|
6731
|
+
* a cross-field validator on a group, a `minArrayLength` on a form array, a
|
|
6732
|
+
* server-side error attached to a group by an error handler.
|
|
6733
|
+
*
|
|
6734
|
+
* The message comes from the same ladder `tn-form-field` uses (per-instance
|
|
6735
|
+
* `errorMessages`, then the app-wide {@link TN_FORM_FIELD_ERRORS} resolver,
|
|
6736
|
+
* then the built-in defaults), so a group message reads exactly like the field
|
|
6737
|
+
* messages around it. Like `tn-form-field`, it shows ONE message — the active
|
|
6738
|
+
* error, chosen by the same priority — rather than every error at once.
|
|
6739
|
+
*
|
|
6740
|
+
* @example
|
|
6741
|
+
* ```html
|
|
6742
|
+
* <tn-form-errors [control]="form.controls.schedule" />
|
|
6743
|
+
* ```
|
|
6744
|
+
*/
|
|
6745
|
+
declare class TnFormErrorsComponent {
|
|
6746
|
+
/** The control whose errors are rendered. Usually a group or an array. */
|
|
6747
|
+
control: _angular_core.InputSignal<AbstractControl<any, any, any>>;
|
|
6748
|
+
/**
|
|
6749
|
+
* Per-instance overrides, keyed by error key. Take precedence over the
|
|
6750
|
+
* app-wide resolver, exactly as on `tn-form-field`.
|
|
6751
|
+
*/
|
|
6752
|
+
errorMessages: _angular_core.InputSignal<Partial<Record<string, _truenas_ui_components.TnFormFieldErrorMessage>>>;
|
|
6753
|
+
/**
|
|
6754
|
+
* Show the message before the user has touched or dirtied the control.
|
|
6755
|
+
*
|
|
6756
|
+
* Off by default, so a freshly opened form does not greet the user with
|
|
6757
|
+
* errors. Turn it on where the invalid value did not come from the user —
|
|
6758
|
+
* an edit form populated from an API, or a group an error handler has just
|
|
6759
|
+
* attached a server-side failure to.
|
|
6760
|
+
*/
|
|
6761
|
+
showWhenUntouched: _angular_core.InputSignal<boolean>;
|
|
6762
|
+
/**
|
|
6763
|
+
* Test-id base for the message element (`error-` prefixed). There is no
|
|
6764
|
+
* fallback: an `AbstractControl` does not know its own name, so a message
|
|
6765
|
+
* that needs to be addressable in a test has to be named here.
|
|
6766
|
+
*/
|
|
6767
|
+
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
6768
|
+
/**
|
|
6769
|
+
* Error keys whose message renders with a dismiss button beside it — in
|
|
6770
|
+
* practice a failure the user cannot fix by editing a field, so the message
|
|
6771
|
+
* would otherwise stick: a server-side rejection an error handler attached to
|
|
6772
|
+
* the group.
|
|
6773
|
+
*
|
|
6774
|
+
* Only the error actually being shown gets the button, since that is the
|
|
6775
|
+
* message the button belongs to.
|
|
6776
|
+
*
|
|
6777
|
+
* Dismissing deletes these keys from the group's errors — listing them here is
|
|
6778
|
+
* what grants that. Every listed key the group carries goes at once, not just
|
|
6779
|
+
* the one behind the message, so a failure spread across sibling keys cannot
|
|
6780
|
+
* reappear from a sibling. Unlike `tn-form-field` there is no control to hand
|
|
6781
|
+
* focus back to once the button goes away, so a consumer who cares where focus
|
|
6782
|
+
* lands should move it in the {@link dismiss} handler.
|
|
6783
|
+
*
|
|
6784
|
+
* Left unset, the app-wide {@link TN_FORM_FIELD_DISMISSIBLE_ERRORS} default
|
|
6785
|
+
* applies; pass `[]` to opt this message out of it.
|
|
6786
|
+
*/
|
|
6787
|
+
dismissibleErrors: _angular_core.InputSignal<readonly string[] | undefined>;
|
|
6788
|
+
/**
|
|
6789
|
+
* Accessible name for the dismiss button, which is icon-only and so has
|
|
6790
|
+
* nothing else to be named by. The library cannot translate its own
|
|
6791
|
+
* `'Dismiss this error'` default, so a consumer with an i18n layer passes an
|
|
6792
|
+
* already-translated string here.
|
|
6793
|
+
*/
|
|
6794
|
+
dismissAriaLabel: _angular_core.InputSignal<string | undefined>;
|
|
6795
|
+
/**
|
|
6796
|
+
* Hover tooltip for the dismiss button. Defaults to the resolved
|
|
6797
|
+
* `dismissAriaLabel`, so one translated string covers both the accessible name
|
|
6798
|
+
* and the visible hint.
|
|
6799
|
+
*/
|
|
6800
|
+
dismissTooltip: _angular_core.InputSignal<string | undefined>;
|
|
6801
|
+
/** Emits the error key whose message the user dismissed, after it is removed. */
|
|
6802
|
+
dismiss: _angular_core.OutputEmitterRef<string>;
|
|
6803
|
+
/**
|
|
6804
|
+
* Id of the message element, so a caller can point a control's
|
|
6805
|
+
* `aria-describedby` at a group message it is covered by.
|
|
6806
|
+
*/
|
|
6807
|
+
readonly errorId: string;
|
|
6808
|
+
private errorResolver;
|
|
6809
|
+
private state;
|
|
6810
|
+
protected errorMessage: _angular_core.Signal<string>;
|
|
6811
|
+
/**
|
|
6812
|
+
* Whether to render. A control can be invalid with no message to show — a
|
|
6813
|
+
* group whose only error resolves to blank — so the message is part of the
|
|
6814
|
+
* condition rather than something the template renders empty.
|
|
6815
|
+
*/
|
|
6816
|
+
protected show: _angular_core.Signal<boolean>;
|
|
6817
|
+
/**
|
|
6818
|
+
* The id when there is a message to point at, and null when there is not — so
|
|
6819
|
+
* a caller can bind this straight to `aria-describedby` without ever naming
|
|
6820
|
+
* an element that is not on screen.
|
|
6821
|
+
*/
|
|
6822
|
+
readonly describedBy: _angular_core.Signal<string | null>;
|
|
6823
|
+
/**
|
|
6824
|
+
* The error key the shown message came from. Same pick `resolveErrorMessage`
|
|
6825
|
+
* makes, so the dismiss button can never belong to an error other than the one
|
|
6826
|
+
* being read.
|
|
6827
|
+
*/
|
|
6828
|
+
protected activeError: _angular_core.Signal<string | null>;
|
|
6829
|
+
/**
|
|
6830
|
+
* Which list applies, whether the button shows, and what it is called — shared
|
|
6831
|
+
* with `tn-form-field` so a group message and the field messages beside it can
|
|
6832
|
+
* never decide dismissibility by different rules.
|
|
6833
|
+
*/
|
|
6834
|
+
protected dismissible: DismissibleErrorState;
|
|
6835
|
+
protected dismissError(): void;
|
|
6836
|
+
constructor();
|
|
6837
|
+
private sync;
|
|
6838
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnFormErrorsComponent, never>;
|
|
6839
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormErrorsComponent, "tn-form-errors", never, { "control": { "alias": "control"; "required": true; "isSignal": true; }; "errorMessages": { "alias": "errorMessages"; "required": false; "isSignal": true; }; "showWhenUntouched": { "alias": "showWhenUntouched"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "dismissibleErrors": { "alias": "dismissibleErrors"; "required": false; "isSignal": true; }; "dismissAriaLabel": { "alias": "dismissAriaLabel"; "required": false; "isSignal": true; }; "dismissTooltip": { "alias": "dismissTooltip"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, never, never, true, never>;
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6842
|
+
/**
|
|
6843
|
+
* A set of criteria that can be used to filter a list of
|
|
6844
|
+
* `TnFormErrorsHarness` instances.
|
|
6845
|
+
*/
|
|
6846
|
+
interface TnFormErrorsHarnessFilters extends BaseHarnessFilters {
|
|
6847
|
+
/** Filters by the rendered message. Supports string or regex matching. */
|
|
6848
|
+
textContains?: string | RegExp;
|
|
6849
|
+
}
|
|
6850
|
+
/**
|
|
6851
|
+
* Harness for `tn-form-errors`.
|
|
6852
|
+
*
|
|
6853
|
+
* The host renders nothing while the control is valid, untouched, or has no
|
|
6854
|
+
* message to show, so `hasMessage()` — not the presence of the harness — is
|
|
6855
|
+
* what tells you whether an error is visible.
|
|
6856
|
+
*
|
|
6857
|
+
* @example
|
|
6858
|
+
* ```typescript
|
|
6859
|
+
* const errors = await loader.getHarness(TnFormErrorsHarness);
|
|
6860
|
+
* expect(await errors.getMessage()).toBe('Select at least one day');
|
|
6861
|
+
* ```
|
|
6862
|
+
*/
|
|
6863
|
+
declare class TnFormErrorsHarness extends ComponentHarness {
|
|
6864
|
+
static hostSelector: string;
|
|
6865
|
+
/**
|
|
6866
|
+
* Gets a `HarnessPredicate` for finding a `tn-form-errors` by its message.
|
|
6867
|
+
*
|
|
6868
|
+
* @param options Options for filtering which instances are considered a match.
|
|
6869
|
+
*/
|
|
6870
|
+
static with(options?: TnFormErrorsHarnessFilters): HarnessPredicate<TnFormErrorsHarness>;
|
|
6871
|
+
private message;
|
|
6872
|
+
private dismissButton;
|
|
6873
|
+
/** Whether a message is currently rendered. */
|
|
6874
|
+
hasMessage(): Promise<boolean>;
|
|
6875
|
+
/** The rendered message, or `''` when none is shown. */
|
|
6876
|
+
getMessage(): Promise<string>;
|
|
6877
|
+
/**
|
|
6878
|
+
* Whether the shown message carries a dismiss button — true only when the
|
|
6879
|
+
* active error key is one of the host's `dismissibleErrors`.
|
|
6880
|
+
*/
|
|
6881
|
+
isDismissible(): Promise<boolean>;
|
|
6882
|
+
/**
|
|
6883
|
+
* Clicks the dismiss button, as a user clearing a server-side error would.
|
|
6884
|
+
*
|
|
6885
|
+
* The component clears the error itself — every key in its `dismissibleErrors`
|
|
6886
|
+
* that the control carries — and then emits `dismiss` with the key that was on
|
|
6887
|
+
* screen, so the message is gone by the time this resolves. The handler is for
|
|
6888
|
+
* what the app does next, such as moving focus.
|
|
6889
|
+
*
|
|
6890
|
+
* @throws If the shown message is not dismissible.
|
|
6891
|
+
*/
|
|
6892
|
+
dismiss(): Promise<void>;
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
/**
|
|
6896
|
+
* What a `tn-form-list-item` needs to know about the list it was projected
|
|
6897
|
+
* into. Published over DI rather than passed as an input, so that locking a
|
|
6898
|
+
* list is one binding on the list and not one on every entry the consumer
|
|
6899
|
+
* writes inside its `@for`.
|
|
6900
|
+
*
|
|
6901
|
+
* A projected entry's element injector chains through the `tn-form-list` it is
|
|
6902
|
+
* declared inside, the same way a control projected into `tn-form-field` reaches
|
|
6903
|
+
* {@link TN_FORM_FIELD_CONTEXT}. An entry used on its own injects nothing and
|
|
6904
|
+
* falls back to its own defaults.
|
|
6905
|
+
*
|
|
6906
|
+
* Its own file, and not `form-list.component.ts`, because the list already
|
|
6907
|
+
* imports the item to count its entries — a token declared beside either
|
|
6908
|
+
* component would make that import cycle.
|
|
6909
|
+
*/
|
|
6910
|
+
interface TnFormListContext {
|
|
6911
|
+
/** Whether the enclosing list is locked, so an entry can disable its remove button. */
|
|
6912
|
+
disabled: Signal<boolean>;
|
|
6913
|
+
}
|
|
6914
|
+
/** DI token under which `tn-form-list` exposes its {@link TnFormListContext}. */
|
|
6915
|
+
declare const TN_FORM_LIST_CONTEXT: InjectionToken<TnFormListContext>;
|
|
6916
|
+
|
|
6917
|
+
/**
|
|
6918
|
+
* The editor for a repeating group of fields — a `FormArray` the user grows
|
|
6919
|
+
* and shrinks, rendered as a labelled group of {@link TnFormListItemComponent}
|
|
6920
|
+
* cards with an Add control in the header.
|
|
6921
|
+
*
|
|
6922
|
+
* Not to be confused with `tn-list`, which DISPLAYS a list of items. This one
|
|
6923
|
+
* edits one, and owns none of the array: the consumer holds the `FormArray`,
|
|
6924
|
+
* renders an item per element, and does the pushing and splicing in response
|
|
6925
|
+
* to `(add)` and each item's `(delete)`. That keeps the item's shape — which
|
|
6926
|
+
* only the consumer knows — out of the library.
|
|
6927
|
+
*
|
|
6928
|
+
* @example
|
|
6929
|
+
* ```html
|
|
6930
|
+
* <tn-form-list label="ACL entries" [control]="form.controls.entries" (add)="addEntry()">
|
|
6931
|
+
* @for (entry of form.controls.entries.controls; track entry; let i = $index) {
|
|
6932
|
+
* <tn-form-list-item label="ACL entry" (delete)="removeEntry(i)">
|
|
6933
|
+
* <!-- the entry's own fields -->
|
|
6934
|
+
* </tn-form-list-item>
|
|
6935
|
+
* }
|
|
6936
|
+
* </tn-form-list>
|
|
6937
|
+
* ```
|
|
6938
|
+
*/
|
|
6939
|
+
declare class TnFormListComponent implements TnFormListContext {
|
|
6940
|
+
/**
|
|
6941
|
+
* The `FormArray` being edited. Optional, and used only to render an error
|
|
6942
|
+
* that belongs to the array as a whole — a minimum or maximum length. The
|
|
6943
|
+
* component neither reads the elements nor writes to it.
|
|
6944
|
+
*/
|
|
6945
|
+
control: _angular_core.InputSignal<AbstractControl<any, any, any> | undefined>;
|
|
6946
|
+
/**
|
|
6947
|
+
* What the list is called, in the plural ('ACL entries'). Names the group,
|
|
6948
|
+
* so a screen reader announces which list a field is inside. Supports the
|
|
6949
|
+
* same lightweight markup as `tn-form-field` labels.
|
|
6950
|
+
*/
|
|
6951
|
+
label: _angular_core.InputSignal<string>;
|
|
6952
|
+
/**
|
|
6953
|
+
* Optional help tooltip, shown via an icon in the header — next to the label
|
|
6954
|
+
* where there is one, and on its own where there is not.
|
|
6955
|
+
*/
|
|
6956
|
+
tooltip: _angular_core.InputSignal<string>;
|
|
6957
|
+
/** Placement of the tooltip relative to its help icon. */
|
|
6958
|
+
tooltipPosition: _angular_core.InputSignal<TooltipPosition>;
|
|
6959
|
+
/** Marks the list as required — at least one entry. Renders the asterisk. */
|
|
6960
|
+
required: _angular_core.InputSignal<boolean>;
|
|
6961
|
+
/** Whether the Add control renders. Turn it off at a maximum length. */
|
|
6962
|
+
canAdd: _angular_core.InputSignal<boolean>;
|
|
6963
|
+
/**
|
|
6964
|
+
* Locks the list, for one the user may not edit yet: the group reports itself
|
|
6965
|
+
* `aria-disabled`, the entries are dimmed and stop taking pointer events, and
|
|
6966
|
+
* Add and every remove button are disabled.
|
|
6967
|
+
*
|
|
6968
|
+
* It does NOT disable the fields inside the entries — those are projected
|
|
6969
|
+
* content the consumer owns, so locking them is `entries.disable()` on the
|
|
6970
|
+
* `FormArray`, which is also what keeps their values out of `form.value`. This
|
|
6971
|
+
* input deliberately does not reach them by going `inert` instead: the entries
|
|
6972
|
+
* stay on screen, and `inert` would drop what a sighted user can still read out
|
|
6973
|
+
* of the accessibility tree entirely.
|
|
6974
|
+
*/
|
|
6975
|
+
disabled: _angular_core.InputSignal<boolean>;
|
|
6976
|
+
/** Text of the Add control. English by default — pass a translated string. */
|
|
6977
|
+
addLabel: _angular_core.InputSignal<string>;
|
|
6978
|
+
/** Shown in place of the entries while there are none. */
|
|
6979
|
+
emptyMessage: _angular_core.InputSignal<string>;
|
|
6980
|
+
/**
|
|
6981
|
+
* Overrides the derived empty state. Set it to `false` while the entries are
|
|
6982
|
+
* still being fetched, so a list that is merely not loaded yet does not
|
|
6983
|
+
* announce itself as empty and then fill in.
|
|
6984
|
+
*/
|
|
6985
|
+
empty: _angular_core.InputSignal<boolean | undefined>;
|
|
6986
|
+
/** Per-error overrides for the array-level message, as on `tn-form-field`. */
|
|
6987
|
+
errorMessages: _angular_core.InputSignal<Partial<Record<string, _truenas_ui_components.TnFormFieldErrorMessage>>>;
|
|
6988
|
+
/**
|
|
6989
|
+
* Show the array-level message before the user has touched the array — for a
|
|
6990
|
+
* list populated from an API, or one an error handler has just attached a
|
|
6991
|
+
* server-side failure to. Passed straight to `tn-form-errors`.
|
|
6992
|
+
*/
|
|
6993
|
+
showErrorWhenUntouched: _angular_core.InputSignal<boolean>;
|
|
6994
|
+
/**
|
|
6995
|
+
* Error keys whose array-level message renders with a close button, and which
|
|
6996
|
+
* dismissing deletes. Unset takes the app-wide
|
|
6997
|
+
* `TN_FORM_FIELD_DISMISSIBLE_ERRORS` default; `[]` opts this list out. Passed
|
|
6998
|
+
* straight to `tn-form-errors`, which is where the reasoning lives.
|
|
6999
|
+
*/
|
|
7000
|
+
dismissibleErrors: _angular_core.InputSignal<readonly string[] | undefined>;
|
|
7001
|
+
/** Accessible name for that close button. Pass it already translated. */
|
|
7002
|
+
dismissAriaLabel: _angular_core.InputSignal<string | undefined>;
|
|
7003
|
+
/** Hover hint for that close button. Defaults to `dismissAriaLabel`. */
|
|
7004
|
+
dismissTooltip: _angular_core.InputSignal<string | undefined>;
|
|
7005
|
+
/**
|
|
7006
|
+
* Test-id base for the group (`form-list-` prefixed). Also names the
|
|
7007
|
+
* array-level message, which gets it `error-` prefixed.
|
|
7008
|
+
*/
|
|
7009
|
+
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
7010
|
+
/** Emitted when Add is pressed. Appending the element is the consumer's. */
|
|
7011
|
+
add: _angular_core.OutputEmitterRef<void>;
|
|
7012
|
+
/**
|
|
7013
|
+
* Emitted with the error key when the user closes the array-level message,
|
|
7014
|
+
* after it has been removed. `tn-form-errors` has no control to hand focus
|
|
7015
|
+
* back to, so a consumer who cares where focus lands moves it here.
|
|
7016
|
+
*/
|
|
7017
|
+
dismiss: _angular_core.OutputEmitterRef<string>;
|
|
7018
|
+
/**
|
|
7019
|
+
* The projected entries. Counted rather than derived from the `FormArray`,
|
|
7020
|
+
* so the empty state follows what is actually on screen — a consumer may
|
|
7021
|
+
* filter or page the elements it renders, and `control` is optional anyway.
|
|
7022
|
+
*/
|
|
7023
|
+
private items;
|
|
7024
|
+
protected isEmpty: _angular_core.Signal<boolean>;
|
|
7025
|
+
/**
|
|
7026
|
+
* The array-level message, queried rather than reached through a template
|
|
7027
|
+
* reference: it renders inside an `@if`, and a reference declared in a block
|
|
7028
|
+
* is scoped to that block.
|
|
7029
|
+
*/
|
|
7030
|
+
private errors;
|
|
7031
|
+
/**
|
|
7032
|
+
* Points the group at its own message. `role="alert"` covers the moment the
|
|
7033
|
+
* error appears; it says nothing to a screen reader that tabs into the list
|
|
7034
|
+
* afterwards, which would otherwise hear the label and never learn the array
|
|
7035
|
+
* is in error. Null while there is no message, so this never names an element
|
|
7036
|
+
* that is not on screen.
|
|
7037
|
+
*/
|
|
7038
|
+
protected describedBy: _angular_core.Signal<string | null>;
|
|
7039
|
+
/**
|
|
7040
|
+
* Accessible name for the Add control, which reads as a bare 'Add' beside
|
|
7041
|
+
* every other list on a long form. Names the list it adds to.
|
|
7042
|
+
*/
|
|
7043
|
+
protected addAriaLabel: _angular_core.Signal<string>;
|
|
7044
|
+
protected tooltipAriaLabel: _angular_core.Signal<string>;
|
|
7045
|
+
/**
|
|
7046
|
+
* Stable id naming the group from the label text alone. Without it the
|
|
7047
|
+
* group's accessible name would also absorb the tooltip button and the Add
|
|
7048
|
+
* control, announcing both on every field inside the list.
|
|
7049
|
+
*/
|
|
7050
|
+
protected readonly labelId: string;
|
|
7051
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnFormListComponent, never>;
|
|
7052
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormListComponent, "tn-form-list", never, { "control": { "alias": "control"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "canAdd": { "alias": "canAdd"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "addLabel": { "alias": "addLabel"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "empty": { "alias": "empty"; "required": false; "isSignal": true; }; "errorMessages": { "alias": "errorMessages"; "required": false; "isSignal": true; }; "showErrorWhenUntouched": { "alias": "showErrorWhenUntouched"; "required": false; "isSignal": true; }; "dismissibleErrors": { "alias": "dismissibleErrors"; "required": false; "isSignal": true; }; "dismissAriaLabel": { "alias": "dismissAriaLabel"; "required": false; "isSignal": true; }; "dismissTooltip": { "alias": "dismissTooltip"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "add": "add"; "dismiss": "dismiss"; }, ["items"], ["*"], true, never>;
|
|
7053
|
+
}
|
|
7054
|
+
|
|
7055
|
+
/**
|
|
7056
|
+
* One entry of a {@link TnFormListComponent} — a bordered card holding the
|
|
7057
|
+
* controls for a single element of the form array, with the control that
|
|
7058
|
+
* removes it.
|
|
7059
|
+
*
|
|
7060
|
+
* The item owns only the frame and the remove button; the controls inside are
|
|
7061
|
+
* the consumer's, bound to that element's `FormGroup`.
|
|
7062
|
+
*/
|
|
7063
|
+
declare class TnFormListItemComponent {
|
|
7064
|
+
/**
|
|
7065
|
+
* Whether this entry can be removed. Turn it off for an entry the form
|
|
7066
|
+
* requires — the button disappears rather than being disabled, since a
|
|
7067
|
+
* permanently disabled control tells the user nothing about why.
|
|
7068
|
+
*/
|
|
7069
|
+
canDelete: _angular_core.InputSignal<boolean>;
|
|
7070
|
+
/**
|
|
7071
|
+
* What one entry is called, in the singular ('ACL entry', 'Portal'). Used to
|
|
7072
|
+
* name the remove button, which is icon-only and has nothing else to be
|
|
7073
|
+
* named by. Pass it already translated.
|
|
7074
|
+
*/
|
|
7075
|
+
label: _angular_core.InputSignal<string>;
|
|
7076
|
+
/**
|
|
7077
|
+
* Accessible name for the remove button. Defaults to `Remove <label>`, or
|
|
7078
|
+
* plain `Remove` when there is no label. Set it to translate the wording —
|
|
7079
|
+
* the library ships English only.
|
|
7080
|
+
*/
|
|
7081
|
+
removeAriaLabel: _angular_core.InputSignal<string>;
|
|
7082
|
+
/**
|
|
7083
|
+
* Disables the remove button — the entry stays readable, the control just
|
|
7084
|
+
* stops working, the way a native disabled control does.
|
|
7085
|
+
*
|
|
7086
|
+
* Left unset it follows the enclosing `tn-form-list`'s own `disabled`, so
|
|
7087
|
+
* locking a list is one binding on the list rather than one per entry. Set it
|
|
7088
|
+
* explicitly to lock a single entry inside an otherwise editable list.
|
|
7089
|
+
*/
|
|
7090
|
+
disabled: _angular_core.InputSignal<boolean | undefined>;
|
|
7091
|
+
/** Test-id base for the remove button (`icon-button-` prefixed). */
|
|
7092
|
+
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
7093
|
+
/** Emitted when the remove button is pressed. Removing is the consumer's. */
|
|
7094
|
+
delete: _angular_core.OutputEmitterRef<void>;
|
|
7095
|
+
/** Absent when the entry is used outside a `tn-form-list`. */
|
|
7096
|
+
private list;
|
|
7097
|
+
protected resolvedDisabled: _angular_core.Signal<boolean>;
|
|
7098
|
+
protected resolvedRemoveAriaLabel: _angular_core.Signal<string>;
|
|
7099
|
+
protected resolvedTestId: _angular_core.Signal<string | number | (string | number | null | undefined)[]>;
|
|
7100
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnFormListItemComponent, never>;
|
|
7101
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormListItemComponent, "tn-form-list-item", never, { "canDelete": { "alias": "canDelete"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "removeAriaLabel": { "alias": "removeAriaLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "delete": "delete"; }, never, ["*"], true, never>;
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
/** Criteria for filtering `TnFormListHarness` instances. */
|
|
7105
|
+
interface TnFormListHarnessFilters extends BaseHarnessFilters {
|
|
7106
|
+
/** Filters by the list's label. Supports string or regex matching. */
|
|
7107
|
+
label?: string | RegExp;
|
|
7108
|
+
}
|
|
7109
|
+
/** Criteria for filtering `TnFormListItemHarness` instances. */
|
|
7110
|
+
type TnFormListItemHarnessFilters = BaseHarnessFilters;
|
|
7111
|
+
/**
|
|
7112
|
+
* Harness for one entry of a `tn-form-list`.
|
|
7113
|
+
*
|
|
7114
|
+
* @example
|
|
7115
|
+
* ```typescript
|
|
7116
|
+
* const [first] = await list.getItems();
|
|
7117
|
+
* await first.remove();
|
|
7118
|
+
* ```
|
|
7119
|
+
*/
|
|
7120
|
+
declare class TnFormListItemHarness extends ComponentHarness {
|
|
7121
|
+
static hostSelector: string;
|
|
7122
|
+
static with(options?: TnFormListItemHarnessFilters): HarnessPredicate<TnFormListItemHarness>;
|
|
7123
|
+
private removeButton;
|
|
7124
|
+
/** Whether this entry offers a remove control. */
|
|
7125
|
+
canRemove(): Promise<boolean>;
|
|
7126
|
+
/**
|
|
7127
|
+
* Whether the remove control is disabled — true for an entry inside a
|
|
7128
|
+
* `disabled` list, or one given its own `disabled`. `false` when the entry
|
|
7129
|
+
* offers no remove control at all; ask `canRemove()` to tell the two apart.
|
|
7130
|
+
*/
|
|
7131
|
+
isRemoveDisabled(): Promise<boolean>;
|
|
7132
|
+
/** Presses the remove control. Throws when the entry has none. */
|
|
7133
|
+
remove(): Promise<void>;
|
|
7134
|
+
}
|
|
7135
|
+
/**
|
|
7136
|
+
* Harness for `tn-form-list`, the editor for a repeating group of fields.
|
|
7137
|
+
*
|
|
7138
|
+
* @example
|
|
7139
|
+
* ```typescript
|
|
7140
|
+
* const list = await loader.getHarness(TnFormListHarness.with({ label: 'ACL entries' }));
|
|
7141
|
+
* expect(await list.getItemCount()).toBe(0);
|
|
7142
|
+
* expect(await list.isEmpty()).toBe(true);
|
|
7143
|
+
*
|
|
7144
|
+
* await list.add();
|
|
7145
|
+
* expect(await list.getItemCount()).toBe(1);
|
|
7146
|
+
* ```
|
|
7147
|
+
*/
|
|
7148
|
+
declare class TnFormListHarness extends ComponentHarness {
|
|
7149
|
+
static hostSelector: string;
|
|
7150
|
+
static with(options?: TnFormListHarnessFilters): HarnessPredicate<TnFormListHarness>;
|
|
7151
|
+
private labelEl;
|
|
7152
|
+
private addButton;
|
|
7153
|
+
private emptyEl;
|
|
7154
|
+
/** The list's label, or `''` when it has none. */
|
|
7155
|
+
getLabel(): Promise<string>;
|
|
7156
|
+
/** Whether the Add control renders. */
|
|
7157
|
+
canAdd(): Promise<boolean>;
|
|
7158
|
+
/** Whether the Add control is disabled. */
|
|
7159
|
+
isAddDisabled(): Promise<boolean>;
|
|
7160
|
+
/** Presses Add. Throws when the list offers no Add control. */
|
|
7161
|
+
add(): Promise<void>;
|
|
7162
|
+
/** The entries currently rendered. */
|
|
7163
|
+
getItems(): Promise<TnFormListItemHarness[]>;
|
|
7164
|
+
/** How many entries are rendered. */
|
|
7165
|
+
getItemCount(): Promise<number>;
|
|
7166
|
+
/** Whether the empty message is showing. */
|
|
7167
|
+
isEmpty(): Promise<boolean>;
|
|
7168
|
+
/** The empty message, or `''` when the list has entries. */
|
|
7169
|
+
getEmptyMessage(): Promise<string>;
|
|
7170
|
+
}
|
|
7171
|
+
|
|
5475
7172
|
/**
|
|
5476
7173
|
* Semantic grouping for a related set of form fields. Renders a native
|
|
5477
7174
|
* `<fieldset>` with an optional `<legend>` heading and help tooltip, and
|
|
@@ -5488,6 +7185,21 @@ declare class TnFormSectionComponent {
|
|
|
5488
7185
|
tooltip: _angular_core.InputSignal<string>;
|
|
5489
7186
|
/** Placement of the tooltip relative to its help icon. */
|
|
5490
7187
|
tooltipPosition: _angular_core.InputSignal<TooltipPosition>;
|
|
7188
|
+
/**
|
|
7189
|
+
* Whether a tooltip message holding a link may be pinned open by clicking the help button (see
|
|
7190
|
+
* `tnTooltipSticky`). On by default, like the directive.
|
|
7191
|
+
*
|
|
7192
|
+
* It does not make plain tooltips pinnable — section help is nearly always plain text, and that
|
|
7193
|
+
* keeps hovering. Set it to false only to force a message that does hold a link back to hover
|
|
7194
|
+
* behaviour, accepting that the link is then unreachable.
|
|
7195
|
+
*/
|
|
7196
|
+
tooltipSticky: _angular_core.InputSignal<boolean>;
|
|
7197
|
+
/**
|
|
7198
|
+
* Accessible name for the help button, which is icon-only and so has nothing else to be named
|
|
7199
|
+
* by. The message is what the button is for, but it may hold markup — a link is the whole point
|
|
7200
|
+
* of `tooltipSticky` — and `aria-label` takes plain text, so the tags come off first.
|
|
7201
|
+
*/
|
|
7202
|
+
protected readonly tooltipAriaLabel: _angular_core.Signal<string>;
|
|
5491
7203
|
/** Test id applied to the host for harness/e2e selection. */
|
|
5492
7204
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
5493
7205
|
/**
|
|
@@ -5498,7 +7210,7 @@ declare class TnFormSectionComponent {
|
|
|
5498
7210
|
*/
|
|
5499
7211
|
protected readonly headingId: string;
|
|
5500
7212
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnFormSectionComponent, never>;
|
|
5501
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormSectionComponent, "tn-form-section", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
7213
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnFormSectionComponent, "tn-form-section", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "tooltipSticky": { "alias": "tooltipSticky"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
5502
7214
|
}
|
|
5503
7215
|
|
|
5504
7216
|
/**
|
|
@@ -6233,8 +7945,78 @@ declare class TnListItemComponent {
|
|
|
6233
7945
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnListItemComponent, "tn-list-item", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "dense": { "alias": "dense"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, ["leadingIcons", "leadingAvatars", "secondaryLines", "secondaryTexts", "trailing"], ["[tnListIcon], [tnListAvatar]", "[tnListItemTitle], [tnListItemPrimary]", "*", "[tnListItemLine], [tnListItemSecondary]", "[tnListItemTrailing]"], true, never>;
|
|
6234
7946
|
}
|
|
6235
7947
|
|
|
6236
|
-
|
|
7948
|
+
/**
|
|
7949
|
+
* A section heading inside a list.
|
|
7950
|
+
*
|
|
7951
|
+
* A container role can forbid its children's roles, so a heading between two
|
|
7952
|
+
* rows invalidates the container — axe's `aria-required-children`, and the
|
|
7953
|
+
* defect fixed in #237 and #259. The heading is not dropped for that; it is
|
|
7954
|
+
* moved, and where the HOST goes depends on what owns it. See `ariaOwnerRole`
|
|
7955
|
+
* for what "owns" means here.
|
|
7956
|
+
*
|
|
7957
|
+
* | Owner | Host | The element around the text |
|
|
7958
|
+
* |---|---|---|
|
|
7959
|
+
* | `role="list"` | `listitem` | `heading`, level 3 |
|
|
7960
|
+
* | `role="listbox"` | `group`, named by the text below | an ordinary span |
|
|
7961
|
+
* | anything else | `heading`, level 3 | an ordinary span |
|
|
7962
|
+
*
|
|
7963
|
+
* Inside a LIST the host becomes the `listitem` the list requires and the
|
|
7964
|
+
* heading goes one level in — `<li><h3>Pools</h3></li>` in plain HTML, which
|
|
7965
|
+
* keeps the section heading in the accessibility tree at the level it always
|
|
7966
|
+
* had. The cost is that the list counts one more item per section, which is the
|
|
7967
|
+
* same count a browser reports for that HTML.
|
|
7968
|
+
*
|
|
7969
|
+
* Inside a LISTBOX neither of those is available. `listitem` is not an allowed
|
|
7970
|
+
* child of a listbox either, so it trades one `aria-required-children` violation
|
|
7971
|
+
* for another; and the heading cannot move one level in the way it does for a
|
|
7972
|
+
* list, because axe reads THROUGH a `group` when it collects what a listbox
|
|
7973
|
+
* owns — measured, a `group` wrapping a `role="heading"` reports the same
|
|
7974
|
+
* violation, now naming the heading. So the section survives as a `group` with
|
|
7975
|
+
* the subheader's own text as its accessible name, via `aria-labelledby` to the
|
|
7976
|
+
* unmarked span: the text is still in the accessibility tree and still
|
|
7977
|
+
* announced, as a named section rather than as a heading.
|
|
7978
|
+
*
|
|
7979
|
+
* That group holds the text and NOT the rows that follow it: a subheader is
|
|
7980
|
+
* projected content and is a sibling of the rows it introduces, so it can name
|
|
7981
|
+
* a section without enclosing one. Genuinely nesting the options is markup for
|
|
7982
|
+
* the consumer to write.
|
|
7983
|
+
*
|
|
7984
|
+
* Outside either — or nested inside a row of one, where a heading is already
|
|
7985
|
+
* legal — the host carries the heading itself and the inner element is an
|
|
7986
|
+
* ordinary span.
|
|
7987
|
+
*/
|
|
7988
|
+
declare class TnListSubheaderComponent implements DoCheck {
|
|
6237
7989
|
inset: _angular_core.InputSignal<boolean>;
|
|
7990
|
+
private readonly owner;
|
|
7991
|
+
/**
|
|
7992
|
+
* Id of the inner span, so that the `group` form can be named by the text.
|
|
7993
|
+
*
|
|
7994
|
+
* Allocated per instance rather than per render, and emitted only when the
|
|
7995
|
+
* group needs it — see the template.
|
|
7996
|
+
*/
|
|
7997
|
+
protected readonly textId: string;
|
|
7998
|
+
/**
|
|
7999
|
+
* Which of the three containers in the class docblock this is sitting in.
|
|
8000
|
+
*
|
|
8001
|
+
* Only the two that prescribe their children are named; every other owner,
|
|
8002
|
+
* `null` included, leaves the heading on the host where it has always been.
|
|
8003
|
+
*/
|
|
8004
|
+
private readonly ownerKind;
|
|
8005
|
+
/** The role the host carries. See the table in the class docblock. */
|
|
8006
|
+
protected readonly hostRole: _angular_core.Signal<"heading" | "listitem" | "group">;
|
|
8007
|
+
/** Whether the heading is on the HOST, which is where `aria-level` follows it. */
|
|
8008
|
+
protected readonly headingOnHost: _angular_core.Signal<boolean>;
|
|
8009
|
+
/**
|
|
8010
|
+
* Whether the heading has moved one level in, onto the span.
|
|
8011
|
+
*
|
|
8012
|
+
* `list` alone, and never both this and {@link headingOnHost}: one section is
|
|
8013
|
+
* one heading, and a host that is both a `listitem` and a heading is what
|
|
8014
|
+
* #237 removed.
|
|
8015
|
+
*/
|
|
8016
|
+
protected readonly headingInside: _angular_core.Signal<boolean>;
|
|
8017
|
+
/** Whether the host is a `group` that the span below has to name. */
|
|
8018
|
+
protected readonly namesGroup: _angular_core.Signal<boolean>;
|
|
8019
|
+
ngDoCheck(): void;
|
|
6238
8020
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnListSubheaderComponent, never>;
|
|
6239
8021
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnListSubheaderComponent, "tn-list-subheader", never, { "inset": { "alias": "inset"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
6240
8022
|
}
|
|
@@ -6303,14 +8085,53 @@ declare class TnListItemTrailingDirective {
|
|
|
6303
8085
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnListItemTrailingDirective, never>;
|
|
6304
8086
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnListItemTrailingDirective, "[tnListItemTrailing]", never, {}, {}, never, never, true, never>;
|
|
6305
8087
|
}
|
|
6306
|
-
|
|
8088
|
+
/**
|
|
8089
|
+
* Makes an element that is already something else read as a divider.
|
|
8090
|
+
*
|
|
8091
|
+
* Same ARIA as `TnDividerComponent` and for the same reason (#237):
|
|
8092
|
+
* `role="separator"`, unless a `role="list"` is what owns it, where a separator
|
|
8093
|
+
* is not an allowed child and invalidates the list.
|
|
8094
|
+
*
|
|
8095
|
+
* **It no longer matches `tn-divider` as well, which is a breaking change**:
|
|
8096
|
+
* code that writes `<tn-divider>` while importing only this directive now has
|
|
8097
|
+
* no match for that element and stops compiling. Import `TnDividerComponent`
|
|
8098
|
+
* for the element form — it is what declares the element, and what every use of
|
|
8099
|
+
* `<tn-divider>` in this repository already imports.
|
|
8100
|
+
*
|
|
8101
|
+
* On `tn-divider` this only ever restated what the component declares, and once
|
|
8102
|
+
* the role varies by context, a second source for it is a second answer waiting
|
|
8103
|
+
* to disagree — two instances of the tracking that decides it, on one element,
|
|
8104
|
+
* writing one attribute.
|
|
8105
|
+
*/
|
|
8106
|
+
declare class TnDividerDirective implements DoCheck {
|
|
8107
|
+
private readonly owner;
|
|
8108
|
+
protected readonly role: _angular_core.Signal<"presentation" | "separator">;
|
|
8109
|
+
ngDoCheck(): void;
|
|
6307
8110
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDividerDirective, never>;
|
|
6308
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnDividerDirective, "
|
|
8111
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnDividerDirective, "[tnDivider]", never, {}, {}, never, never, true, never>;
|
|
6309
8112
|
}
|
|
6310
8113
|
|
|
6311
|
-
|
|
8114
|
+
/**
|
|
8115
|
+
* A rule between things.
|
|
8116
|
+
*
|
|
8117
|
+
* `role="separator"`, except where the element that owns it is a container
|
|
8118
|
+
* whose children are prescribed — a `role="list"` owns only `listitem`, so a
|
|
8119
|
+
* separator between two rows invalidates the list it sits in (#237). Owned by a
|
|
8120
|
+
* ROW of that list — a divider inside a `tn-list-item` — it is a separator like
|
|
8121
|
+
* anywhere else. See `ariaOwnerRole` for what "owns" means and why the DOM
|
|
8122
|
+
* decides it rather than DI.
|
|
8123
|
+
*/
|
|
8124
|
+
declare class TnDividerComponent implements DoCheck {
|
|
6312
8125
|
vertical: _angular_core.InputSignal<boolean>;
|
|
6313
8126
|
inset: _angular_core.InputSignal<boolean>;
|
|
8127
|
+
private readonly owner;
|
|
8128
|
+
/**
|
|
8129
|
+
* `presentation` rather than no role at all: both are invisible to assistive
|
|
8130
|
+
* technology and satisfy the container, and this one says in the DOM that the
|
|
8131
|
+
* rule is decoration on purpose.
|
|
8132
|
+
*/
|
|
8133
|
+
protected readonly role: _angular_core.Signal<"presentation" | "separator">;
|
|
8134
|
+
ngDoCheck(): void;
|
|
6314
8135
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDividerComponent, never>;
|
|
6315
8136
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDividerComponent, "tn-divider", never, { "vertical": { "alias": "vertical"; "required": false; "isSignal": true; }; "inset": { "alias": "inset"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
6316
8137
|
}
|
|
@@ -6330,14 +8151,95 @@ declare class TnListOptionComponent implements AfterContentInit {
|
|
|
6330
8151
|
internalDisabled: _angular_core.WritableSignal<boolean | null>;
|
|
6331
8152
|
internalColor: _angular_core.WritableSignal<"primary" | "warn" | "accent" | null>;
|
|
6332
8153
|
effectiveSelected: _angular_core.Signal<boolean>;
|
|
8154
|
+
/**
|
|
8155
|
+
* Whether the parent `tn-selection-list` is disabled as a whole, pushed down
|
|
8156
|
+
* by the listbox (#221). `false` when there is no parent.
|
|
8157
|
+
*
|
|
8158
|
+
* Kept apart from `internalDisabled` rather than written into it, because the
|
|
8159
|
+
* two answer different questions and a single slot cannot hold both. The list
|
|
8160
|
+
* disabling itself must not erase an option the consumer disabled on its own,
|
|
8161
|
+
* and re-enabling the list is exactly where a shared slot loses that: the
|
|
8162
|
+
* parent has no record of what the option's own state was before it wrote
|
|
8163
|
+
* over it, so `[disabled]="true"` on the option never comes back. That is not
|
|
8164
|
+
* hypothetical — it is what `setDisabledState()` did to a per-option
|
|
8165
|
+
* `[disabled]` on every `control.enable()` before this signal existed.
|
|
8166
|
+
*
|
|
8167
|
+
* A plain boolean rather than the nullable this sits beside: absent and
|
|
8168
|
+
* not-disabled are the same thing for a parent's opinion, whereas
|
|
8169
|
+
* `internalDisabled` needs `null` to mean "the input still speaks".
|
|
8170
|
+
*/
|
|
8171
|
+
listDisabled: _angular_core.WritableSignal<boolean>;
|
|
8172
|
+
/**
|
|
8173
|
+
* Disabled if EITHER source says so — the list as a whole, or this option.
|
|
8174
|
+
*
|
|
8175
|
+
* Not a precedence: a disabled list cannot be overridden by an enabled
|
|
8176
|
+
* option, and a list that is enabled has nothing to say about an option that
|
|
8177
|
+
* disabled itself. Only the second half is a fallback chain, and it is the
|
|
8178
|
+
* one that already existed: `internalDisabled` outranks the input when it has
|
|
8179
|
+
* been written. Nothing in the library writes it any more — `setDisabledState()`
|
|
8180
|
+
* was its only writer and now stops at `listDisabled` — so it is kept for the
|
|
8181
|
+
* public surface it has always been part of, alongside `internalSelected` and
|
|
8182
|
+
* `internalColor`, rather than because a path in here still uses it.
|
|
8183
|
+
*/
|
|
6333
8184
|
effectiveDisabled: _angular_core.Signal<boolean>;
|
|
6334
8185
|
effectiveColor: _angular_core.Signal<"primary" | "warn" | "accent">;
|
|
8186
|
+
/**
|
|
8187
|
+
* The tabindex a parent `tn-selection-list` has assigned, or `null` when this
|
|
8188
|
+
* option is standalone.
|
|
8189
|
+
*
|
|
8190
|
+
* Set by the listbox's roving tabindex (#216): 0 on the one option that is
|
|
8191
|
+
* the list's single tab stop, -1 on the rest. `null` is not "no tab stop" but
|
|
8192
|
+
* "no parent" — it is what keeps a `tn-list-option` used on its own behaving
|
|
8193
|
+
* as #213 left it, and it is why this is a nullable number rather than a
|
|
8194
|
+
* number defaulting to -1.
|
|
8195
|
+
*/
|
|
8196
|
+
rovingTabindex: _angular_core.WritableSignal<number | null>;
|
|
8197
|
+
/**
|
|
8198
|
+
* -1 rather than a removed attribute for a disabled option under a roving
|
|
8199
|
+
* tabindex, which is the one place these two paths disagree. An element with
|
|
8200
|
+
* no `tabindex` cannot be focused programmatically either, so dropping the
|
|
8201
|
+
* attribute would leave the listbox's arrow keys with nothing to move focus
|
|
8202
|
+
* to — and the listbox deliberately visits disabled options, so that they can
|
|
8203
|
+
* be perceived rather than silently skipped. Standalone, where every stop
|
|
8204
|
+
* costs a Tab press, the disabled option is still left out entirely.
|
|
8205
|
+
*/
|
|
8206
|
+
effectiveTabindex: _angular_core.Signal<number | null>;
|
|
6335
8207
|
protected hasLeadingContent: _angular_core.WritableSignal<boolean>;
|
|
6336
8208
|
protected hasSecondaryTextContent: _angular_core.WritableSignal<boolean>;
|
|
6337
8209
|
protected hasPrimaryTextDirective: _angular_core.WritableSignal<boolean>;
|
|
6338
8210
|
ngAfterContentInit(): void;
|
|
6339
8211
|
private checkContentProjection;
|
|
8212
|
+
/**
|
|
8213
|
+
* Move real DOM focus to this option.
|
|
8214
|
+
*
|
|
8215
|
+
* Called by the parent listbox as the arrow keys move (#216). Focus lands on
|
|
8216
|
+
* the host, which is both the element carrying the roving tabindex and the
|
|
8217
|
+
* element `:host(:focus-visible)` draws the #215 focus ring on — the two
|
|
8218
|
+
* reasons the listbox moves focus rather than pointing at the option with
|
|
8219
|
+
* `aria-activedescendant`.
|
|
8220
|
+
*/
|
|
8221
|
+
focus(): void;
|
|
6340
8222
|
onClick(_event: Event): void;
|
|
8223
|
+
/**
|
|
8224
|
+
* The key is swallowed BEFORE the disabled guard, not after it.
|
|
8225
|
+
*
|
|
8226
|
+
* Declining to toggle is not the same as declining the key. Space scrolls the
|
|
8227
|
+
* page on any element that is neither a form control nor a scroller, and the
|
|
8228
|
+
* option host is neither — so bailing out first hands the browser a Space
|
|
8229
|
+
* with its default action intact, on an element the user deliberately focused
|
|
8230
|
+
* and got nothing from. Nothing upstream saves it either: the listbox's own
|
|
8231
|
+
* handler falls through for Space on purpose, so that the key reaches here.
|
|
8232
|
+
*
|
|
8233
|
+
* Only reachable under a parent `tn-selection-list` (#216), whose roving
|
|
8234
|
+
* tabindex is what makes a disabled option focusable — standalone it carries
|
|
8235
|
+
* no `tabindex`, so it cannot hold focus and this never fires on it.
|
|
8236
|
+
*
|
|
8237
|
+
* Which is safe only because the key has to have been pressed on the host
|
|
8238
|
+
* itself. A host listener hears whatever bubbles out of projected content, so
|
|
8239
|
+
* a consumer's `<input>` inside an option would otherwise have its Space
|
|
8240
|
+
* swallowed by the `preventDefault()` above — and, on an enabled option,
|
|
8241
|
+
* toggle the option as well as typing.
|
|
8242
|
+
*/
|
|
6341
8243
|
onKeydown(event: Event): void;
|
|
6342
8244
|
toggle(): void;
|
|
6343
8245
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnListOptionComponent, never>;
|
|
@@ -6353,21 +8255,238 @@ declare class TnSelectionListComponent implements ControlValueAccessor {
|
|
|
6353
8255
|
disabled: _angular_core.InputSignal<boolean>;
|
|
6354
8256
|
multiple: _angular_core.InputSignal<boolean>;
|
|
6355
8257
|
color: _angular_core.InputSignal<"primary" | "warn" | "accent">;
|
|
8258
|
+
/**
|
|
8259
|
+
* Explicit accessible name for the listbox. Inside a `tn-form-field` with a
|
|
8260
|
+
* label this is unnecessary — the field names the list automatically — but a
|
|
8261
|
+
* list with neither is announced as an unlabelled listbox.
|
|
8262
|
+
*
|
|
8263
|
+
* ALIASED to the attribute name, unlike the plain `ariaLabel` most controls in
|
|
8264
|
+
* this library take, and the divergence is forced rather than chosen: this
|
|
8265
|
+
* component's `role="listbox"` is on the HOST, so `<tn-selection-list
|
|
8266
|
+
* aria-label="Mailboxes">` is valid markup that named the list before this
|
|
8267
|
+
* input existed. The host binding below rewrites that attribute on every
|
|
8268
|
+
* change-detection pass, so an unaliased input would silently STRIP a name
|
|
8269
|
+
* that used to work. The alias makes the same markup set the input instead,
|
|
8270
|
+
* and the binding writes it straight back.
|
|
8271
|
+
*/
|
|
8272
|
+
ariaLabel: _angular_core.InputSignal<string | undefined>;
|
|
8273
|
+
/** Id of visible text naming the listbox. Wins over `ariaLabel` where it resolves. */
|
|
8274
|
+
ariaLabelledby: _angular_core.InputSignal<string | undefined>;
|
|
8275
|
+
/**
|
|
8276
|
+
* ARIA wiring from an enclosing `tn-form-field`, read for `labelledby` alone:
|
|
8277
|
+
* #235 is about the listbox having no accessible name, and the field's
|
|
8278
|
+
* `describedby`/`invalid`/`required` are a separate question this list has
|
|
8279
|
+
* never answered either way.
|
|
8280
|
+
*
|
|
8281
|
+
* Called with NO argument, so it reports the field's label id unconditioned.
|
|
8282
|
+
* Handing it the `ariaLabel` input would have it suppress the field itself —
|
|
8283
|
+
* on truthiness, so a whitespace-only label would cancel the field while being
|
|
8284
|
+
* dropped as no name, leaving nothing — and it would only do so while this
|
|
8285
|
+
* field is initialised after the one it reads, since a signal captured before
|
|
8286
|
+
* its own initialiser runs arrives as `undefined` and is swallowed by an
|
|
8287
|
+
* optional call. The suppression is applied below instead, where the rest of
|
|
8288
|
+
* the precedence is.
|
|
8289
|
+
*/
|
|
8290
|
+
private readonly fieldAria;
|
|
8291
|
+
/**
|
|
8292
|
+
* The `aria-label` this component supplies, or `null` when it supplies none.
|
|
8293
|
+
*
|
|
8294
|
+
* Blank is not a name: `aria-label=""` names the listbox as emptily as no
|
|
8295
|
+
* attribute at all, while satisfying axe's `aria-input-field-name` rule — a
|
|
8296
|
+
* green check on a control a screen reader announces as "listbox".
|
|
8297
|
+
*/
|
|
8298
|
+
private readonly resolvedAriaLabel;
|
|
8299
|
+
/**
|
|
8300
|
+
* The explicit `ariaLabelledby` input, blank normalised away, or `null`.
|
|
8301
|
+
*
|
|
8302
|
+
* Kept apart from the field's label because the two rank differently against
|
|
8303
|
+
* a name the consumer wrote on the host — see {@link hostAriaLabelledby}.
|
|
8304
|
+
*/
|
|
8305
|
+
private readonly explicitAriaLabelledby;
|
|
8306
|
+
private readonly hostElement;
|
|
8307
|
+
/**
|
|
8308
|
+
* The value this component last wrote for each naming attribute, so that it
|
|
8309
|
+
* can tell its own from the consumer's. See {@link claim}.
|
|
8310
|
+
*/
|
|
8311
|
+
private readonly written;
|
|
6356
8312
|
selectionChange: _angular_core.OutputEmitterRef<TnSelectionChange>;
|
|
6357
8313
|
options: _angular_core.Signal<readonly TnListOptionComponent[]>;
|
|
6358
8314
|
private formDisabled;
|
|
6359
8315
|
isDisabled: _angular_core.Signal<boolean>;
|
|
8316
|
+
/**
|
|
8317
|
+
* The option the user has moved to, and the slot it occupied at the time —
|
|
8318
|
+
* or `null` while they have not moved yet.
|
|
8319
|
+
*
|
|
8320
|
+
* Kept separate from `activeIndex` rather than seeded with a starting value,
|
|
8321
|
+
* because "where the user last was" and "where a user who has not arrived yet
|
|
8322
|
+
* would land" are different questions and only the second one should follow
|
|
8323
|
+
* the selection around. See `activeIndex`.
|
|
8324
|
+
*
|
|
8325
|
+
* The OPTION and not merely its index, because the options are content
|
|
8326
|
+
* children and the caller can add or remove them ABOVE the one the user is
|
|
8327
|
+
* standing on. An index survives that edit while quietly changing meaning —
|
|
8328
|
+
* it names whichever option shifted into the slot — so the tab stop and the
|
|
8329
|
+
* arrow keys' starting point both come away from the option holding focus,
|
|
8330
|
+
* and one ArrowDown lands two options from where the user is. A reference
|
|
8331
|
+
* cannot drift that way. The index rides along only as the fallback for the
|
|
8332
|
+
* one case a reference cannot answer: the option itself being removed.
|
|
8333
|
+
*/
|
|
8334
|
+
private visited;
|
|
8335
|
+
/**
|
|
8336
|
+
* Which option carries the listbox's single tab stop.
|
|
8337
|
+
*
|
|
8338
|
+
* Before the user has touched the list this tracks the first selected option,
|
|
8339
|
+
* which is what APG asks for — tabbing into a list that already has a
|
|
8340
|
+
* selection should land where the user left off rather than at the top. Once
|
|
8341
|
+
* they have moved, `visited` pins it and the selection no longer drags the
|
|
8342
|
+
* tab stop around underneath them.
|
|
8343
|
+
*
|
|
8344
|
+
* Resolved against the current options on every read rather than stored, so
|
|
8345
|
+
* that a list edited underneath the user still points at the option they were
|
|
8346
|
+
* on. Only once that option has left the list is there nothing to resolve,
|
|
8347
|
+
* and the remembered slot is the best answer left — clamped, because an index
|
|
8348
|
+
* held across a removal otherwise reads past the end of the array.
|
|
8349
|
+
*/
|
|
8350
|
+
private activeIndex;
|
|
8351
|
+
/**
|
|
8352
|
+
* The option host that currently holds DOM focus, or `null`.
|
|
8353
|
+
*
|
|
8354
|
+
* Held as an element rather than an index, because the whole point of it is
|
|
8355
|
+
* to outlive the option: an index still resolves after a removal, to whatever
|
|
8356
|
+
* option moved into that slot.
|
|
8357
|
+
*/
|
|
8358
|
+
private focusedOptionElement;
|
|
6360
8359
|
private onChange;
|
|
6361
8360
|
private onTouched;
|
|
6362
8361
|
constructor();
|
|
8362
|
+
/**
|
|
8363
|
+
* ArrowUp / ArrowDown / Home / End — unmodified, and pressed on an option
|
|
8364
|
+
* host — and deliberately nothing else.
|
|
8365
|
+
*
|
|
8366
|
+
* Space and Enter are absent because `tn-list-option` has handled them since
|
|
8367
|
+
* before this component had any keyboard handling at all, and its keydown
|
|
8368
|
+
* bubbles up to here. Toggling from both places would toggle twice — select
|
|
8369
|
+
* then immediately deselect — which reads as the key doing nothing rather
|
|
8370
|
+
* than as a bug, so it is worth stating why it is missing rather than leaving
|
|
8371
|
+
* a later reader to add it.
|
|
8372
|
+
*
|
|
8373
|
+
* Every other key is left alone, Tab included: `preventDefault()` on an
|
|
8374
|
+
* unrecognised key is how a widget traps a keyboard user inside it.
|
|
8375
|
+
*
|
|
8376
|
+
* Navigation is not gated on `isDisabled()`, because moving focus selects
|
|
8377
|
+
* nothing: a disabled list a user can still read through is the same
|
|
8378
|
+
* reasoning that has the arrow keys visit disabled options at all.
|
|
8379
|
+
*
|
|
8380
|
+
* That is a statement about NAVIGATION only, and deliberately not the wider
|
|
8381
|
+
* claim that a disabled list can be toggled. It cannot: `isDisabled()` is
|
|
8382
|
+
* pushed onto every option's `listDisabled` by the effect in the constructor
|
|
8383
|
+
* (#221), so the option's own guard refuses the toggle whichever route asked
|
|
8384
|
+
* for it — mouse, Space, Enter or a reactive form. What a disabled list still
|
|
8385
|
+
* allows is moving through it, which selects nothing.
|
|
8386
|
+
*
|
|
8387
|
+
* Only keys pressed ON an option host are the listbox's. The handler is on
|
|
8388
|
+
* the host and hears everything that bubbles through it, so without that test
|
|
8389
|
+
* a consumer who projects a focusable control into an option — a text input,
|
|
8390
|
+
* a slider — would have Home and End taken off its caret and the arrows taken
|
|
8391
|
+
* off its value, and get a `preventDefault()` for it. Nothing in this library
|
|
8392
|
+
* projects such a control today, which is why this is a target check rather
|
|
8393
|
+
* than a redesign.
|
|
8394
|
+
*/
|
|
8395
|
+
onKeydown(event: KeyboardEvent): void;
|
|
8396
|
+
/**
|
|
8397
|
+
* Keep the tab stop under whichever option actually holds focus.
|
|
8398
|
+
*
|
|
8399
|
+
* Covers the routes into the list that are not the arrow keys — a click, and
|
|
8400
|
+
* a Tab that lands here — so that leaving the list and coming back returns to
|
|
8401
|
+
* the option the user was last on, rather than to the one the arrow keys
|
|
8402
|
+
* happened to leave the index at.
|
|
8403
|
+
*
|
|
8404
|
+
* `contains` rather than an identity check on the target, because focus can
|
|
8405
|
+
* land on something projected into the option rather than on the option host.
|
|
8406
|
+
*/
|
|
8407
|
+
onFocusIn(event: FocusEvent): void;
|
|
8408
|
+
/**
|
|
8409
|
+
* Forget the focused option once focus leaves it under its own steam.
|
|
8410
|
+
*
|
|
8411
|
+
* Guarded on the option still being in the document, because a `focusout`
|
|
8412
|
+
* fired BY a removal — Firefox fires one, Chrome does not — is exactly the
|
|
8413
|
+
* case the restore in the constructor exists for, and clearing on it would
|
|
8414
|
+
* defeat that restore in one browser and not the other.
|
|
8415
|
+
*/
|
|
8416
|
+
onFocusOut(event: FocusEvent): void;
|
|
8417
|
+
/**
|
|
8418
|
+
* The `aria-label` the host should carry.
|
|
8419
|
+
*
|
|
8420
|
+
* A METHOD rather than a signal, and that is the load-bearing part of this
|
|
8421
|
+
* whole arrangement: a host binding re-evaluates on every change-detection
|
|
8422
|
+
* pass, while a `computed` re-runs only when a signal it read has changed.
|
|
8423
|
+
* These two answers depend on the host's CURRENT attributes, which are not
|
|
8424
|
+
* signals — a consumer's `[attr.aria-label]` bound in the parent template
|
|
8425
|
+
* never reaches an input and never notifies anything. Asked once per pass,
|
|
8426
|
+
* the answer stays true; asked from a `computed`, it goes stale the moment
|
|
8427
|
+
* the consumer's binding changes, and the list is left announcing the wrong
|
|
8428
|
+
* name or no name at all.
|
|
8429
|
+
*/
|
|
8430
|
+
protected hostAriaLabel(): string | null;
|
|
8431
|
+
/**
|
|
8432
|
+
* The `aria-labelledby` the host should carry — and the one place the naming
|
|
8433
|
+
* precedence is decided.
|
|
8434
|
+
*
|
|
8435
|
+
* Most specific first: the `ariaLabelledby` input, then any name the consumer
|
|
8436
|
+
* put on the host by another route, then the enclosing `tn-form-field`'s
|
|
8437
|
+
* label. The field comes last because it is chrome the consumer did not write
|
|
8438
|
+
* on this element, and it is WITHHELD rather than rendered alongside: ARIA
|
|
8439
|
+
* prefers `aria-labelledby` wherever it resolves, so a field reference emitted
|
|
8440
|
+
* beside a consumer's `aria-label` does not merely coexist with it — it
|
|
8441
|
+
* outranks it, and the list announces the field's label instead.
|
|
8442
|
+
*
|
|
8443
|
+
* With none of the three, a list stays unnamed — deliberately: a generic
|
|
8444
|
+
* fallback ("List") would satisfy axe while announcing nothing the user can
|
|
8445
|
+
* act on, and only the consumer knows what this list holds.
|
|
8446
|
+
*/
|
|
8447
|
+
protected hostAriaLabelledby(): string | null;
|
|
8448
|
+
/** Whether the host already carries a non-blank name this component did not write. */
|
|
8449
|
+
private namedByConsumer;
|
|
8450
|
+
/**
|
|
8451
|
+
* What to bind `attribute` to: this component's own `value` where it has one,
|
|
8452
|
+
* and otherwise whatever is already on the host — so that a name the consumer
|
|
8453
|
+
* set is preserved rather than overwritten by the `null` that means "nothing
|
|
8454
|
+
* to say".
|
|
8455
|
+
*
|
|
8456
|
+
* That distinction is the reason this exists. `[attr.x]="null"` REMOVES the
|
|
8457
|
+
* attribute, and the role is on the HOST here, so `<tn-selection-list
|
|
8458
|
+
* aria-label="…">` and `[attr.aria-labelledby]="…"` are valid markup that
|
|
8459
|
+
* named this list before it had any naming input at all. Measured on Angular
|
|
8460
|
+
* 21, a plain host binding of these attributes ran after the parent's and left
|
|
8461
|
+
* the element with NEITHER — named to unnamed, silently, with the parent's
|
|
8462
|
+
* value reappearing only on a later pass and only for the one whose bound
|
|
8463
|
+
* value had changed.
|
|
8464
|
+
*
|
|
8465
|
+
* Ownership is tracked by value rather than by a flag, so it also lapses
|
|
8466
|
+
* correctly: an attribute this component wrote and something else has since
|
|
8467
|
+
* changed is no longer this component's to rewrite or take away.
|
|
8468
|
+
*/
|
|
8469
|
+
private claim;
|
|
6363
8470
|
writeValue(value: unknown[]): void;
|
|
6364
8471
|
registerOnChange(fn: (value: unknown[]) => void): void;
|
|
6365
8472
|
registerOnTouched(fn: () => void): void;
|
|
8473
|
+
/**
|
|
8474
|
+
* Records the form's state and stops there — the effect in the constructor is
|
|
8475
|
+
* what reaches the options, via `isDisabled()`.
|
|
8476
|
+
*
|
|
8477
|
+
* It used to also write each `option.internalDisabled` directly, which is the
|
|
8478
|
+
* only reason this route enforced anything while `[disabled]` enforced
|
|
8479
|
+
* nothing. Two problems, both fixed by routing it through the same signal the
|
|
8480
|
+
* input uses: the two paths could disagree, and `internalDisabled` is the
|
|
8481
|
+
* slot an option's own `[disabled]` input falls back to — so `control.enable()`
|
|
8482
|
+
* wrote `false` over an option the consumer had disabled independently, and
|
|
8483
|
+
* never gave it back.
|
|
8484
|
+
*/
|
|
6366
8485
|
setDisabledState(isDisabled: boolean): void;
|
|
6367
8486
|
onOptionSelectionChange(): void;
|
|
6368
8487
|
get selectedOptions(): TnListOptionComponent[];
|
|
6369
8488
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnSelectionListComponent, never>;
|
|
6370
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnSelectionListComponent, "tn-selection-list", never, { "dense": { "alias": "dense"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; }, { "selectionChange": "selectionChange"; }, ["options"], ["*"], true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
|
|
8489
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnSelectionListComponent, "tn-selection-list", never, { "dense": { "alias": "dense"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; }, { "selectionChange": "selectionChange"; }, ["options"], ["*"], true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
|
|
6371
8490
|
}
|
|
6372
8491
|
|
|
6373
8492
|
declare class TnHeaderCellDefDirective {
|
|
@@ -6392,6 +8511,26 @@ declare class TnTableColumnDirective {
|
|
|
6392
8511
|
* overrides the card field label. Falls back to the column `name` when unset.
|
|
6393
8512
|
*/
|
|
6394
8513
|
label: _angular_core.InputSignal<string | undefined>;
|
|
8514
|
+
/**
|
|
8515
|
+
* Renders the header label for screen readers only, leaving the header cell
|
|
8516
|
+
* visually blank. For a column whose purpose is obvious from its contents and
|
|
8517
|
+
* whose heading would only add noise — a row-actions or icon column.
|
|
8518
|
+
*
|
|
8519
|
+
* A blank header is not the same thing as an unlabelled one: `<th>` with no
|
|
8520
|
+
* text at all fails axe's `empty-table-header`, and a screen-reader user
|
|
8521
|
+
* moving across the header row hears nothing where a column exists (#246). So
|
|
8522
|
+
* the label is still rendered, inside the table's own `.cdk-visually-hidden`
|
|
8523
|
+
* — the same treatment the built-in `__expand` and row-actions headers get.
|
|
8524
|
+
*
|
|
8525
|
+
* The text is `label`, falling back to the column `name`. A `tnHeaderCellDef`
|
|
8526
|
+
* template is NOT rendered when this is set: the point is that nothing shows,
|
|
8527
|
+
* and the class that hides it is scoped to the table's own view, so a
|
|
8528
|
+
* consumer's projected markup could not use it anyway.
|
|
8529
|
+
*
|
|
8530
|
+
* Header-only. Card mode has no header row; use `cardHidden` to keep a column
|
|
8531
|
+
* off the card.
|
|
8532
|
+
*/
|
|
8533
|
+
hideLabel: _angular_core.InputSignal<boolean>;
|
|
6395
8534
|
/**
|
|
6396
8535
|
* Relative importance of this column in card mode (see `mobileLayout` on
|
|
6397
8536
|
* `tn-table`). Higher numbers render first; fields ranked beyond
|
|
@@ -6428,7 +8567,7 @@ declare class TnTableColumnDirective {
|
|
|
6428
8567
|
headerTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
|
|
6429
8568
|
cellTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
|
|
6430
8569
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTableColumnDirective, never>;
|
|
6431
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnTableColumnDirective, "[tnColumnDef]", ["tnColumnDef"], { "name": { "alias": "tnColumnDef"; "required": true; "isSignal": true; }; "sortable": { "alias": "sortable"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "cardPriority": { "alias": "cardPriority"; "required": false; "isSignal": true; }; "cardTitle": { "alias": "cardTitle"; "required": false; "isSignal": true; }; "cardHidden": { "alias": "cardHidden"; "required": false; "isSignal": true; }; "cardLabel": { "alias": "cardLabel"; "required": false; "isSignal": true; }; }, {}, ["headerTemplate", "cellTemplate"], never, true, never>;
|
|
8570
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnTableColumnDirective, "[tnColumnDef]", ["tnColumnDef"], { "name": { "alias": "tnColumnDef"; "required": true; "isSignal": true; }; "sortable": { "alias": "sortable"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "cardPriority": { "alias": "cardPriority"; "required": false; "isSignal": true; }; "cardTitle": { "alias": "cardTitle"; "required": false; "isSignal": true; }; "cardHidden": { "alias": "cardHidden"; "required": false; "isSignal": true; }; "cardLabel": { "alias": "cardLabel"; "required": false; "isSignal": true; }; }, {}, ["headerTemplate", "cellTemplate"], never, true, never>;
|
|
6432
8571
|
}
|
|
6433
8572
|
/**
|
|
6434
8573
|
* Directive to define the expandable detail row template.
|
|
@@ -6475,6 +8614,25 @@ declare class TnRowActionsDefDirective {
|
|
|
6475
8614
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnRowActionsDefDirective, "[tnRowActionsDef]", never, {}, {}, never, never, true, never>;
|
|
6476
8615
|
}
|
|
6477
8616
|
|
|
8617
|
+
/**
|
|
8618
|
+
* The name the table's scroll region falls back to when the consumer has not
|
|
8619
|
+
* said what the table holds (#270).
|
|
8620
|
+
*
|
|
8621
|
+
* A focusable element with no accessible name is announced as a bare "group",
|
|
8622
|
+
* which tells a listener that something has been reached and nothing about what
|
|
8623
|
+
* it is. "Table" is a poor name and is deliberately the fallback rather than
|
|
8624
|
+
* the expectation — `scrollRegionAriaLabel` is how a consumer says something
|
|
8625
|
+
* useful, and it is the one input on this component whose default is worth
|
|
8626
|
+
* overriding on sight.
|
|
8627
|
+
*
|
|
8628
|
+
* No dev-mode warning goes with it, unlike `tnAccessibleName`'s fallbacks: this
|
|
8629
|
+
* name is rendered only on a table that is WIDER THAN ITS CONTAINER, which
|
|
8630
|
+
* depends on the consumer's layout rather than on their markup, so the warning
|
|
8631
|
+
* would fire on a viewport rather than on a mistake.
|
|
8632
|
+
*
|
|
8633
|
+
* Exported so specs assert against it by name rather than by a copied literal.
|
|
8634
|
+
*/
|
|
8635
|
+
declare const TN_TABLE_SCROLL_REGION_LABEL = "Table";
|
|
6478
8636
|
interface TnTableDataSource<T = unknown> {
|
|
6479
8637
|
data?: T[];
|
|
6480
8638
|
connect?(): T[];
|
|
@@ -6511,7 +8669,46 @@ interface TnSortEvent {
|
|
|
6511
8669
|
* Above the breakpoint both modes render the regular table.
|
|
6512
8670
|
*/
|
|
6513
8671
|
type TnTableMobileLayout = 'cards' | 'scroll';
|
|
8672
|
+
/**
|
|
8673
|
+
* Chrome copy `tn-table` renders itself. These were baked into the template as English literals,
|
|
8674
|
+
* which a consumer could not translate at all — there was no input to bind. Provide
|
|
8675
|
+
* {@link TN_TABLE_LABELS} at the app root to wire them to an i18n service.
|
|
8676
|
+
*/
|
|
8677
|
+
interface TnTableLabels {
|
|
8678
|
+
/** Accessible name for the card-mode sort control. */
|
|
8679
|
+
sortBy: string;
|
|
8680
|
+
/** Card-mode sort option that clears the sort. */
|
|
8681
|
+
unsorted: string;
|
|
8682
|
+
/** Toggle that reveals the columns card mode collapsed. */
|
|
8683
|
+
moreFields: string;
|
|
8684
|
+
/** Card-mode button that opens a row's detail panel. */
|
|
8685
|
+
details: string;
|
|
8686
|
+
/**
|
|
8687
|
+
* Card-mode direction toggle while unsorted or sorted descending — the label names the
|
|
8688
|
+
* ACTION, and `toggleSortDirection()` starts at ascending from both of those states.
|
|
8689
|
+
*/
|
|
8690
|
+
sortAscending: string;
|
|
8691
|
+
/** Card-mode direction toggle while sorted ascending. */
|
|
8692
|
+
sortDescending: string;
|
|
8693
|
+
/** Visually-hidden name for the row-expand COLUMN's header cell, not the per-row control. */
|
|
8694
|
+
expand: string;
|
|
8695
|
+
/** Per-row expand control while its detail row is collapsed. */
|
|
8696
|
+
expandRow: string;
|
|
8697
|
+
/** Per-row expand control while its detail row is open. */
|
|
8698
|
+
collapseRow: string;
|
|
8699
|
+
/** Visually-hidden name for the row actions column. */
|
|
8700
|
+
actions: string;
|
|
8701
|
+
}
|
|
8702
|
+
/** English defaults used when no `TN_TABLE_LABELS` provider is registered. */
|
|
8703
|
+
declare const TN_TABLE_DEFAULT_LABELS: TnTableLabels;
|
|
8704
|
+
/**
|
|
8705
|
+
* DI token for app-wide table chrome labels. Provide either a static object or a
|
|
8706
|
+
* `Signal<TnTableLabels>` — the latter lets every table react to language changes when the
|
|
8707
|
+
* consumer wires it up to an i18n service.
|
|
8708
|
+
*/
|
|
8709
|
+
declare const TN_TABLE_LABELS: InjectionToken<TnTableLabels | Signal<TnTableLabels>>;
|
|
6514
8710
|
declare class TnTableComponent<T = unknown> implements OnInit {
|
|
8711
|
+
protected readonly labels: Signal<TnTableLabels>;
|
|
6515
8712
|
private destroyRef;
|
|
6516
8713
|
private elementRef;
|
|
6517
8714
|
private injector;
|
|
@@ -6523,6 +8720,39 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6523
8720
|
* loads — was dropped to `<body>` and had to tab back from the top of the document.
|
|
6524
8721
|
*/
|
|
6525
8722
|
private focusBeforeLoading;
|
|
8723
|
+
/**
|
|
8724
|
+
* Whether the host carries a tab stop, `role="group"` and a name because it
|
|
8725
|
+
* is currently scrolling (#270).
|
|
8726
|
+
*
|
|
8727
|
+
* `:host` is `overflow-x: auto` — the stylesheet says why the scrollport is
|
|
8728
|
+
* the host and not `.tn-table__table` — so a table wider than its container
|
|
8729
|
+
* scrolls HERE, and axe's `scrollable-region-focusable` reports a scroll
|
|
8730
|
+
* container that is neither in the tab order nor holds anything that is.
|
|
8731
|
+
*
|
|
8732
|
+
* Which this table is depends entirely on how it was configured: sortable
|
|
8733
|
+
* headers and clickable rows are `tabindex="0"`, so a table with either
|
|
8734
|
+
* satisfies the rule through its content, and a plain read-only one — the
|
|
8735
|
+
* default of every input on this component — satisfies nothing and leaves its
|
|
8736
|
+
* trailing columns unreachable from a keyboard.
|
|
8737
|
+
*
|
|
8738
|
+
* Gated on the measurement rather than on `isScrollMode()`: that class says
|
|
8739
|
+
* which LAYOUT the container's width selected, and this asks whether the
|
|
8740
|
+
* content actually exceeds the box, which is a different question and the one
|
|
8741
|
+
* axe asks. The measurement, the observers behind it and the rule that holds
|
|
8742
|
+
* the answer true while the host has focus are `tnScrollableRegion`'s.
|
|
8743
|
+
*
|
|
8744
|
+
* A field initializer rather than the constructor, because it registers an
|
|
8745
|
+
* `effect` and so needs an injection context.
|
|
8746
|
+
*/
|
|
8747
|
+
protected scrollKeyboardReachable: Signal<boolean>;
|
|
8748
|
+
/**
|
|
8749
|
+
* The scroll region's name, falling back when a consumer passes whitespace.
|
|
8750
|
+
*
|
|
8751
|
+
* Blank is not a name: an `aria-label=" "` names the group as emptily as no
|
|
8752
|
+
* label at all, and axe agrees — the same rule `tnAccessibleName` applies to
|
|
8753
|
+
* every other name in this library.
|
|
8754
|
+
*/
|
|
8755
|
+
protected resolvedScrollRegionLabel: Signal<string>;
|
|
6526
8756
|
dataSource: _angular_core.InputSignal<TnTableDataSource<T> | T[]>;
|
|
6527
8757
|
displayedColumns: _angular_core.InputSignal<string[]>;
|
|
6528
8758
|
trackBy: _angular_core.InputSignal<((index: number, item: T) => unknown) | undefined>;
|
|
@@ -6534,6 +8764,17 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6534
8764
|
*/
|
|
6535
8765
|
emptyDescription: _angular_core.InputSignal<string>;
|
|
6536
8766
|
emptyIcon: _angular_core.InputSignal<string>;
|
|
8767
|
+
/**
|
|
8768
|
+
* Accessible name for the table's own scroll region, used only while the host
|
|
8769
|
+
* actually scrolls — see `scrollKeyboardReachable` and
|
|
8770
|
+
* `TN_TABLE_SCROLL_REGION_LABEL`.
|
|
8771
|
+
*
|
|
8772
|
+
* Set it to say what the table holds ("Storage pools"). It names the SCROLL
|
|
8773
|
+
* REGION rather than the table: a table's own structure is announced from its
|
|
8774
|
+
* rows and headers, and this is the box around it that a keyboard user stands
|
|
8775
|
+
* on to scroll sideways.
|
|
8776
|
+
*/
|
|
8777
|
+
scrollRegionAriaLabel: _angular_core.InputSignal<string>;
|
|
6537
8778
|
selectable: _angular_core.InputSignal<boolean>;
|
|
6538
8779
|
expandable: _angular_core.InputSignal<boolean>;
|
|
6539
8780
|
bordered: _angular_core.InputSignal<boolean>;
|
|
@@ -6620,6 +8861,10 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6620
8861
|
* rows the `isRowExpandable` predicate rejects are unaffected, since `toggleRowExpansion` gates
|
|
6621
8862
|
* on it. `rowClick` still emits, so a consumer can both expand and react to the click.
|
|
6622
8863
|
*
|
|
8864
|
+
* In table mode the expanded state is announced by the chevron, not by the row: `aria-expanded`
|
|
8865
|
+
* is a `treegrid`-row attribute and a plain `table` row cannot hold it (#246). The row points at
|
|
8866
|
+
* the open panel with `aria-controls`, which every role permits.
|
|
8867
|
+
*
|
|
6623
8868
|
* Applies in card mode too, where activating the card toggles its detail section. Both controls
|
|
6624
8869
|
* report the state: the card carries `aria-expanded` while it is the trigger (`listitem` does
|
|
6625
8870
|
* permit it, unlike `aria-selected`), and the "Details" button carries it unconditionally,
|
|
@@ -6670,7 +8915,7 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6670
8915
|
*/
|
|
6671
8916
|
minWidth: _angular_core.InputSignal<string>;
|
|
6672
8917
|
/** The floor actually applied to the table — explicit if given, else derived, else none. */
|
|
6673
|
-
protected readonly resolvedMinWidth:
|
|
8918
|
+
protected readonly resolvedMinWidth: Signal<string | null>;
|
|
6674
8919
|
/**
|
|
6675
8920
|
* How the table adapts when its container is narrower than `cardBreakpoint`.
|
|
6676
8921
|
* See {@link TnTableMobileLayout}. Defaults to `scroll`, which preserves the
|
|
@@ -6708,9 +8953,9 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6708
8953
|
* button inside the row, as the file picker does for entering directories.
|
|
6709
8954
|
*/
|
|
6710
8955
|
rowDoubleClick: _angular_core.OutputEmitterRef<T>;
|
|
6711
|
-
columnDefs:
|
|
6712
|
-
detailRowDef:
|
|
6713
|
-
rowActionsDef:
|
|
8956
|
+
columnDefs: Signal<readonly TnTableColumnDirective[]>;
|
|
8957
|
+
detailRowDef: Signal<TnDetailRowDefDirective | undefined>;
|
|
8958
|
+
rowActionsDef: Signal<TnRowActionsDefDirective | undefined>;
|
|
6714
8959
|
/** Observed host width in px; drives the switch into card mode. */
|
|
6715
8960
|
private containerWidth;
|
|
6716
8961
|
private resizeObserver?;
|
|
@@ -6748,21 +8993,50 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6748
8993
|
private measureContainer;
|
|
6749
8994
|
ngOnInit(): void;
|
|
6750
8995
|
/** True when the layout should collapse rows into cards. */
|
|
6751
|
-
isCardMode:
|
|
8996
|
+
isCardMode: Signal<boolean>;
|
|
6752
8997
|
/** True when the layout should keep the table but pin edge columns and scroll. */
|
|
6753
|
-
isScrollMode:
|
|
6754
|
-
data:
|
|
6755
|
-
effectiveDisplayedColumns:
|
|
8998
|
+
isScrollMode: Signal<boolean>;
|
|
8999
|
+
data: Signal<T[]>;
|
|
9000
|
+
effectiveDisplayedColumns: Signal<string[]>;
|
|
6756
9001
|
/**
|
|
6757
9002
|
* Every column the table actually renders, including the trailing actions column, which
|
|
6758
9003
|
* `effectiveDisplayedColumns` does not track because it comes from a content template rather
|
|
6759
9004
|
* than `displayedColumns`. Used for the detail row's `colspan`, so a detail row spans the full
|
|
6760
9005
|
* width no matter which structural columns are on.
|
|
6761
9006
|
*/
|
|
6762
|
-
totalColumnCount:
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
9007
|
+
totalColumnCount: Signal<number>;
|
|
9008
|
+
/**
|
|
9009
|
+
* How many rows a select-all can actually select.
|
|
9010
|
+
*
|
|
9011
|
+
* `SelectionModel` stores selections in a `Set`, so `selection.selected.length`
|
|
9012
|
+
* counts DISTINCT rows while `data().length` counts array entries. A `dataSource`
|
|
9013
|
+
* holding the same row reference twice makes the two disagree, and every comparison
|
|
9014
|
+
* of a selection count against a row count has to use this one to stay honest —
|
|
9015
|
+
* see {@link isAllSelected}.
|
|
9016
|
+
*/
|
|
9017
|
+
private distinctRowCount;
|
|
9018
|
+
isAllSelected: Signal<boolean>;
|
|
9019
|
+
isIndeterminate: Signal<boolean>;
|
|
9020
|
+
/**
|
|
9021
|
+
* Whether the select-all control has anything to act on.
|
|
9022
|
+
*
|
|
9023
|
+
* Disabling it on an empty table is a correctness guard, not a nicety. Since #236
|
|
9024
|
+
* the checkbox is a real control the user can click, and its `checked` binding is
|
|
9025
|
+
* one-way: the DOM follows `isAllSelected()`, and Angular only writes the attribute
|
|
9026
|
+
* back when that value CHANGES. With no rows, `isAllSelected()` is pinned false —
|
|
9027
|
+
* selecting nothing leaves the count at zero — so a click would flip the input in
|
|
9028
|
+
* the DOM, change no bound value, and leave a checked-looking box over an empty
|
|
9029
|
+
* selection until something else re-rendered it.
|
|
9030
|
+
*
|
|
9031
|
+
* The hit area around the checkbox stands down for the same reason, so the two
|
|
9032
|
+
* cannot disagree about whether the control is live.
|
|
9033
|
+
*
|
|
9034
|
+
* An empty table is the only case that has to be disabled rather than fixed: a
|
|
9035
|
+
* repeated row reference produces the same DOM-versus-model divergence, and
|
|
9036
|
+
* {@link distinctRowCount} resolves that one by making the control work.
|
|
9037
|
+
*/
|
|
9038
|
+
canSelectAll: Signal<boolean>;
|
|
9039
|
+
trackByFn: Signal<(index: number, item: T) => unknown>;
|
|
6766
9040
|
onSortClick(column: string): void;
|
|
6767
9041
|
isSorted(column: string): boolean;
|
|
6768
9042
|
/**
|
|
@@ -6776,11 +9050,17 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6776
9050
|
toggleRowExpansion(row: T): void;
|
|
6777
9051
|
isRowExpanded(row: T): boolean;
|
|
6778
9052
|
/**
|
|
6779
|
-
* Whether the row element itself acts as the expand/collapse control
|
|
6780
|
-
*
|
|
6781
|
-
*
|
|
6782
|
-
*
|
|
6783
|
-
*
|
|
9053
|
+
* Whether the row element itself acts as the expand/collapse control. Requires `clickable` —
|
|
9054
|
+
* without it the row isn't activatable and {@link onRowClick} returns before toggling anything.
|
|
9055
|
+
*
|
|
9056
|
+
* A table row does NOT get `aria-expanded` from this. That attribute is only supported on a
|
|
9057
|
+
* `treegrid` row; on a plain `table` it is ignored, so the state it seemed to publish reached
|
|
9058
|
+
* nobody (#246). The chevron in the `__expand` cell carries it instead, on a `button`, where it
|
|
9059
|
+
* is valid and where a screen-reader user lands by tabbing. What the row does take from this is
|
|
9060
|
+
* `aria-controls`, which is global rather than role-conditional.
|
|
9061
|
+
*
|
|
9062
|
+
* Card mode is the case where the trigger element CAN hold the state — see
|
|
9063
|
+
* {@link isCardExpandTrigger}.
|
|
6784
9064
|
*/
|
|
6785
9065
|
isRowExpandTrigger(row: T): boolean;
|
|
6786
9066
|
/**
|
|
@@ -6788,8 +9068,8 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6788
9068
|
* template. Card mode renders no expansion affordance at all without one, so `aria-expanded`
|
|
6789
9069
|
* on the card would advertise a state change that produces nothing.
|
|
6790
9070
|
*
|
|
6791
|
-
*
|
|
6792
|
-
*
|
|
9071
|
+
* `listitem` permits `aria-expanded`, which is what lets the card hold the state its table-row
|
|
9072
|
+
* equivalent cannot.
|
|
6793
9073
|
*/
|
|
6794
9074
|
isCardExpandTrigger(row: T): boolean;
|
|
6795
9075
|
/** DOM id for a row's detail panel, so the expand trigger can point `aria-controls` at it. */
|
|
@@ -6855,14 +9135,55 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6855
9135
|
*/
|
|
6856
9136
|
onSortHeaderClick(column: string, event: Event): void;
|
|
6857
9137
|
/**
|
|
6858
|
-
* Keyboard counterpart of {@link onSortHeaderClick}.
|
|
9138
|
+
* Keyboard counterpart of {@link onSortHeaderClick}.
|
|
9139
|
+
*
|
|
9140
|
+
* Takes `Event` rather than `KeyboardEvent` because Angular types `$event` as `Event` for
|
|
9141
|
+
* the `keydown.enter` / `keydown.space` pseudo-events; narrowing happens here.
|
|
9142
|
+
*/
|
|
9143
|
+
onSortHeaderKeydown(column: string, event: Event): void;
|
|
9144
|
+
/** Handles the card-mode sort `<select>` change. */
|
|
9145
|
+
onSortSelectChange(event: Event): void;
|
|
9146
|
+
/**
|
|
9147
|
+
* Whether an event started inside a selection checkbox rather than on the hit area
|
|
9148
|
+
* around it.
|
|
9149
|
+
*
|
|
9150
|
+
* Matched on the component's host class rather than through
|
|
9151
|
+
* {@link isControlTarget}, because the element clicked is usually neither the input
|
|
9152
|
+
* nor the host: `tn-checkbox` renders a `<label>` wrapping the input and its
|
|
9153
|
+
* checkmark, and a click on the checkmark activates the input as the label's default
|
|
9154
|
+
* action. `closest('input')` says no to that click, and the toggle would then happen
|
|
9155
|
+
* twice — once here, once from the label's own activation.
|
|
9156
|
+
*
|
|
9157
|
+
* @param event The DOM event; its `currentTarget` is the hit area.
|
|
9158
|
+
*/
|
|
9159
|
+
private isSelectionCheckboxTarget;
|
|
9160
|
+
/**
|
|
9161
|
+
* Click on the hit area around a select-all checkbox, in either layout.
|
|
9162
|
+
*
|
|
9163
|
+
* @param event The originating click.
|
|
9164
|
+
*/
|
|
9165
|
+
onSelectAllHitAreaClick(event: Event): void;
|
|
9166
|
+
/**
|
|
9167
|
+
* Enter on the select-all checkbox.
|
|
9168
|
+
*
|
|
9169
|
+
* A native checkbox answers to Space and not to Enter, and the `<th>` this replaced
|
|
9170
|
+
* handled both. Bound on the header's checkbox only, which is where that behaviour
|
|
9171
|
+
* existed — card mode's select-all never had it.
|
|
9172
|
+
*
|
|
9173
|
+
* @param event The originating keydown; typed as `Event` because Angular types
|
|
9174
|
+
* `$event` that way for the `keydown.enter` pseudo-event.
|
|
9175
|
+
*/
|
|
9176
|
+
onSelectAllEnter(event: Event): void;
|
|
9177
|
+
/**
|
|
9178
|
+
* Click on the hit area around a row's selection checkbox, in either layout.
|
|
6859
9179
|
*
|
|
6860
|
-
*
|
|
6861
|
-
*
|
|
9180
|
+
* Propagation stops whichever path activates the checkbox: a row is clickable and a
|
|
9181
|
+
* card is activatable, and selecting is not activating.
|
|
9182
|
+
*
|
|
9183
|
+
* @param event The originating click.
|
|
9184
|
+
* @param row The row the cell or card belongs to.
|
|
6862
9185
|
*/
|
|
6863
|
-
|
|
6864
|
-
/** Handles the card-mode sort `<select>` change. */
|
|
6865
|
-
onSortSelectChange(event: Event): void;
|
|
9186
|
+
onRowSelectHitAreaClick(event: Event, row: T): void;
|
|
6866
9187
|
toggleSelectAll(): void;
|
|
6867
9188
|
toggleRowSelection(row: T): void;
|
|
6868
9189
|
isRowSelected(row: T): boolean;
|
|
@@ -6873,17 +9194,17 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6873
9194
|
* most prominent slot. Falls back to the first displayed column only when every
|
|
6874
9195
|
* column is hidden, since a card still needs a title.
|
|
6875
9196
|
*/
|
|
6876
|
-
cardTitleColumn:
|
|
9197
|
+
cardTitleColumn: Signal<string>;
|
|
6877
9198
|
/**
|
|
6878
9199
|
* Columns rendered as label/value fields in a card, ordered by descending
|
|
6879
9200
|
* `cardPriority` (ties keep `displayedColumns` order). Excludes the title column
|
|
6880
9201
|
* and any `cardHidden` columns.
|
|
6881
9202
|
*/
|
|
6882
|
-
cardFieldColumns:
|
|
9203
|
+
cardFieldColumns: Signal<string[]>;
|
|
6883
9204
|
/** Fields shown directly on the card (up to `cardPrimaryCount`). */
|
|
6884
|
-
cardPrimaryColumns:
|
|
9205
|
+
cardPrimaryColumns: Signal<string[]>;
|
|
6885
9206
|
/** Fields tucked behind the "More fields" disclosure. */
|
|
6886
|
-
cardSecondaryColumns:
|
|
9207
|
+
cardSecondaryColumns: Signal<string[]>;
|
|
6887
9208
|
/**
|
|
6888
9209
|
* Displayed columns that are sortable — populates the card-mode sort menu.
|
|
6889
9210
|
*
|
|
@@ -6897,7 +9218,7 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6897
9218
|
* The title column stays eligible: it is excluded from the *fields*, but it is
|
|
6898
9219
|
* displayed, so sorting by it is meaningful.
|
|
6899
9220
|
*/
|
|
6900
|
-
sortableColumns:
|
|
9221
|
+
sortableColumns: Signal<string[]>;
|
|
6901
9222
|
/**
|
|
6902
9223
|
* Whether the active sort column is one the table can actually sort by.
|
|
6903
9224
|
*
|
|
@@ -6907,7 +9228,7 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6907
9228
|
* non-sortable header does nothing, so the direction toggle is hidden — and
|
|
6908
9229
|
* {@link toggleSortDirection} refuses — for the same column.
|
|
6909
9230
|
*/
|
|
6910
|
-
protected readonly canSortActiveColumn:
|
|
9231
|
+
protected readonly canSortActiveColumn: Signal<boolean>;
|
|
6911
9232
|
/**
|
|
6912
9233
|
* Sets (or clears, when passed `''`) the active sort column for card mode.
|
|
6913
9234
|
* Switching columns resets to ascending, and clearing emits an empty `column` —
|
|
@@ -6931,7 +9252,7 @@ declare class TnTableComponent<T = unknown> implements OnInit {
|
|
|
6931
9252
|
getCardLabel(column: string): string;
|
|
6932
9253
|
getCellValue(row: T, column: string): unknown;
|
|
6933
9254
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTableComponent<any>, never>;
|
|
6934
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTableComponent<any>, "tn-table", never, { "dataSource": { "alias": "dataSource"; "required": false; "isSignal": true; }; "displayedColumns": { "alias": "displayedColumns"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "emptyDescription": { "alias": "emptyDescription"; "required": false; "isSignal": true; }; "emptyIcon": { "alias": "emptyIcon"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "isRowExpandable": { "alias": "isRowExpandable"; "required": false; "isSignal": true; }; "activeRow": { "alias": "activeRow"; "required": false; "isSignal": true; }; "activeWhen": { "alias": "activeWhen"; "required": false; "isSignal": true; }; "activeBg": { "alias": "activeBg"; "required": false; "isSignal": true; }; "activeIndicator": { "alias": "activeIndicator"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "loadingMessage": { "alias": "loadingMessage"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "expandOnRowClick": { "alias": "expandOnRowClick"; "required": false; "isSignal": true; }; "singleExpand": { "alias": "singleExpand"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "cardBreakpoint": { "alias": "cardBreakpoint"; "required": false; "isSignal": true; }; "cardPrimaryCount": { "alias": "cardPrimaryCount"; "required": false; "isSignal": true; }; "sortColumn": { "alias": "sortColumn"; "required": false; "isSignal": true; }; "sortDirection": { "alias": "sortDirection"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "selectionChange": "selectionChange"; "rowClick": "rowClick"; "rowDoubleClick": "rowDoubleClick"; "sortColumn": "sortColumnChange"; "sortDirection": "sortDirectionChange"; }, ["columnDefs", "detailRowDef", "rowActionsDef"], never, true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
|
|
9255
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTableComponent<any>, "tn-table", never, { "dataSource": { "alias": "dataSource"; "required": false; "isSignal": true; }; "displayedColumns": { "alias": "displayedColumns"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "emptyDescription": { "alias": "emptyDescription"; "required": false; "isSignal": true; }; "emptyIcon": { "alias": "emptyIcon"; "required": false; "isSignal": true; }; "scrollRegionAriaLabel": { "alias": "scrollRegionAriaLabel"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "isRowExpandable": { "alias": "isRowExpandable"; "required": false; "isSignal": true; }; "activeRow": { "alias": "activeRow"; "required": false; "isSignal": true; }; "activeWhen": { "alias": "activeWhen"; "required": false; "isSignal": true; }; "activeBg": { "alias": "activeBg"; "required": false; "isSignal": true; }; "activeIndicator": { "alias": "activeIndicator"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "loadingMessage": { "alias": "loadingMessage"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "expandOnRowClick": { "alias": "expandOnRowClick"; "required": false; "isSignal": true; }; "singleExpand": { "alias": "singleExpand"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "cardBreakpoint": { "alias": "cardBreakpoint"; "required": false; "isSignal": true; }; "cardPrimaryCount": { "alias": "cardPrimaryCount"; "required": false; "isSignal": true; }; "sortColumn": { "alias": "sortColumn"; "required": false; "isSignal": true; }; "sortDirection": { "alias": "sortDirection"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "selectionChange": "selectionChange"; "rowClick": "rowClick"; "rowDoubleClick": "rowDoubleClick"; "sortColumn": "sortColumnChange"; "sortDirection": "sortDirectionChange"; }, ["columnDefs", "detailRowDef", "rowActionsDef"], never, true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
|
|
6935
9256
|
}
|
|
6936
9257
|
|
|
6937
9258
|
/**
|
|
@@ -7471,11 +9792,6 @@ declare class TnTablePagerComponent {
|
|
|
7471
9792
|
* `TnTestIdValue` testId.
|
|
7472
9793
|
*/
|
|
7473
9794
|
protected childTestId(suffix: string): string;
|
|
7474
|
-
/**
|
|
7475
|
-
* Normalize the injected token into a Signal so consumers can supply either
|
|
7476
|
-
* a plain object or a reactive signal (e.g. derived from a TranslateService's
|
|
7477
|
-
* onLangChange) and the pager re-renders when labels change.
|
|
7478
|
-
*/
|
|
7479
9795
|
private readonly defaultLabels;
|
|
7480
9796
|
/** 1-based index of the currently displayed page. */
|
|
7481
9797
|
currentPage: _angular_core.ModelSignal<number>;
|
|
@@ -7509,7 +9825,27 @@ declare class TnTablePagerComponent {
|
|
|
7509
9825
|
previousPageLabel: _angular_core.InputSignal<string | undefined>;
|
|
7510
9826
|
nextPageLabel: _angular_core.InputSignal<string | undefined>;
|
|
7511
9827
|
lastPageLabel: _angular_core.InputSignal<string | undefined>;
|
|
9828
|
+
/**
|
|
9829
|
+
* Accessible name for the pager's `navigation` landmark — what a screen
|
|
9830
|
+
* reader user picking this pager out of a landmark list hears.
|
|
9831
|
+
*
|
|
9832
|
+
* **Name every pager on a page that has more than one.** Two pagers both
|
|
9833
|
+
* announcing the DI default are one landmark repeated as far as that list is
|
|
9834
|
+
* concerned, which is what #249 was: `landmark-unique` fails and the user has
|
|
9835
|
+
* nothing to choose between them.
|
|
9836
|
+
*/
|
|
7512
9837
|
tablePaginationLabel: _angular_core.InputSignal<string | undefined>;
|
|
9838
|
+
/**
|
|
9839
|
+
* IDREF naming the pager from text already on the page — the heading over the
|
|
9840
|
+
* table it pages, typically, which is the name the user can see.
|
|
9841
|
+
*
|
|
9842
|
+
* Wins over the `aria-label` in the ARIA name computation while it resolves,
|
|
9843
|
+
* so pass one or the other rather than both. The `aria-label` is rendered
|
|
9844
|
+
* beside it either way and is what the pager falls back to if the IDREF is
|
|
9845
|
+
* typo'd or names an element that has not rendered yet — see
|
|
9846
|
+
* `resolvedTablePaginationLabel`.
|
|
9847
|
+
*/
|
|
9848
|
+
ariaLabelledby: _angular_core.InputSignal<string | undefined>;
|
|
7513
9849
|
/** Resolved labels: explicit input takes precedence over the DI default. */
|
|
7514
9850
|
protected resolvedItemsPerPageLabel: Signal<string>;
|
|
7515
9851
|
protected resolvedOfLabel: Signal<string>;
|
|
@@ -7517,7 +9853,57 @@ declare class TnTablePagerComponent {
|
|
|
7517
9853
|
protected resolvedPreviousPageLabel: Signal<string>;
|
|
7518
9854
|
protected resolvedNextPageLabel: Signal<string>;
|
|
7519
9855
|
protected resolvedLastPageLabel: Signal<string>;
|
|
7520
|
-
|
|
9856
|
+
/**
|
|
9857
|
+
* The landmark name a pager falls back to when the consumer sets no
|
|
9858
|
+
* `tablePaginationLabel` — the DI default, scoped by `testId` when there is
|
|
9859
|
+
* one.
|
|
9860
|
+
*
|
|
9861
|
+
* The scoping is what stops the DEFAULT from being the same string on every
|
|
9862
|
+
* pager, which is the shape #249 reported: two pagers, both announcing "Table
|
|
9863
|
+
* pagination", indistinguishable in a landmark list. `testId` is the only
|
|
9864
|
+
* per-instance identity the pager already has, and a page with two pagers
|
|
9865
|
+
* needs distinct ones anyway — without them the pagers' own child controls
|
|
9866
|
+
* collide on `select-page-size` / `button-first-page` (see the **Multiple
|
|
9867
|
+
* Pagers** story), so the multi-pager case that trips this rule is exactly the
|
|
9868
|
+
* case that already sets it.
|
|
9869
|
+
*
|
|
9870
|
+
* **It is a fallback and not the good answer.** A test id is a developer-facing
|
|
9871
|
+
* token: it is not translated, and `storage` reads as the word rather than as
|
|
9872
|
+
* "Storage pools". A pager whose name matters names itself with
|
|
9873
|
+
* `tablePaginationLabel`, or points at the table's visible heading with
|
|
9874
|
+
* `ariaLabelledby`; this only keeps the unnamed case from being ambiguous as
|
|
9875
|
+
* well as generic.
|
|
9876
|
+
*
|
|
9877
|
+
* Appended in parentheses rather than woven into the sentence, because the
|
|
9878
|
+
* base string comes from `TN_TABLE_PAGER_LABELS` and may be in any language —
|
|
9879
|
+
* there is no word order here to get right, only a translated name and a
|
|
9880
|
+
* scope after it.
|
|
9881
|
+
*/
|
|
9882
|
+
private defaultTablePaginationLabel;
|
|
9883
|
+
/**
|
|
9884
|
+
* The name rendered as `aria-label`, always — the explicit
|
|
9885
|
+
* `tablePaginationLabel` when there is one, the scoped default otherwise.
|
|
9886
|
+
*
|
|
9887
|
+
* **It is emitted beside an `ariaLabelledby` rather than withheld under it**,
|
|
9888
|
+
* which is where this differs from `../a11y/accessible-name`, the shared rule
|
|
9889
|
+
* the progressbars and the dialogs take. That rule drops the generic fallback
|
|
9890
|
+
* when the caller supplies an IDREF, so a dangling IDREF surfaces as an
|
|
9891
|
+
* unnamed element instead of being masked by a name that says nothing. The
|
|
9892
|
+
* trade is only worth making where "unnamed" is itself caught: a progressbar
|
|
9893
|
+
* fails `aria-progressbar-name`, an `over` drawer fails `aria-dialog-name`,
|
|
9894
|
+
* and a `side` drawer — a landmark, like this — rests on that module's
|
|
9895
|
+
* dev-mode warning instead. This pager has neither. `landmark-unique` compares
|
|
9896
|
+
* landmarks against each other, so one unnamed pager violates nothing at all;
|
|
9897
|
+
* and the warning is one it cannot take, because the fallback here is a name
|
|
9898
|
+
* the consumer configures through `TN_TABLE_PAGER_LABELS` rather than one
|
|
9899
|
+
* someone forgot, so it would fire on the ordinary case.
|
|
9900
|
+
*
|
|
9901
|
+
* Withholding here would therefore turn a typo in `ariaLabelledby` into a
|
|
9902
|
+
* pager that announces nothing and reports nothing — a worse version of the
|
|
9903
|
+
* defect #249 opened on. So the pager keeps a name it can always fall back to,
|
|
9904
|
+
* and accepts that a dangling IDREF is masked by a generic one.
|
|
9905
|
+
*/
|
|
9906
|
+
protected resolvedTablePaginationLabel: Signal<string | undefined>;
|
|
7521
9907
|
/** Emits the new 1-based page number whenever the user navigates. */
|
|
7522
9908
|
pageChange: _angular_core.OutputEmitterRef<number>;
|
|
7523
9909
|
/** Emits the new page-size value when the dropdown changes. */
|
|
@@ -7562,7 +9948,7 @@ declare class TnTablePagerComponent {
|
|
|
7562
9948
|
/** Forwards the current page/size to the data provider, if one is bound. */
|
|
7563
9949
|
private pushToProvider;
|
|
7564
9950
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTablePagerComponent, never>;
|
|
7565
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTablePagerComponent, "tn-table-pager", never, { "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "currentPage": { "alias": "currentPage"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "totalItems": { "alias": "totalItems"; "required": false; "isSignal": true; }; "dataProvider": { "alias": "dataProvider"; "required": false; "isSignal": true; }; "itemsPerPageLabel": { "alias": "itemsPerPageLabel"; "required": false; "isSignal": true; }; "ofLabel": { "alias": "ofLabel"; "required": false; "isSignal": true; }; "firstPageLabel": { "alias": "firstPageLabel"; "required": false; "isSignal": true; }; "previousPageLabel": { "alias": "previousPageLabel"; "required": false; "isSignal": true; }; "nextPageLabel": { "alias": "nextPageLabel"; "required": false; "isSignal": true; }; "lastPageLabel": { "alias": "lastPageLabel"; "required": false; "isSignal": true; }; "tablePaginationLabel": { "alias": "tablePaginationLabel"; "required": false; "isSignal": true; }; }, { "currentPage": "currentPageChange"; "pageSize": "pageSizeChange"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; }, never, never, true, never>;
|
|
9951
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTablePagerComponent, "tn-table-pager", never, { "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "currentPage": { "alias": "currentPage"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "totalItems": { "alias": "totalItems"; "required": false; "isSignal": true; }; "dataProvider": { "alias": "dataProvider"; "required": false; "isSignal": true; }; "itemsPerPageLabel": { "alias": "itemsPerPageLabel"; "required": false; "isSignal": true; }; "ofLabel": { "alias": "ofLabel"; "required": false; "isSignal": true; }; "firstPageLabel": { "alias": "firstPageLabel"; "required": false; "isSignal": true; }; "previousPageLabel": { "alias": "previousPageLabel"; "required": false; "isSignal": true; }; "nextPageLabel": { "alias": "nextPageLabel"; "required": false; "isSignal": true; }; "lastPageLabel": { "alias": "lastPageLabel"; "required": false; "isSignal": true; }; "tablePaginationLabel": { "alias": "tablePaginationLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, { "currentPage": "currentPageChange"; "pageSize": "pageSizeChange"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; }, never, never, true, never>;
|
|
7566
9952
|
}
|
|
7567
9953
|
|
|
7568
9954
|
/**
|
|
@@ -8561,6 +10947,26 @@ declare const QuickShortcuts: {
|
|
|
8561
10947
|
alt(key: string, platform?: PlatformType): string;
|
|
8562
10948
|
};
|
|
8563
10949
|
|
|
10950
|
+
/**
|
|
10951
|
+
* Reads a label token and hands back a Signal, whichever of the two shapes the app provided.
|
|
10952
|
+
*
|
|
10953
|
+
* Every `TN_*_LABELS` token in this library is declared as `T | Signal<T>` so a consumer can
|
|
10954
|
+
* pass a plain object (the common case — one static bundle for a single-language app) or a
|
|
10955
|
+
* Signal derived from an i18n service, in which case a language switch re-renders the chrome
|
|
10956
|
+
* live instead of needing a reload. Components only ever want the Signal, so normalizing at
|
|
10957
|
+
* the injection point keeps that union from leaking into every read site.
|
|
10958
|
+
*
|
|
10959
|
+
* @param token The label token to inject.
|
|
10960
|
+
* @returns A Signal of the provided labels; a plain object is wrapped in a constant Signal.
|
|
10961
|
+
*
|
|
10962
|
+
* @example
|
|
10963
|
+
* ```typescript
|
|
10964
|
+
* private readonly labels = injectTnLabels(TN_TABLE_LABELS);
|
|
10965
|
+
* // template: {{ labels().sortBy }}
|
|
10966
|
+
* ```
|
|
10967
|
+
*/
|
|
10968
|
+
declare function injectTnLabels<T>(token: InjectionToken<T | Signal<T>>): Signal<T>;
|
|
10969
|
+
|
|
8564
10970
|
declare class FileSizePipe implements PipeTransform {
|
|
8565
10971
|
transform(value: number): string;
|
|
8566
10972
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<FileSizePipe, never>;
|
|
@@ -8618,6 +11024,20 @@ declare function labelMarkupToHtml(value: string): string;
|
|
|
8618
11024
|
declare function labelMarkupToText(value: string): string;
|
|
8619
11025
|
|
|
8620
11026
|
type SpinnerMode = 'determinate' | 'indeterminate';
|
|
11027
|
+
/**
|
|
11028
|
+
* The accessible name a spinner falls back to when the caller names neither
|
|
11029
|
+
* `ariaLabel` nor `ariaLabelledby` (#202). Same reasoning as
|
|
11030
|
+
* `TN_PROGRESS_BAR_DEFAULT_LABEL`, and the case is sharper here: the spinner
|
|
11031
|
+
* defaults to indeterminate mode, so its unnamed default rendering reached
|
|
11032
|
+
* assistive technology as a progressbar with neither a name nor a value.
|
|
11033
|
+
*
|
|
11034
|
+
* `branded-spinner.component.ts` in this folder already fell back this way —
|
|
11035
|
+
* `ariaLabel() || "Loading..."` inline — so a fallback is the shape this
|
|
11036
|
+
* library had already settled on; what it lacked was the warning. It has both
|
|
11037
|
+
* since #206, through the same `tnAccessibleName` this component uses, which is
|
|
11038
|
+
* why the two constants sit side by side and differ.
|
|
11039
|
+
*/
|
|
11040
|
+
declare const TN_SPINNER_DEFAULT_LABEL = "Loading";
|
|
8621
11041
|
declare class TnSpinnerComponent {
|
|
8622
11042
|
mode: _angular_core.InputSignal<SpinnerMode>;
|
|
8623
11043
|
value: _angular_core.InputSignal<number>;
|
|
@@ -8625,6 +11045,18 @@ declare class TnSpinnerComponent {
|
|
|
8625
11045
|
strokeWidth: _angular_core.InputSignal<number>;
|
|
8626
11046
|
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
8627
11047
|
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
11048
|
+
/**
|
|
11049
|
+
* The name to render, or `null` to render no `aria-label` attribute — and the
|
|
11050
|
+
* dev-mode warning when the caller named neither input.
|
|
11051
|
+
*
|
|
11052
|
+
* Both halves live in `../a11y/accessible-name`, shared with `tn-progress-bar`
|
|
11053
|
+
* and `tn-branded-spinner` (#206), where the reasoning for each is set out:
|
|
11054
|
+
* an explicit `ariaLabel` always survives, because `aria-labelledby` only wins
|
|
11055
|
+
* the name calculation while its IDREF resolves; the generic fallback is
|
|
11056
|
+
* withheld beside one, because there it would mask a dangling IDREF with a
|
|
11057
|
+
* name that says nothing.
|
|
11058
|
+
*/
|
|
11059
|
+
resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
8628
11060
|
radius: _angular_core.Signal<number>;
|
|
8629
11061
|
circumference: _angular_core.Signal<number>;
|
|
8630
11062
|
strokeDasharray: _angular_core.Signal<string>;
|
|
@@ -8634,8 +11066,35 @@ declare class TnSpinnerComponent {
|
|
|
8634
11066
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnSpinnerComponent, "tn-spinner", never, { "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "diameter": { "alias": "diameter"; "required": false; "isSignal": true; }; "strokeWidth": { "alias": "strokeWidth"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
8635
11067
|
}
|
|
8636
11068
|
|
|
11069
|
+
/**
|
|
11070
|
+
* The name this spinner falls back to when the caller names neither `ariaLabel`
|
|
11071
|
+
* nor `ariaLabelledby`. Same reasoning as `TN_SPINNER_DEFAULT_LABEL` and
|
|
11072
|
+
* `TN_PROGRESS_BAR_DEFAULT_LABEL`.
|
|
11073
|
+
*
|
|
11074
|
+
* It differs from `TN_SPINNER_DEFAULT_LABEL` — `"Loading..."` against
|
|
11075
|
+
* `"Loading"` — only because this is the string the component already rendered,
|
|
11076
|
+
* inline in its host binding, before #206 gave it the shared resolver. Aligning
|
|
11077
|
+
* the two would change what a screen reader announces for every unnamed branded
|
|
11078
|
+
* spinner already in the wild, which is a louder change than the consistency
|
|
11079
|
+
* fix it would be part of. Exported so specs assert against it by name rather
|
|
11080
|
+
* than by a copied string literal.
|
|
11081
|
+
*/
|
|
11082
|
+
declare const TN_BRANDED_SPINNER_DEFAULT_LABEL = "Loading...";
|
|
8637
11083
|
declare class TnBrandedSpinnerComponent implements OnInit, OnDestroy, AfterViewInit {
|
|
8638
11084
|
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
11085
|
+
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
11086
|
+
/**
|
|
11087
|
+
* The name to render, or `null` to render no `aria-label` attribute — and the
|
|
11088
|
+
* dev-mode warning when the caller named neither input.
|
|
11089
|
+
*
|
|
11090
|
+
* This component is why `../a11y/accessible-name` exists (#206). It carried a
|
|
11091
|
+
* fallback inline in its host binding, so it never failed
|
|
11092
|
+
* `aria-progressbar-name` and the two fixes that gave the other progressbars
|
|
11093
|
+
* an `ariaLabelledby` input and a warning both passed it by. Routing it
|
|
11094
|
+
* through the shared resolver is what makes it named by the same rule rather
|
|
11095
|
+
* than by a third one nobody chose.
|
|
11096
|
+
*/
|
|
11097
|
+
resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
8639
11098
|
private paths;
|
|
8640
11099
|
private animationId;
|
|
8641
11100
|
private isAnimating;
|
|
@@ -8651,16 +11110,48 @@ declare class TnBrandedSpinnerComponent implements OnInit, OnDestroy, AfterViewI
|
|
|
8651
11110
|
private animateSequence;
|
|
8652
11111
|
private tween;
|
|
8653
11112
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnBrandedSpinnerComponent, never>;
|
|
8654
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnBrandedSpinnerComponent, "tn-branded-spinner", never, { "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
11113
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnBrandedSpinnerComponent, "tn-branded-spinner", never, { "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
8655
11114
|
}
|
|
8656
11115
|
|
|
8657
11116
|
type ProgressBarMode = 'determinate' | 'indeterminate' | 'buffer';
|
|
11117
|
+
/**
|
|
11118
|
+
* The accessible name a bar falls back to when the caller names neither
|
|
11119
|
+
* `ariaLabel` nor `ariaLabelledby` (#202).
|
|
11120
|
+
*
|
|
11121
|
+
* Deliberately generic, and deliberately not silent: the host carries
|
|
11122
|
+
* `role="progressbar"` unconditionally, so without a fallback the default
|
|
11123
|
+
* rendering is a progressbar assistive technology announces with no name at all
|
|
11124
|
+
* — "progress bar, 40%", with nothing to say what is progressing. The
|
|
11125
|
+
* alternative fix, withholding the role until there is a name for it, trades
|
|
11126
|
+
* that for no announcement whatever, which is worse: a screen reader would not
|
|
11127
|
+
* learn that anything is in progress, and on a determinate bar it would lose
|
|
11128
|
+
* the value too.
|
|
11129
|
+
*
|
|
11130
|
+
* A generic name is still a poor one, so it is paired with the dev-mode warning
|
|
11131
|
+
* `tnAccessibleName` raises. Exported so specs assert against it by name rather
|
|
11132
|
+
* than by a copied string literal.
|
|
11133
|
+
*/
|
|
11134
|
+
declare const TN_PROGRESS_BAR_DEFAULT_LABEL = "Progress";
|
|
8658
11135
|
declare class TnProgressBarComponent {
|
|
8659
11136
|
mode: _angular_core.InputSignal<ProgressBarMode>;
|
|
8660
11137
|
value: _angular_core.InputSignal<number>;
|
|
8661
11138
|
bufferValue: _angular_core.InputSignal<number>;
|
|
8662
11139
|
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
8663
11140
|
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
11141
|
+
/**
|
|
11142
|
+
* The name to render, or `null` to render no `aria-label` attribute — and the
|
|
11143
|
+
* dev-mode warning when the caller named neither input.
|
|
11144
|
+
*
|
|
11145
|
+
* Both halves live in `../a11y/accessible-name`, shared with `tn-spinner` and
|
|
11146
|
+
* `tn-branded-spinner` (#206), where the reasoning for each is set out: why an
|
|
11147
|
+
* explicit `ariaLabel` always survives, and why the generic fallback is
|
|
11148
|
+
* withheld beside an `ariaLabelledby`.
|
|
11149
|
+
*
|
|
11150
|
+
* A field initializer rather than the constructor, because it registers an
|
|
11151
|
+
* `effect` and so needs an injection context; this is one, and it keeps the
|
|
11152
|
+
* signal beside the inputs it reads.
|
|
11153
|
+
*/
|
|
11154
|
+
resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
8664
11155
|
/**
|
|
8665
11156
|
* Gets the transform value for the primary progress bar
|
|
8666
11157
|
*/
|
|
@@ -8680,13 +11171,113 @@ declare class TnProgressBarComponent {
|
|
|
8680
11171
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnProgressBarComponent, "tn-progress-bar", never, { "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "bufferValue": { "alias": "bufferValue"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
8681
11172
|
}
|
|
8682
11173
|
|
|
11174
|
+
/**
|
|
11175
|
+
* The accessible name this bar falls back to when the caller names neither
|
|
11176
|
+
* `ariaLabel` nor `ariaLabelledby` (#209).
|
|
11177
|
+
*
|
|
11178
|
+
* The same string and the same reasoning as `TN_PROGRESS_BAR_DEFAULT_LABEL`
|
|
11179
|
+
* next door, and deliberately a separate constant rather than an import of it:
|
|
11180
|
+
* `tnAccessibleName` takes a fallback PER COMPONENT because the least-bad
|
|
11181
|
+
* generic name differs by what the component is ("Progress" for a bar,
|
|
11182
|
+
* "Loading" for a spinner), and sharing the binding would make a future
|
|
11183
|
+
* divergence in one a silent change to the other. Exported so specs assert
|
|
11184
|
+
* against it by name rather than by a copied string literal.
|
|
11185
|
+
*/
|
|
11186
|
+
declare const TN_PARTICLE_PROGRESS_BAR_DEFAULT_LABEL = "Progress";
|
|
11187
|
+
/**
|
|
11188
|
+
* THE DECISION #209 ASKED FOR: THIS IS A PROGRESSBAR, NOT DECORATION
|
|
11189
|
+
* ------------------------------------------------------------------
|
|
11190
|
+
* Both readings were open. Before the change the host carried a class and
|
|
11191
|
+
* nothing else — no role, no name, no value — and because it never claimed
|
|
11192
|
+
* `role="progressbar"` it could not fail `aria-progressbar-name` either, so
|
|
11193
|
+
* every axe-based check in this library was silent on it by construction.
|
|
11194
|
+
* Measured on the unchanged component under jsdom, sweeping `svg-img-alt` and
|
|
11195
|
+
* the four rules `particle-progress-bar-a11y.spec.ts` keeps: 0 violations, 0
|
|
11196
|
+
* passes, 0 incomplete, on every one of them. Not clean — unexamined. That
|
|
11197
|
+
* spec keeps the measurement as its first test.
|
|
11198
|
+
*
|
|
11199
|
+
* It is a progressbar because it is a WHOLE indicator rather than an overlay on
|
|
11200
|
+
* someone else's. The SVG draws its own background track and its own fill rect,
|
|
11201
|
+
* and `fill` sizes the second against the first; that is a determinate progress
|
|
11202
|
+
* value, whatever the particles on top of it are doing. The alternative reading
|
|
11203
|
+
* — an ambient flourish shown BESIDE a real indicator, where a role would be
|
|
11204
|
+
* the redundant second announcement #203 was about — needs the real indicator
|
|
11205
|
+
* to exist, and nothing here provides one.
|
|
11206
|
+
*
|
|
11207
|
+
* The usage evidence points the same way, weakly but only in one direction. Its
|
|
11208
|
+
* only consumer in this repository is its own Storybook story: it has never
|
|
11209
|
+
* been placed next to a `tn-progress-bar`, so the redundancy risk is
|
|
11210
|
+
* hypothetical. It IS exported from `public-api.ts`, so consumers outside this
|
|
11211
|
+
* repository may already be showing it as the only progress on a screen — and
|
|
11212
|
+
* that asymmetry is what settles it. Choosing `aria-hidden` would assert a
|
|
11213
|
+
* usage constraint the library cannot enforce, and when that assertion is wrong
|
|
11214
|
+
* a screen-reader user gets nothing at all where a sighted user sees a bar
|
|
11215
|
+
* filling — strictly worse than the unnamed progressbars #202/#205/#206 fixed.
|
|
11216
|
+
* Choosing the role when the component really is ambient costs a redundant
|
|
11217
|
+
* announcement a consumer can silence with `aria-hidden` on its own wrapper.
|
|
11218
|
+
* The two errors are not the same size.
|
|
11219
|
+
*
|
|
11220
|
+
* The canvas IS decoration, and that is a separate question from what the host
|
|
11221
|
+
* is. It draws particles and carries no information the fill rect does not, so
|
|
11222
|
+
* the whole drawing is hidden and the ARIA value is what conveys the progress —
|
|
11223
|
+
* see the `aria-hidden` on the `<svg>` in the template.
|
|
11224
|
+
*
|
|
11225
|
+
* WHY THE VALUE IS A PERCENTAGE AND `fill` IS NOT
|
|
11226
|
+
* ----------------------------------------------
|
|
11227
|
+
* `fill` is a px length along the track, not a percentage — the story's control
|
|
11228
|
+
* runs it 0–600 while `width` runs 200–800, so the same `fill` means different
|
|
11229
|
+
* progress at different widths. `aria-valuenow` is reported on 0–100 instead,
|
|
11230
|
+
* matching `tn-progress-bar`, so that two bars from one library do not announce
|
|
11231
|
+
* on two different scales and no layout px reaches the accessibility tree.
|
|
11232
|
+
* Assistive technology derives the percentage from the range either way; what
|
|
11233
|
+
* this fixes is which range a consumer reads in the DOM.
|
|
11234
|
+
*/
|
|
8683
11235
|
declare class TnParticleProgressBarComponent implements AfterViewInit, OnDestroy {
|
|
8684
11236
|
speed: _angular_core.InputSignal<"medium" | "slow" | "fast" | "ludicrous">;
|
|
8685
11237
|
color: _angular_core.InputSignal<string>;
|
|
8686
11238
|
height: _angular_core.InputSignal<number>;
|
|
8687
11239
|
width: _angular_core.InputSignal<number>;
|
|
8688
11240
|
fill: _angular_core.InputSignal<number>;
|
|
11241
|
+
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
11242
|
+
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
8689
11243
|
canvasRef: _angular_core.Signal<ElementRef<HTMLCanvasElement>>;
|
|
11244
|
+
/** Exposed to the template so the rects and the ARIA value share one inset. */
|
|
11245
|
+
readonly trackInset = 50;
|
|
11246
|
+
/**
|
|
11247
|
+
* The name to render, or `null` to render no `aria-label` at all — and the
|
|
11248
|
+
* dev-mode warning when the caller named neither input.
|
|
11249
|
+
*
|
|
11250
|
+
* Both halves live in `../a11y/accessible-name`, shared with the other three
|
|
11251
|
+
* progressbars in this library (#206), where the reasoning for each is set
|
|
11252
|
+
* out: why an explicit `ariaLabel` always survives, and why the generic
|
|
11253
|
+
* fallback is withheld beside an `ariaLabelledby`. Routed through that helper
|
|
11254
|
+
* rather than given a rule of its own, which is what #209 asked for — a
|
|
11255
|
+
* fourth naming rule is how `tn-branded-spinner` ended up divergent.
|
|
11256
|
+
*
|
|
11257
|
+
* A field initializer rather than the constructor, because it registers an
|
|
11258
|
+
* `effect` and so needs an injection context; this is one, and it keeps the
|
|
11259
|
+
* signal beside the inputs it reads.
|
|
11260
|
+
*/
|
|
11261
|
+
resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
11262
|
+
/** The drawable length of the track: the SVG width less the inset at each end. */
|
|
11263
|
+
trackLength: _angular_core.Signal<number>;
|
|
11264
|
+
/**
|
|
11265
|
+
* `fill` as a percentage of the track, or `null` when there is no track to
|
|
11266
|
+
* measure against.
|
|
11267
|
+
*
|
|
11268
|
+
* Clamped, because `fill` is not: the story alone can drive it to 600 against
|
|
11269
|
+
* a 500px track, where the fill rect simply overflows. `aria-valuenow` may not
|
|
11270
|
+
* exceed `aria-valuemax`, and 100 is also what such a bar visually reads as —
|
|
11271
|
+
* a track filled end to end. The clamp is on the announcement only; nothing
|
|
11272
|
+
* here changes what is drawn.
|
|
11273
|
+
*
|
|
11274
|
+
* `null` on a non-positive track — `width` at or below twice the inset — is
|
|
11275
|
+
* what makes the host announce as an INDETERMINATE progressbar rather than
|
|
11276
|
+
* carrying a value derived from a division by zero or a negative range. The
|
|
11277
|
+
* role stays either way, because "something is in progress" is true even when
|
|
11278
|
+
* how far is not answerable.
|
|
11279
|
+
*/
|
|
11280
|
+
valuePercent: _angular_core.Signal<number | null>;
|
|
8690
11281
|
private ctx;
|
|
8691
11282
|
private particles;
|
|
8692
11283
|
private shades;
|
|
@@ -8714,7 +11305,7 @@ declare class TnParticleProgressBarComponent implements AfterViewInit, OnDestroy
|
|
|
8714
11305
|
*/
|
|
8715
11306
|
private generateDarkerShades;
|
|
8716
11307
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnParticleProgressBarComponent, never>;
|
|
8717
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnParticleProgressBarComponent, "tn-particle-progress-bar", never, { "speed": { "alias": "speed"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "fill": { "alias": "fill"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
11308
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnParticleProgressBarComponent, "tn-particle-progress-bar", never, { "speed": { "alias": "speed"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "fill": { "alias": "fill"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
8718
11309
|
}
|
|
8719
11310
|
|
|
8720
11311
|
interface DateRange {
|
|
@@ -9769,8 +12360,9 @@ declare class TnSliderThumbDirective implements ControlValueAccessor, OnInit, On
|
|
|
9769
12360
|
value: () => number;
|
|
9770
12361
|
labelPrefix: () => string;
|
|
9771
12362
|
labelSuffix: () => string;
|
|
9772
|
-
|
|
9773
|
-
|
|
12363
|
+
resolvedAriaLabel: () => string | null;
|
|
12364
|
+
explicitAriaLabelledby: () => string | null;
|
|
12365
|
+
fieldAriaLabelledby: () => string | null;
|
|
9774
12366
|
updateValue: (value: number) => void;
|
|
9775
12367
|
markTouched: () => void;
|
|
9776
12368
|
getSliderRect: () => DOMRect;
|
|
@@ -9817,12 +12409,29 @@ declare class TnSliderThumbDirective implements ControlValueAccessor, OnInit, On
|
|
|
9817
12409
|
private onGlobalTouchEnd;
|
|
9818
12410
|
private updateValueFromPosition;
|
|
9819
12411
|
/**
|
|
9820
|
-
*
|
|
9821
|
-
*
|
|
9822
|
-
*
|
|
9823
|
-
*
|
|
12412
|
+
* The `aria-label` for the range input: the parent slider's input when set,
|
|
12413
|
+
* otherwise a value placed directly on the `<input tnSliderThumb>`. Returning
|
|
12414
|
+
* the fallback keeps the host binding from wiping a directly-set label. Null
|
|
12415
|
+
* removes the attribute.
|
|
9824
12416
|
*/
|
|
9825
12417
|
ariaLabel(): string | null;
|
|
12418
|
+
/**
|
|
12419
|
+
* The `aria-labelledby` for the range input — and the one place the precedence
|
|
12420
|
+
* between an explicit name and an inherited one is decided.
|
|
12421
|
+
*
|
|
12422
|
+
* An explicit reference comes first, from the slider's input or from this
|
|
12423
|
+
* input itself. **An enclosing `tn-form-field`'s label comes last, and only
|
|
12424
|
+
* when nothing has named the control directly.** That ordering is the point:
|
|
12425
|
+
* `aria-labelledby` beats `aria-label` in the ARIA name calculation whenever
|
|
12426
|
+
* it resolves, so emitting the field's label beside an `aria-label` written on
|
|
12427
|
+
* this input would render both attributes and announce the FIELD's label —
|
|
12428
|
+
* silently replacing the name the consumer wrote on the control (#235).
|
|
12429
|
+
*
|
|
12430
|
+
* The slider's own `ariaLabel` input needs no such check here, because
|
|
12431
|
+
* `injectTnFormFieldAria` already withholds the field's label whenever it is
|
|
12432
|
+
* set; this covers the label that is written past the slider, onto the
|
|
12433
|
+
* projected input, which the slider's inputs never see.
|
|
12434
|
+
*/
|
|
9826
12435
|
ariaLabelledby(): string | null;
|
|
9827
12436
|
/**
|
|
9828
12437
|
* Commit a value that originated outside the native input (a thumb drag).
|
|
@@ -9860,13 +12469,63 @@ declare class TnSliderComponent implements ControlValueAccessor, OnDestroy, Afte
|
|
|
9860
12469
|
/**
|
|
9861
12470
|
* Accessible name forwarded to the inner range input — the focusable element
|
|
9862
12471
|
* screen readers actually announce. Set this (or `aria-labelledby`) when the
|
|
9863
|
-
* slider isn't already
|
|
12472
|
+
* slider isn't already inside a labelled `tn-form-field`, otherwise a
|
|
9864
12473
|
* standalone `<tn-slider><input tnSliderThumb></tn-slider>` announces only
|
|
9865
|
-
* "slider".
|
|
9866
|
-
*
|
|
12474
|
+
* "slider".
|
|
12475
|
+
*
|
|
12476
|
+
* Precedence is per attribute: each of `aria-label` and `aria-labelledby` is
|
|
12477
|
+
* taken from the matching input here, else from one written directly on the
|
|
12478
|
+
* `input[tnSliderThumb]`. Between the two attributes, ARIA's own rule decides
|
|
12479
|
+
* — `aria-labelledby` wins wherever it resolves.
|
|
12480
|
+
*
|
|
12481
|
+
* An enclosing `tn-form-field`'s label comes after all of those, and only when
|
|
12482
|
+
* nothing above has named the control: it is chrome the consumer did not write
|
|
12483
|
+
* on the control. See `TnSliderThumbDirective.ariaLabelledby`, where the
|
|
12484
|
+
* ordering is applied.
|
|
9867
12485
|
*/
|
|
9868
12486
|
ariaLabel: _angular_core.InputSignal<string | undefined>;
|
|
9869
12487
|
ariaLabelledby: _angular_core.InputSignal<string | undefined>;
|
|
12488
|
+
/**
|
|
12489
|
+
* ARIA wiring from an enclosing `tn-form-field`, read for `labelledby` alone:
|
|
12490
|
+
* #235 is about the range input having no accessible name, and the field's
|
|
12491
|
+
* `describedby`/`invalid`/`required` are a separate question this slider has
|
|
12492
|
+
* never answered either way.
|
|
12493
|
+
*
|
|
12494
|
+
* Called with NO argument, so it reports the field's label id unconditioned.
|
|
12495
|
+
* Handing it the `ariaLabel` input would have it suppress the field itself —
|
|
12496
|
+
* on truthiness, so a whitespace-only label would cancel the field while being
|
|
12497
|
+
* dropped as no name, leaving nothing — and it would only do so while this
|
|
12498
|
+
* field is initialised after the one it reads, since a signal captured before
|
|
12499
|
+
* its own initialiser runs arrives as `undefined` and is swallowed by an
|
|
12500
|
+
* optional call. The suppression is the thumb's, where the rest of the
|
|
12501
|
+
* precedence already lives and where it is visible.
|
|
12502
|
+
*/
|
|
12503
|
+
private readonly fieldAria;
|
|
12504
|
+
/**
|
|
12505
|
+
* The `aria-label` the thumb should render, or `null` for none.
|
|
12506
|
+
*
|
|
12507
|
+
* Blank is not a name: `aria-label=""` names the input as emptily as no
|
|
12508
|
+
* attribute at all, while satisfying axe's `label` rule — a green check on a
|
|
12509
|
+
* control a screen reader announces as "slider" (#235). Same reasoning as
|
|
12510
|
+
* `a11y/accessible-name.ts`, which the three progressbars share.
|
|
12511
|
+
*/
|
|
12512
|
+
readonly resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
12513
|
+
/**
|
|
12514
|
+
* The `ariaLabelledby` INPUT, blank normalised away, or `null` when unset.
|
|
12515
|
+
*
|
|
12516
|
+
* Kept separate from {@link fieldAriaLabelledby} rather than folded into one
|
|
12517
|
+
* resolved value, because the two rank differently against a label written on
|
|
12518
|
+
* the projected input — see `TnSliderThumbDirective.ariaLabelledby`.
|
|
12519
|
+
*/
|
|
12520
|
+
readonly explicitAriaLabelledby: _angular_core.Signal<string | null>;
|
|
12521
|
+
/**
|
|
12522
|
+
* The enclosing `tn-form-field`'s label id, or `null` outside one.
|
|
12523
|
+
*
|
|
12524
|
+
* With no field and no input, a slider stays unnamed — deliberately: a
|
|
12525
|
+
* generic fallback ("Slider") would satisfy axe while announcing nothing the
|
|
12526
|
+
* user can act on, and only the consumer knows what this slider controls.
|
|
12527
|
+
*/
|
|
12528
|
+
readonly fieldAriaLabelledby: _angular_core.Signal<string | null>;
|
|
9870
12529
|
thumbDirective: _angular_core.Signal<TnSliderThumbDirective>;
|
|
9871
12530
|
sliderContainer: _angular_core.Signal<ElementRef<HTMLDivElement>>;
|
|
9872
12531
|
private onChange;
|
|
@@ -10175,11 +12834,243 @@ interface ButtonToggleHarnessFilters extends BaseHarnessFilters {
|
|
|
10175
12834
|
label?: string | RegExp;
|
|
10176
12835
|
}
|
|
10177
12836
|
|
|
12837
|
+
/**
|
|
12838
|
+
* The visual half of `tnTooltip`, and — while it is only shown on hover — only the visual half
|
|
12839
|
+
* (#203).
|
|
12840
|
+
*
|
|
12841
|
+
* WHY THE HOVER PANEL IS `aria-hidden` AND CARRIES NO ROLE
|
|
12842
|
+
* --------------------------------------------------------
|
|
12843
|
+
* The accessible text is `TnTooltipDirective`'s job, and it routes it through
|
|
12844
|
+
* CDK's `AriaDescriber` — a persistent visually-hidden element referenced by
|
|
12845
|
+
* `aria-describedby` on the interactive control, so the reference never dangles
|
|
12846
|
+
* while this overlay comes and goes. That is one tooltip, described once.
|
|
12847
|
+
*
|
|
12848
|
+
* This node used to ALSO claim `role="tooltip"`, which put a second tooltip in
|
|
12849
|
+
* the accessibility tree for the same message: the describer's (named, and
|
|
12850
|
+
* referenced by the control) and this one (named by nothing when the message is
|
|
12851
|
+
* empty or markup-only). axe scores that `aria-tooltip-name`, WCAG 4.1.2.
|
|
12852
|
+
* Of the two models the ticket set out — make the overlay the accessible
|
|
12853
|
+
* tooltip, or make it decorative — decorative is the one the rest of the
|
|
12854
|
+
* directive is already built for, and it is what `pointer-events: none` on
|
|
12855
|
+
* `:host` already says: this element cannot be hovered, clicked or interacted
|
|
12856
|
+
* with. Hiding it removes the duplicate rather than moving the name onto it.
|
|
12857
|
+
*
|
|
12858
|
+
* `aria-hidden="true"` is bound to `sticky` rather than to a visibility signal.
|
|
12859
|
+
* The node is exposed in no state a hover tooltip can be in, so there is no
|
|
12860
|
+
* visibility for it to reflect — the old hard-coded `aria-hidden="false"` was
|
|
12861
|
+
* the bug, because that value DID depend on state it was not tracking.
|
|
12862
|
+
*
|
|
12863
|
+
* AND WHY A PINNED ONE IS THE OPPOSITE
|
|
12864
|
+
* ------------------------------------
|
|
12865
|
+
* `sticky` is the one state where the panel is more than decoration: it takes
|
|
12866
|
+
* pointer events, holds focusable content, and renders a dismiss button, so
|
|
12867
|
+
* hiding it would put focusable nodes inside an `aria-hidden` subtree and make
|
|
12868
|
+
* the very content pinning exists to reach unreachable. A pinned panel is
|
|
12869
|
+
* therefore an exposed, named `dialog` (see `sticky`), and the describer's
|
|
12870
|
+
* description on the host stands alongside it rather than in place of it.
|
|
12871
|
+
*/
|
|
10178
12872
|
declare class TnTooltipComponent {
|
|
10179
12873
|
message: _angular_core.InputSignal<string>;
|
|
12874
|
+
/**
|
|
12875
|
+
* Optional DOM id for the rendered element. Omitted entirely when empty
|
|
12876
|
+
* rather than rendered as `id=""`, which no `aria-describedby` or selector can
|
|
12877
|
+
* reference — a silently dangling hook.
|
|
12878
|
+
*/
|
|
10180
12879
|
id: _angular_core.InputSignal<string>;
|
|
12880
|
+
/**
|
|
12881
|
+
* Pinned ("sticky") mode. The tooltip stops being click-through so its content can be
|
|
12882
|
+
* interacted with, and a dismiss button is rendered next to the message.
|
|
12883
|
+
*
|
|
12884
|
+
* It also changes what the panel *is* for assistive tech: ARIA's `tooltip` role is specified as
|
|
12885
|
+
* non-focusable, non-interactive content that something else is described by, so a screen
|
|
12886
|
+
* reader may flatten it to a text description and never expose the link or the dismiss button -
|
|
12887
|
+
* exactly what pinning exists to make reachable. A pinned panel is therefore a `dialog`, named
|
|
12888
|
+
* by `panelAriaLabel`.
|
|
12889
|
+
*
|
|
12890
|
+
* It is a *non-modal* dialog and deliberately traps nothing: Tab past the dismiss button leaves
|
|
12891
|
+
* the panel while it stays open. Where focus lands next is wherever the panel sits in the tab
|
|
12892
|
+
* order, which is the end of it - CDK appends its overlay container as the last child of
|
|
12893
|
+
* `<body>` - so in practice Tab leaves the document for the browser's own chrome rather than
|
|
12894
|
+
* continuing after the host. Not trapping is the right shape for a popup the user can also
|
|
12895
|
+
* leave by Escape or by clicking outside, and it keeps a tooltip from holding the keyboard
|
|
12896
|
+
* hostage.
|
|
12897
|
+
*/
|
|
12898
|
+
sticky: _angular_core.InputSignal<boolean>;
|
|
12899
|
+
/** Accessible name for the dismiss button, so consumers can localize it. */
|
|
12900
|
+
closeAriaLabel: _angular_core.InputSignal<string>;
|
|
12901
|
+
/**
|
|
12902
|
+
* Accessible name for the pinned panel (see `sticky`), so consumers can localize it.
|
|
12903
|
+
*
|
|
12904
|
+
* A short static name rather than the message: a screen reader reads a dialog's name on entry
|
|
12905
|
+
* and then its content, so naming it after the message would announce that message twice —
|
|
12906
|
+
* three times counting the host's own description.
|
|
12907
|
+
*/
|
|
12908
|
+
panelAriaLabel: _angular_core.InputSignal<string>;
|
|
12909
|
+
/** Emitted when the user activates the dismiss button. */
|
|
12910
|
+
onDismiss: _angular_core.OutputEmitterRef<void>;
|
|
12911
|
+
private panel;
|
|
12912
|
+
/**
|
|
12913
|
+
* Moves focus onto the tooltip panel. Used when sticky mode is entered from the keyboard, so
|
|
12914
|
+
* the tooltip's content is reachable without a pointer: from the panel, Tab walks the message
|
|
12915
|
+
* (links included) and then the dismiss button. The panel is only focusable in sticky mode,
|
|
12916
|
+
* so this is a no-op otherwise.
|
|
12917
|
+
*/
|
|
12918
|
+
focusPanel(): void;
|
|
10181
12919
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTooltipComponent, never>;
|
|
10182
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTooltipComponent, "tn-tooltip", never, { "message": { "alias": "message"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
12920
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTooltipComponent, "tn-tooltip", never, { "message": { "alias": "message"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "sticky": { "alias": "sticky"; "required": false; "isSignal": true; }; "closeAriaLabel": { "alias": "closeAriaLabel"; "required": false; "isSignal": true; }; "panelAriaLabel": { "alias": "panelAriaLabel"; "required": false; "isSignal": true; }; }, { "onDismiss": "onDismiss"; }, never, never, true, never>;
|
|
12921
|
+
}
|
|
12922
|
+
|
|
12923
|
+
/**
|
|
12924
|
+
* Harness for interacting with a tooltip rendered by `tnTooltip` in tests.
|
|
12925
|
+
*
|
|
12926
|
+
* Tooltips render in a CDK overlay **outside** the component tree, so the regular
|
|
12927
|
+
* `TestbedHarnessEnvironment.loader(fixture)` won't find them. Use
|
|
12928
|
+
* `TnTooltipTesting.rootLoader(fixture)` instead — it searches the whole document.
|
|
12929
|
+
*
|
|
12930
|
+
* @example
|
|
12931
|
+
* ```typescript
|
|
12932
|
+
* import { TnTooltipHarness, TnTooltipTesting } from '@truenas/ui-components';
|
|
12933
|
+
*
|
|
12934
|
+
* const rootLoader = TnTooltipTesting.rootLoader(fixture);
|
|
12935
|
+
*
|
|
12936
|
+
* // Hover tooltip: show it, then read it
|
|
12937
|
+
* host.dispatchEvent(new MouseEvent('mouseenter'));
|
|
12938
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
12939
|
+
* expect(await tooltip.getText()).toBe('Pool is online');
|
|
12940
|
+
*
|
|
12941
|
+
* // Sticky tooltip: click the host to pin it, then dismiss it
|
|
12942
|
+
* host.click();
|
|
12943
|
+
* const pinned = await rootLoader.getHarness(TnTooltipHarness);
|
|
12944
|
+
* expect(await pinned.isSticky()).toBe(true);
|
|
12945
|
+
* await pinned.dismiss();
|
|
12946
|
+
* ```
|
|
12947
|
+
*/
|
|
12948
|
+
declare class TnTooltipHarness extends ComponentHarness {
|
|
12949
|
+
/**
|
|
12950
|
+
* The selector for the host element of a `TnTooltipComponent` instance.
|
|
12951
|
+
*/
|
|
12952
|
+
static hostSelector: string;
|
|
12953
|
+
private _panel;
|
|
12954
|
+
private _message;
|
|
12955
|
+
private _closeButton;
|
|
12956
|
+
/**
|
|
12957
|
+
* Gets a `HarnessPredicate` that can be used to search for a tooltip with specific
|
|
12958
|
+
* attributes. Useful when more than one tooltip is on screen — a pinned one plus a hover
|
|
12959
|
+
* one, for instance.
|
|
12960
|
+
*
|
|
12961
|
+
* @param options Options for filtering which tooltip instances are considered a match.
|
|
12962
|
+
* @returns A `HarnessPredicate` configured with the given options.
|
|
12963
|
+
*
|
|
12964
|
+
* @example
|
|
12965
|
+
* ```typescript
|
|
12966
|
+
* // Find a tooltip by its text
|
|
12967
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness.with({ text: 'Pool is online' }));
|
|
12968
|
+
*
|
|
12969
|
+
* // Find only the pinned one
|
|
12970
|
+
* const pinned = await rootLoader.getHarness(TnTooltipHarness.with({ sticky: true }));
|
|
12971
|
+
* ```
|
|
12972
|
+
*/
|
|
12973
|
+
static with(options?: TooltipHarnessFilters): HarnessPredicate<TnTooltipHarness>;
|
|
12974
|
+
/**
|
|
12975
|
+
* Gets the tooltip's text content, with any markup in the message stripped.
|
|
12976
|
+
*
|
|
12977
|
+
* @returns Promise resolving to the tooltip's text.
|
|
12978
|
+
*
|
|
12979
|
+
* @example
|
|
12980
|
+
* ```typescript
|
|
12981
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
12982
|
+
* expect(await tooltip.getText()).toBe('Pool is online');
|
|
12983
|
+
* ```
|
|
12984
|
+
*/
|
|
12985
|
+
getText(): Promise<string>;
|
|
12986
|
+
/**
|
|
12987
|
+
* Checks whether the tooltip is pinned open (sticky mode), i.e. interactive and dismissible
|
|
12988
|
+
* rather than tied to the pointer.
|
|
12989
|
+
*
|
|
12990
|
+
* @returns Promise resolving to true if the tooltip is pinned.
|
|
12991
|
+
*
|
|
12992
|
+
* @example
|
|
12993
|
+
* ```typescript
|
|
12994
|
+
* host.click();
|
|
12995
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
12996
|
+
* expect(await tooltip.isSticky()).toBe(true);
|
|
12997
|
+
* ```
|
|
12998
|
+
*/
|
|
12999
|
+
isSticky(): Promise<boolean>;
|
|
13000
|
+
/**
|
|
13001
|
+
* Gets the accessible name of the dismiss button, or null when the tooltip isn't pinned.
|
|
13002
|
+
*
|
|
13003
|
+
* @returns Promise resolving to the dismiss button's aria-label.
|
|
13004
|
+
*
|
|
13005
|
+
* @example
|
|
13006
|
+
* ```typescript
|
|
13007
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
13008
|
+
* expect(await tooltip.getDismissLabel()).toBe('Close tooltip');
|
|
13009
|
+
* ```
|
|
13010
|
+
*/
|
|
13011
|
+
getDismissLabel(): Promise<string | null>;
|
|
13012
|
+
/**
|
|
13013
|
+
* Clicks the tooltip's dismiss button. Throws if the tooltip isn't pinned, since the button
|
|
13014
|
+
* only exists in sticky mode.
|
|
13015
|
+
*
|
|
13016
|
+
* @returns Promise that resolves when the tooltip has been dismissed.
|
|
13017
|
+
*
|
|
13018
|
+
* @example
|
|
13019
|
+
* ```typescript
|
|
13020
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
13021
|
+
* await tooltip.dismiss();
|
|
13022
|
+
* ```
|
|
13023
|
+
*/
|
|
13024
|
+
dismiss(): Promise<void>;
|
|
13025
|
+
/**
|
|
13026
|
+
* Clicks an element inside the tooltip's message — a link, typically. Only meaningful in
|
|
13027
|
+
* sticky mode, where the tooltip stops being click-through.
|
|
13028
|
+
*
|
|
13029
|
+
* @param selector CSS selector of the element to click, relative to the message.
|
|
13030
|
+
* @returns Promise that resolves when the click action is complete.
|
|
13031
|
+
*
|
|
13032
|
+
* @example
|
|
13033
|
+
* ```typescript
|
|
13034
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
13035
|
+
* await tooltip.clickContent('a');
|
|
13036
|
+
* ```
|
|
13037
|
+
*/
|
|
13038
|
+
clickContent(selector: string): Promise<void>;
|
|
13039
|
+
}
|
|
13040
|
+
/**
|
|
13041
|
+
* A set of criteria that can be used to filter a list of `TnTooltipHarness` instances.
|
|
13042
|
+
*/
|
|
13043
|
+
interface TooltipHarnessFilters extends BaseHarnessFilters {
|
|
13044
|
+
/** Filters by the tooltip's text. Supports string or regex matching. */
|
|
13045
|
+
text?: string | RegExp;
|
|
13046
|
+
/** Filters by whether the tooltip is pinned open (sticky mode). */
|
|
13047
|
+
sticky?: boolean;
|
|
13048
|
+
}
|
|
13049
|
+
|
|
13050
|
+
/**
|
|
13051
|
+
* Test utilities for working with `TnTooltipHarness`.
|
|
13052
|
+
*
|
|
13053
|
+
* Tooltips are portaled into the CDK overlay outside the component tree, so a regular
|
|
13054
|
+
* `TestbedHarnessEnvironment.loader()` won't find them. Use `TnTooltipTesting.rootLoader()`
|
|
13055
|
+
* to get a loader that can.
|
|
13056
|
+
*
|
|
13057
|
+
* @example
|
|
13058
|
+
* ```typescript
|
|
13059
|
+
* import { TnTooltipTesting, TnTooltipHarness } from '@truenas/ui-components';
|
|
13060
|
+
*
|
|
13061
|
+
* const rootLoader = TnTooltipTesting.rootLoader(fixture);
|
|
13062
|
+
* const tooltip = await rootLoader.getHarness(TnTooltipHarness);
|
|
13063
|
+
* ```
|
|
13064
|
+
*/
|
|
13065
|
+
declare class TnTooltipTesting {
|
|
13066
|
+
/**
|
|
13067
|
+
* Creates a `HarnessLoader` that searches the entire document, including the CDK overlays
|
|
13068
|
+
* that tooltips are rendered into.
|
|
13069
|
+
*
|
|
13070
|
+
* @param fixture The component fixture for the test.
|
|
13071
|
+
* @returns A `HarnessLoader` capable of finding tooltip harnesses.
|
|
13072
|
+
*/
|
|
13073
|
+
static rootLoader(fixture: ComponentFixture<unknown>): HarnessLoader;
|
|
10183
13074
|
}
|
|
10184
13075
|
|
|
10185
13076
|
interface TnConfirmDialogData {
|
|
@@ -10234,7 +13125,56 @@ declare class TnDialog {
|
|
|
10234
13125
|
static ɵprov: _angular_core.ɵɵInjectableDeclaration<TnDialog>;
|
|
10235
13126
|
}
|
|
10236
13127
|
|
|
13128
|
+
/**
|
|
13129
|
+
* Chrome copy `tn-dialog-shell` renders itself. These were baked into the template as English
|
|
13130
|
+
* literals, which a consumer could not translate at all — there was no input to bind. Provide
|
|
13131
|
+
* {@link TN_DIALOG_CHROME_LABELS} at the app root to wire them to an i18n service.
|
|
13132
|
+
*
|
|
13133
|
+
* "Chrome" rather than plain `TnDialogLabels` to keep this bundle a clear letter apart from
|
|
13134
|
+
* {@link TN_DIALOG_SHELL_DEFAULT_LABEL}, which is a different thing entirely — the fallback
|
|
13135
|
+
* accessible name for the dialog surface itself, not copy the header renders.
|
|
13136
|
+
*/
|
|
13137
|
+
interface TnDialogChromeLabels {
|
|
13138
|
+
/** Accessible name for the header close (X) button. */
|
|
13139
|
+
close: string;
|
|
13140
|
+
/** Accessible name for the fullscreen toggle while the dialog is windowed. */
|
|
13141
|
+
enterFullscreen: string;
|
|
13142
|
+
/** Accessible name for the fullscreen toggle while the dialog is fullscreen. */
|
|
13143
|
+
exitFullscreen: string;
|
|
13144
|
+
}
|
|
13145
|
+
/** English defaults used when no `TN_DIALOG_CHROME_LABELS` provider is registered. */
|
|
13146
|
+
declare const TN_DIALOG_DEFAULT_CHROME_LABELS: TnDialogChromeLabels;
|
|
13147
|
+
/**
|
|
13148
|
+
* DI token for app-wide dialog chrome labels. Provide either a static object or a
|
|
13149
|
+
* `Signal<TnDialogChromeLabels>` — the latter lets every dialog react to language changes when
|
|
13150
|
+
* the consumer wires it up to an i18n service.
|
|
13151
|
+
*/
|
|
13152
|
+
declare const TN_DIALOG_CHROME_LABELS: InjectionToken<TnDialogChromeLabels | Signal<TnDialogChromeLabels>>;
|
|
13153
|
+
/**
|
|
13154
|
+
* The accessible name a dialog falls back to when it renders no `title` and the
|
|
13155
|
+
* caller named it through neither this component nor the `DialogConfig` (#219).
|
|
13156
|
+
*
|
|
13157
|
+
* `title` defaults to `''`, so the DEFAULT rendering of this component put an
|
|
13158
|
+
* empty `<h2>` in the header and left the CDK container with no naming
|
|
13159
|
+
* attribute at all — measured as `empty-heading` on the heading and
|
|
13160
|
+
* `aria-dialog-name` on the dialog. A dialog with no name is announced as
|
|
13161
|
+
* "dialog" and nothing else, which is the whole of what a screen-reader user is
|
|
13162
|
+
* told about a surface that just took over the page and trapped their focus.
|
|
13163
|
+
*
|
|
13164
|
+
* "Dialog" is a poor name, and says almost exactly what the role already says.
|
|
13165
|
+
* It is still better than the two alternatives: leaving the surface unnamed, or
|
|
13166
|
+
* withholding `role="dialog"` until there is a name — the latter would move a
|
|
13167
|
+
* listener into a focus trap with no announcement that anything had opened. So
|
|
13168
|
+
* it is paired with the dev-mode warning `tnAccessibleName` raises, which is
|
|
13169
|
+
* what keeps the fallback from becoming a quiet way to ship a nameless dialog.
|
|
13170
|
+
*
|
|
13171
|
+
* Exported so specs assert against it by name rather than by a copied literal.
|
|
13172
|
+
*/
|
|
13173
|
+
declare const TN_DIALOG_SHELL_DEFAULT_LABEL = "Dialog";
|
|
10237
13174
|
declare class TnDialogShellComponent implements OnInit {
|
|
13175
|
+
protected readonly labels: Signal<TnDialogChromeLabels>;
|
|
13176
|
+
/** Accessible name for the fullscreen toggle, following its current state. */
|
|
13177
|
+
protected readonly fullscreenLabel: Signal<string>;
|
|
10238
13178
|
title: _angular_core.InputSignal<string>;
|
|
10239
13179
|
showFullscreenButton: _angular_core.InputSignal<boolean>;
|
|
10240
13180
|
/**
|
|
@@ -10260,6 +13200,22 @@ declare class TnDialogShellComponent implements OnInit {
|
|
|
10260
13200
|
* provided (useful when more than one dialog can be open).
|
|
10261
13201
|
*/
|
|
10262
13202
|
testId: _angular_core.InputSignal<TnTestIdValue>;
|
|
13203
|
+
/**
|
|
13204
|
+
* Accessible name for the dialog itself, for a dialog that renders no `title`.
|
|
13205
|
+
*
|
|
13206
|
+
* A `title` outranks it: the heading is what the user can see, and
|
|
13207
|
+
* `aria-labelledby` wins the ARIA name calculation while it resolves. The
|
|
13208
|
+
* attribute is still rendered beside the heading rather than suppressed — see
|
|
13209
|
+
* `tnAccessibleName`, which owns that rule for every component in this
|
|
13210
|
+
* library, and the reason it is safer than the alternative.
|
|
13211
|
+
*/
|
|
13212
|
+
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
13213
|
+
/**
|
|
13214
|
+
* IDREF naming the dialog from text elsewhere on the page, for a dialog that
|
|
13215
|
+
* renders no `title`. Same precedence: a `title` wins, because it is the
|
|
13216
|
+
* visible heading.
|
|
13217
|
+
*/
|
|
13218
|
+
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
10263
13219
|
/**
|
|
10264
13220
|
* The `testId` base normalized to a flat segment array. Nothing is dropped
|
|
10265
13221
|
* here — `composeTestId` (via the `[tnTestId]` directive) filters falsy
|
|
@@ -10276,9 +13232,9 @@ declare class TnDialogShellComponent implements OnInit {
|
|
|
10276
13232
|
* close-button ids and lets automation target "all close buttons" with one
|
|
10277
13233
|
* selector.
|
|
10278
13234
|
*/
|
|
10279
|
-
protected closeTestId:
|
|
13235
|
+
protected closeTestId: Signal<(string | number | null | undefined)[]>;
|
|
10280
13236
|
/** Role-first test-id segments for the fullscreen button: `button-fullscreen[-<base>]`. */
|
|
10281
|
-
protected fullscreenTestId:
|
|
13237
|
+
protected fullscreenTestId: Signal<(string | number | null | undefined)[]>;
|
|
10282
13238
|
/** Stable id for the title heading, referenced by the dialog's aria-labelledby. */
|
|
10283
13239
|
readonly titleId: string;
|
|
10284
13240
|
isFullscreen: _angular_core.WritableSignal<boolean>;
|
|
@@ -10287,14 +13243,52 @@ declare class TnDialogShellComponent implements OnInit {
|
|
|
10287
13243
|
private document;
|
|
10288
13244
|
private host;
|
|
10289
13245
|
private data;
|
|
13246
|
+
/**
|
|
13247
|
+
* Whether there is a heading to render, and to name the dialog from.
|
|
13248
|
+
*
|
|
13249
|
+
* Trimmed, because a whitespace-only title renders a heading that looks empty
|
|
13250
|
+
* to a sighted user and names the dialog with nothing — which is the state
|
|
13251
|
+
* this ticket fixed, arriving by a second route.
|
|
13252
|
+
*/
|
|
13253
|
+
protected hasTitle: Signal<boolean>;
|
|
13254
|
+
/**
|
|
13255
|
+
* What the dialog is named by, in the order ARIA resolves: the visible
|
|
13256
|
+
* heading when there is one, then the caller's IDREF, then one passed to
|
|
13257
|
+
* `TnDialog.open` in the `DialogConfig`.
|
|
13258
|
+
*
|
|
13259
|
+
* The config is consulted so that the fallback below cannot overwrite a name
|
|
13260
|
+
* the opener supplied through CDK's own route. Reading it back through
|
|
13261
|
+
* `ref.config` rather than leaving CDK's binding to render it, because this
|
|
13262
|
+
* component writes both attributes onto that element and would otherwise
|
|
13263
|
+
* clear one it did not set.
|
|
13264
|
+
*
|
|
13265
|
+
* Guarded, even though `DialogRef.config` is non-optional: a shell rendered
|
|
13266
|
+
* directly in a consumer's test gets its `DialogRef` from a mock provider,
|
|
13267
|
+
* which supplies the methods but no config. Naming then falls back to the
|
|
13268
|
+
* component's own inputs, which is what such a test is exercising anyway.
|
|
13269
|
+
*/
|
|
13270
|
+
private resolvedAriaLabelledby;
|
|
13271
|
+
/** An explicit label, from this component's input or from the `DialogConfig`. */
|
|
13272
|
+
private explicitAriaLabel;
|
|
13273
|
+
/**
|
|
13274
|
+
* The name to render as `aria-label`, or `null` to render none — and the
|
|
13275
|
+
* dev-mode warning when the dialog has no name from any route.
|
|
13276
|
+
*
|
|
13277
|
+
* Both halves live in `../a11y/accessible-name`, shared with `tn-side-panel`,
|
|
13278
|
+
* `tn-drawer` and the three progressbars, where the reasoning for each branch
|
|
13279
|
+
* is set out. `title` reaches it as the `ariaLabelledby` above, so a titled
|
|
13280
|
+
* dialog is named, takes no fallback and raises no warning.
|
|
13281
|
+
*/
|
|
13282
|
+
private resolvedAriaLabel;
|
|
10290
13283
|
constructor();
|
|
13284
|
+
private applyName;
|
|
10291
13285
|
ngOnInit(): void;
|
|
10292
13286
|
close(result?: unknown): void;
|
|
10293
13287
|
toggleFullscreen(): void;
|
|
10294
13288
|
private enterFullscreen;
|
|
10295
13289
|
private exitFullscreen;
|
|
10296
13290
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnDialogShellComponent, never>;
|
|
10297
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDialogShellComponent, "tn-dialog-shell", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "showFullscreenButton": { "alias": "showFullscreenButton"; "required": false; "isSignal": true; }; "showCloseButton": { "alias": "showCloseButton"; "required": false; "isSignal": true; }; "hideContent": { "alias": "hideContent"; "required": false; "isSignal": true; }; "hideActions": { "alias": "hideActions"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, {}, never, ["*", "[tnDialogAction]"], true, never>;
|
|
13291
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnDialogShellComponent, "tn-dialog-shell", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "showFullscreenButton": { "alias": "showFullscreenButton"; "required": false; "isSignal": true; }; "showCloseButton": { "alias": "showCloseButton"; "required": false; "isSignal": true; }; "hideContent": { "alias": "hideContent"; "required": false; "isSignal": true; }; "hideActions": { "alias": "hideActions"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, {}, never, ["*", "[tnDialogAction]"], true, never>;
|
|
10298
13292
|
}
|
|
10299
13293
|
|
|
10300
13294
|
/**
|
|
@@ -10346,7 +13340,9 @@ declare class TnDialogHarness extends ComponentHarness {
|
|
|
10346
13340
|
*/
|
|
10347
13341
|
static with(options?: DialogHarnessFilters): HarnessPredicate<TnDialogHarness>;
|
|
10348
13342
|
/**
|
|
10349
|
-
* Gets the dialog's title text
|
|
13343
|
+
* Gets the dialog's title text, or `''` for a dialog opened with no title —
|
|
13344
|
+
* which renders no heading at all (#219), and is named by `ariaLabel` or
|
|
13345
|
+
* `ariaLabelledby` instead.
|
|
10350
13346
|
*
|
|
10351
13347
|
* @returns Promise resolving to the dialog title.
|
|
10352
13348
|
*
|
|
@@ -10436,8 +13432,11 @@ declare class TnDialogHarness extends ComponentHarness {
|
|
|
10436
13432
|
*/
|
|
10437
13433
|
toggleFullscreen(): Promise<void>;
|
|
10438
13434
|
/**
|
|
10439
|
-
* Whether the dialog is currently in fullscreen mode
|
|
10440
|
-
*
|
|
13435
|
+
* Whether the dialog is currently in fullscreen mode, read from the fullscreen toggle's
|
|
13436
|
+
* `data-fullscreen`. Deliberately not derived from the button's `aria-label`: that label is
|
|
13437
|
+
* app-configurable through `TN_DIALOG_CHROME_LABELS`, so matching display text would report
|
|
13438
|
+
* `false` forever the moment a consumer translated the chrome — silently, in exactly the apps
|
|
13439
|
+
* the token exists for. Same call `TnTableHarness.getCardSortDirection` makes.
|
|
10441
13440
|
*
|
|
10442
13441
|
* @returns Promise resolving to true if fullscreen, false if not or if no fullscreen button.
|
|
10443
13442
|
*/
|
|
@@ -10488,6 +13487,53 @@ declare class TnDialogTesting {
|
|
|
10488
13487
|
static rootLoader(fixture: ComponentFixture<unknown>): HarnessLoader;
|
|
10489
13488
|
}
|
|
10490
13489
|
|
|
13490
|
+
/**
|
|
13491
|
+
* The accessible name an open panel falls back to when it has no `title` and the
|
|
13492
|
+
* caller named neither `ariaLabel` nor `ariaLabelledby` (#214).
|
|
13493
|
+
*
|
|
13494
|
+
* `title` defaults to `''`, so the DEFAULT rendering of this component was a
|
|
13495
|
+
* `role="dialog"` with `aria-labelledby` pointing at an empty `<h2>` — measured
|
|
13496
|
+
* as an `aria-dialog-name` violation, alongside `empty-heading`. A dialog with no
|
|
13497
|
+
* name is announced as "dialog" and nothing else, which is the whole of what a
|
|
13498
|
+
* screen-reader user gets told about a surface that just covered the page.
|
|
13499
|
+
*
|
|
13500
|
+
* Withholding `role="dialog"` until there is a name would be the other way to
|
|
13501
|
+
* clear the rule, and it is worse: the panel traps focus either way, so a
|
|
13502
|
+
* listener would be moved into a region with no announcement that anything had
|
|
13503
|
+
* opened. A generic name is still a poor one, so it is paired with the dev-mode
|
|
13504
|
+
* warning `tnAccessibleName` raises.
|
|
13505
|
+
*
|
|
13506
|
+
* Exported so specs assert against it by name rather than by a copied literal.
|
|
13507
|
+
*/
|
|
13508
|
+
declare const TN_SIDE_PANEL_DEFAULT_LABEL = "Side panel";
|
|
13509
|
+
/**
|
|
13510
|
+
* The name given to the scrolling content region once it becomes focusable
|
|
13511
|
+
* (#248).
|
|
13512
|
+
*
|
|
13513
|
+
* A focusable element with no accessible name is announced as a bare "group",
|
|
13514
|
+
* which tells a listener that something has been reached and nothing about what
|
|
13515
|
+
* it is. It names the region rather than repeating the panel's own title: the
|
|
13516
|
+
* dialog announces that on entry, so a second copy of it here would say the
|
|
13517
|
+
* same words twice and still not distinguish the part that scrolls.
|
|
13518
|
+
*
|
|
13519
|
+
* Overridable through `contentAriaLabel`, on the same reasoning as
|
|
13520
|
+
* `closeButtonAriaLabel` — a string this library renders into a consumer's UI
|
|
13521
|
+
* has to be translatable. Exported so specs assert against it by name rather
|
|
13522
|
+
* than by a copied literal.
|
|
13523
|
+
*/
|
|
13524
|
+
declare const TN_SIDE_PANEL_CONTENT_LABEL = "Panel content";
|
|
13525
|
+
/**
|
|
13526
|
+
* How far the content has to exceed the region before the region counts as
|
|
13527
|
+
* scrolling (#248).
|
|
13528
|
+
*
|
|
13529
|
+
* **This is now `TN_SCROLLABLE_REGION_TOLERANCE_PX`**, which is axe's own 13px
|
|
13530
|
+
* buffer and lives with the measurement it belongs to (#270). The alias stays
|
|
13531
|
+
* because it is what `side-panel-scrollable-content.spec.ts` pins against axe
|
|
13532
|
+
* from both sides, and that spec is a guard on this component rather than on
|
|
13533
|
+
* the helper — see `../a11y/scrollable-region.ts` for what the number is and
|
|
13534
|
+
* why it is copied from the rule at all.
|
|
13535
|
+
*/
|
|
13536
|
+
declare const TN_SIDE_PANEL_OVERFLOW_TOLERANCE_PX = 13;
|
|
10491
13537
|
/**
|
|
10492
13538
|
* Directive to mark an element as a side-panel footer action.
|
|
10493
13539
|
*
|
|
@@ -10517,12 +13563,49 @@ declare class TnSidePanelHeaderActionDirective {
|
|
|
10517
13563
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnSidePanelHeaderActionDirective, never>;
|
|
10518
13564
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TnSidePanelHeaderActionDirective, "[tnSidePanelHeaderAction]", never, {}, {}, never, never, true, never>;
|
|
10519
13565
|
}
|
|
13566
|
+
/**
|
|
13567
|
+
* A modal side panel: `role="dialog"` with `aria-modal="true"`, focus trapped
|
|
13568
|
+
* while it is open and restored to the opener when it closes.
|
|
13569
|
+
*
|
|
13570
|
+
* FOCUS ON OPEN
|
|
13571
|
+
* -------------
|
|
13572
|
+
* Opening moves focus to the panel container, whatever you projected into it,
|
|
13573
|
+
* so that a screen reader announces the dialog it has just entered before any
|
|
13574
|
+
* control in it. The first Tab then reaches the close button.
|
|
13575
|
+
*
|
|
13576
|
+
* **`[cdkFocusInitial]` is not honoured** (#227). It used to be, through the
|
|
13577
|
+
* CDK auto-capture this replaced, and `cdkTrapFocus` is still on the panel — so
|
|
13578
|
+
* the marker looks like it should work and does not. To focus a control of your
|
|
13579
|
+
* own, focus it yourself once the panel is open; the component leaves focus
|
|
13580
|
+
* alone as soon as it is inside the panel. `lib/a11y/initial-focus.ts` holds
|
|
13581
|
+
* the reasoning for capturing the container rather than a control.
|
|
13582
|
+
*/
|
|
10520
13583
|
declare class TnSidePanelComponent implements OnDestroy {
|
|
10521
13584
|
private iconRegistry;
|
|
10522
13585
|
private document;
|
|
10523
13586
|
private destroyRef;
|
|
10524
13587
|
private overlayRef;
|
|
13588
|
+
private panelRef;
|
|
13589
|
+
private contentRef;
|
|
10525
13590
|
protected initialized: _angular_core.WritableSignal<boolean>;
|
|
13591
|
+
/**
|
|
13592
|
+
* Whether the content region carries the tab stop, its role and its name
|
|
13593
|
+
* (#248) — which is NOT the same question as whether it currently overflows.
|
|
13594
|
+
*
|
|
13595
|
+
* The measurement, the two observers that keep it current and the focus rule
|
|
13596
|
+
* that decides when the attributes may be taken off again are all
|
|
13597
|
+
* `tnScrollableRegion`'s (#270); this component decides only what to put on
|
|
13598
|
+
* the element, which is the part that differs between the five regions in
|
|
13599
|
+
* this library that scroll. `../a11y/scrollable-region.ts` sets out why the
|
|
13600
|
+
* answer is held true while the region has focus, and why `role` and
|
|
13601
|
+
* `aria-label` are gated on the same signal as `tabindex` rather than left on.
|
|
13602
|
+
*
|
|
13603
|
+
* Read in `afterNextRender`, which is where the helper takes its first
|
|
13604
|
+
* measurement — before the overlay is portaled to `<body>` below, which does
|
|
13605
|
+
* not affect it: `.tn-side-panel__overlay` is `position: fixed; inset: 0`, so
|
|
13606
|
+
* its size comes from the viewport rather than from its parent.
|
|
13607
|
+
*/
|
|
13608
|
+
protected contentKeyboardReachable: _angular_core.Signal<boolean>;
|
|
10526
13609
|
open: _angular_core.ModelSignal<boolean>;
|
|
10527
13610
|
title: _angular_core.InputSignal<string>;
|
|
10528
13611
|
width: _angular_core.InputSignal<string>;
|
|
@@ -10552,13 +13635,83 @@ declare class TnSidePanelComponent implements OnDestroy {
|
|
|
10552
13635
|
* of context otherwise hears only "Dismiss".
|
|
10553
13636
|
*/
|
|
10554
13637
|
closeButtonAriaLabel: _angular_core.InputSignal<string>;
|
|
13638
|
+
/**
|
|
13639
|
+
* Accessible name for the scrolling content region, which is named only while
|
|
13640
|
+
* it is focusable — see `TN_SIDE_PANEL_CONTENT_LABEL`. Override it to
|
|
13641
|
+
* translate it, or to say what the region holds ("Dataset properties").
|
|
13642
|
+
*/
|
|
13643
|
+
contentAriaLabel: _angular_core.InputSignal<string>;
|
|
13644
|
+
/**
|
|
13645
|
+
* Accessible name for the panel itself, for a panel that renders no `title`.
|
|
13646
|
+
*
|
|
13647
|
+
* A `title` outranks it: the heading is what the user can see, and
|
|
13648
|
+
* `aria-labelledby` wins the ARIA name calculation while it resolves. The
|
|
13649
|
+
* attribute is still rendered beside the heading rather than suppressed — see
|
|
13650
|
+
* `tnAccessibleName`, which owns that rule for every component in this
|
|
13651
|
+
* library, and the reason it is safer than the alternative.
|
|
13652
|
+
*/
|
|
13653
|
+
ariaLabel: _angular_core.InputSignal<string | null>;
|
|
13654
|
+
/**
|
|
13655
|
+
* IDREF naming the panel from text elsewhere on the page, for a panel that
|
|
13656
|
+
* renders no `title`. Same precedence: a `title` wins, because it is the
|
|
13657
|
+
* visible heading.
|
|
13658
|
+
*/
|
|
13659
|
+
ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
13660
|
+
/**
|
|
13661
|
+
* Fires once the panel has finished opening.
|
|
13662
|
+
*
|
|
13663
|
+
* "Finished" means the open transition ended, OR that it was going to take
|
|
13664
|
+
* longer than `TN_TRANSITION_FALLBACK_MS` to say so — which is what a user
|
|
13665
|
+
* with `prefers-reduced-motion: reduce` gets, since this component's own
|
|
13666
|
+
* stylesheet zeroes the duration for them and a transition that does not run
|
|
13667
|
+
* fires no `transitionend` (#218). A consumer may assume the panel has
|
|
13668
|
+
* reached its open state and that `open()` is true; it may NOT assume the
|
|
13669
|
+
* animation is visually complete, because for that user there was none.
|
|
13670
|
+
*/
|
|
10555
13671
|
opened: _angular_core.OutputEmitterRef<void>;
|
|
13672
|
+
/**
|
|
13673
|
+
* Fires once the panel has finished closing. Same guarantee as `opened`, and
|
|
13674
|
+
* the same caveat: it reports the state, not the animation.
|
|
13675
|
+
*
|
|
13676
|
+
* Focus restoration does NOT hang off this — it happens as soon as the panel
|
|
13677
|
+
* closes (#214). See the effect in the constructor.
|
|
13678
|
+
*/
|
|
10556
13679
|
closed: _angular_core.OutputEmitterRef<void>;
|
|
10557
13680
|
private actionContent;
|
|
10558
13681
|
protected hasActions: _angular_core.Signal<boolean>;
|
|
10559
13682
|
readonly panelId: string;
|
|
10560
13683
|
readonly titleId: string;
|
|
13684
|
+
/**
|
|
13685
|
+
* Whether there is a heading to render, and to name the dialog from.
|
|
13686
|
+
*
|
|
13687
|
+
* Trimmed, because a whitespace-only title renders a heading that looks empty
|
|
13688
|
+
* to a sighted user and names the dialog with nothing — which is the state
|
|
13689
|
+
* that failed `aria-dialog-name` before #214, arriving by a second route.
|
|
13690
|
+
*/
|
|
13691
|
+
protected hasTitle: _angular_core.Signal<boolean>;
|
|
13692
|
+
/**
|
|
13693
|
+
* What the dialog is named by, in the order ARIA resolves: the visible heading
|
|
13694
|
+
* when there is one, the caller's IDREF otherwise.
|
|
13695
|
+
*/
|
|
13696
|
+
protected resolvedAriaLabelledby: _angular_core.Signal<string | null>;
|
|
13697
|
+
/**
|
|
13698
|
+
* The name to render as `aria-label`, or `null` to render none — and the
|
|
13699
|
+
* dev-mode warning when the panel has no name from any route.
|
|
13700
|
+
*
|
|
13701
|
+
* Both halves live in `../a11y/accessible-name`, shared with the three
|
|
13702
|
+
* progressbars, where the reasoning for each branch is set out. `title` reaches
|
|
13703
|
+
* it as the `ariaLabelledby` above, so a titled panel is named, takes no
|
|
13704
|
+
* fallback and raises no warning.
|
|
13705
|
+
*/
|
|
13706
|
+
protected resolvedAriaLabel: _angular_core.Signal<string | null>;
|
|
10561
13707
|
private previouslyFocusedElement;
|
|
13708
|
+
/**
|
|
13709
|
+
* Decides when an open or a close counts as finished, so that the outputs
|
|
13710
|
+
* above fire exactly once per change whether or not a transition ran. A field
|
|
13711
|
+
* initializer rather than the constructor, because it registers an `effect`
|
|
13712
|
+
* and so needs an injection context.
|
|
13713
|
+
*/
|
|
13714
|
+
private lifecycle;
|
|
10562
13715
|
constructor();
|
|
10563
13716
|
ngOnDestroy(): void;
|
|
10564
13717
|
protected dismiss(): void;
|
|
@@ -10568,7 +13721,7 @@ declare class TnSidePanelComponent implements OnDestroy {
|
|
|
10568
13721
|
private restoreFocus;
|
|
10569
13722
|
private registerMdiIcons;
|
|
10570
13723
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnSidePanelComponent, never>;
|
|
10571
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnSidePanelComponent, "tn-side-panel", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "hasBackdrop": { "alias": "hasBackdrop"; "required": false; "isSignal": true; }; "closeOnBackdropClick": { "alias": "closeOnBackdropClick"; "required": false; "isSignal": true; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; "isSignal": true; }; "closeGuard": { "alias": "closeGuard"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "closeButtonTestId": { "alias": "closeButtonTestId"; "required": false; "isSignal": true; }; "closeButtonAriaLabel": { "alias": "closeButtonAriaLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "opened": "opened"; "closed": "closed"; }, ["actionContent"], ["[tnSidePanelHeaderAction]", "*", "[tnSidePanelAction]"], true, never>;
|
|
13724
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnSidePanelComponent, "tn-side-panel", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "hasBackdrop": { "alias": "hasBackdrop"; "required": false; "isSignal": true; }; "closeOnBackdropClick": { "alias": "closeOnBackdropClick"; "required": false; "isSignal": true; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; "isSignal": true; }; "closeGuard": { "alias": "closeGuard"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "closeButtonTestId": { "alias": "closeButtonTestId"; "required": false; "isSignal": true; }; "closeButtonAriaLabel": { "alias": "closeButtonAriaLabel"; "required": false; "isSignal": true; }; "contentAriaLabel": { "alias": "contentAriaLabel"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "ariaLabelledby"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "opened": "opened"; "closed": "closed"; }, ["actionContent"], ["[tnSidePanelHeaderAction]", "*", "[tnSidePanelAction]"], true, never>;
|
|
10572
13725
|
}
|
|
10573
13726
|
|
|
10574
13727
|
interface SidePanelHarnessFilters extends BaseHarnessFilters {
|
|
@@ -10594,7 +13747,17 @@ declare class TnSidePanelHarness extends ComponentHarness {
|
|
|
10594
13747
|
* Uses the data-tn-panel attribute to correlate the host with its overlay.
|
|
10595
13748
|
*/
|
|
10596
13749
|
private getOverlay;
|
|
10597
|
-
/**
|
|
13750
|
+
/**
|
|
13751
|
+
* Get the panel title text, or `''` for a panel that renders no title.
|
|
13752
|
+
*
|
|
13753
|
+
* `locatorForOptional`, not `locatorFor`: since #214 a panel with no `title`
|
|
13754
|
+
* renders no heading element at all — an `<h2>` with nothing in it is an
|
|
13755
|
+
* `empty-heading` violation — and `locatorFor` throws on a selector that
|
|
13756
|
+
* matches nothing. That would turn `getTitle()` into an error rather than an
|
|
13757
|
+
* empty string, and would make `TnSidePanelHarness.with({title})` REJECT on an
|
|
13758
|
+
* untitled panel instead of simply not matching it, so a filter aimed at one
|
|
13759
|
+
* panel would fail on the presence of another.
|
|
13760
|
+
*/
|
|
10598
13761
|
getTitle(): Promise<string>;
|
|
10599
13762
|
/** Whether the panel is currently open. */
|
|
10600
13763
|
isOpen(): Promise<boolean>;
|
|
@@ -10618,6 +13781,17 @@ declare class TnStepComponent {
|
|
|
10618
13781
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnStepComponent, "tn-step", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "optional": { "alias": "optional"; "required": false; "isSignal": true; }; "completed": { "alias": "completed"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
10619
13782
|
}
|
|
10620
13783
|
|
|
13784
|
+
/**
|
|
13785
|
+
* Screen-reader text for a step whose header shows the error glyph.
|
|
13786
|
+
*
|
|
13787
|
+
* Exported so specs assert by name rather than by a copied literal, the same
|
|
13788
|
+
* reason `TN_SPINNER_DEFAULT_LABEL` is. It is not an override point — nothing
|
|
13789
|
+
* reads a token or an input in its place, so a consumer cannot change what the
|
|
13790
|
+
* stepper says here.
|
|
13791
|
+
*/
|
|
13792
|
+
declare const TN_STEPPER_STATUS_ERROR = "Error";
|
|
13793
|
+
/** Screen-reader text for a step whose header shows the completed state. */
|
|
13794
|
+
declare const TN_STEPPER_STATUS_COMPLETED = "Completed";
|
|
10621
13795
|
declare class TnStepperComponent {
|
|
10622
13796
|
orientation: _angular_core.InputSignal<"auto" | "horizontal" | "vertical">;
|
|
10623
13797
|
linear: _angular_core.InputSignal<boolean>;
|
|
@@ -10645,6 +13819,7 @@ declare class TnStepperComponent {
|
|
|
10645
13819
|
isVertical: _angular_core.Signal<boolean>;
|
|
10646
13820
|
readonly stepEditable: _angular_core.Signal<boolean[]>;
|
|
10647
13821
|
readonly stepGated: _angular_core.Signal<boolean[]>;
|
|
13822
|
+
readonly stepStatusText: _angular_core.Signal<(string | null)[]>;
|
|
10648
13823
|
selectStep(index: number): void;
|
|
10649
13824
|
canSelectStep(index: number): boolean;
|
|
10650
13825
|
next(): void;
|
|
@@ -11569,7 +14744,11 @@ declare enum TnToastPosition {
|
|
|
11569
14744
|
Bottom = "bottom"
|
|
11570
14745
|
}
|
|
11571
14746
|
interface TnToastConfig {
|
|
11572
|
-
/**
|
|
14747
|
+
/**
|
|
14748
|
+
* How long the toast stays on screen, in milliseconds, counted from when it
|
|
14749
|
+
* appears rather than from the `open()` call. Default: 4000. Set to 0 to
|
|
14750
|
+
* disable auto-dismissal.
|
|
14751
|
+
*/
|
|
11573
14752
|
duration?: number;
|
|
11574
14753
|
/** Visual style of the toast. Default: TnToastType.Info. */
|
|
11575
14754
|
type?: TnToastType;
|
|
@@ -11591,12 +14770,46 @@ declare class TnToastComponent {
|
|
|
11591
14770
|
position: _angular_core.WritableSignal<TnToastPosition>;
|
|
11592
14771
|
visible: _angular_core.WritableSignal<boolean>;
|
|
11593
14772
|
icon: _angular_core.Signal<string>;
|
|
14773
|
+
/**
|
|
14774
|
+
* The live-region role, which is also the only thing declaring how urgently the
|
|
14775
|
+
* toast is announced: `alert` implies `aria-live="assertive"` and `status`
|
|
14776
|
+
* implies `polite`.
|
|
14777
|
+
*
|
|
14778
|
+
* The template carried `role="alert"` and `aria-live="polite"` together (#190).
|
|
14779
|
+
* An explicit `aria-live` overrides the role's implicit one, so every toast was
|
|
14780
|
+
* announced politely — including `error`, the one type that needs to interrupt.
|
|
14781
|
+
* Deriving the role from the type and leaving `aria-live` off keeps a single
|
|
14782
|
+
* source: there is no second attribute left to disagree with this one.
|
|
14783
|
+
*
|
|
14784
|
+
* WHICH types get `alert` is shared with banner rather than decided here
|
|
14785
|
+
* (#194): #190 mapped `warning` to `status` while banner mapped it to
|
|
14786
|
+
* `alert`, and `../a11y/live-region.ts` is now the one place that answers it.
|
|
14787
|
+
*/
|
|
14788
|
+
role: _angular_core.Signal<"alert" | "status">;
|
|
11594
14789
|
onAction: () => void;
|
|
11595
14790
|
onDismiss: () => void;
|
|
11596
14791
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnToastComponent, never>;
|
|
11597
14792
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnToastComponent, "tn-toast", never, {}, {}, never, never, true, never>;
|
|
11598
14793
|
}
|
|
11599
14794
|
|
|
14795
|
+
/**
|
|
14796
|
+
* How long the live region is left attached and empty before the message is
|
|
14797
|
+
* written into it, in milliseconds.
|
|
14798
|
+
*
|
|
14799
|
+
* WHY A TIMER AND NOT THE ANIMATION FRAME THE TRANSITION RIDES
|
|
14800
|
+
* -----------------------------------------------------------
|
|
14801
|
+
* A `requestAnimationFrame` callback runs BEFORE that frame's style, layout and
|
|
14802
|
+
* accessibility-tree update. Attaching the region in one task and populating it
|
|
14803
|
+
* from the next frame's callback therefore commits both mutations in a single
|
|
14804
|
+
* accessibility-tree update — which is the already-populated insertion this
|
|
14805
|
+
* deferral exists to avoid, still there and harder to see. The region needs a
|
|
14806
|
+
* rendering pass of its own, which means yielding past one.
|
|
14807
|
+
*
|
|
14808
|
+
* 100ms is what `@angular/cdk`'s `LiveAnnouncer` waits before writing into its
|
|
14809
|
+
* own region — a dependency of this project already, and the closest thing to a
|
|
14810
|
+
* measured number available.
|
|
14811
|
+
*/
|
|
14812
|
+
declare const TN_TOAST_ANNOUNCE_DELAY_MS = 100;
|
|
11600
14813
|
declare class TnToastRef {
|
|
11601
14814
|
private readonly _onAction;
|
|
11602
14815
|
private readonly _afterDismissed;
|
|
@@ -11619,6 +14832,12 @@ declare class TnToastService {
|
|
|
11619
14832
|
/**
|
|
11620
14833
|
* Opens a toast notification.
|
|
11621
14834
|
*
|
|
14835
|
+
* The toast is attached synchronously, but its message and its enter
|
|
14836
|
+
* transition both land `TN_TOAST_ANNOUNCE_DELAY_MS` later — the delay is what
|
|
14837
|
+
* makes the text a *change* to a live region a screen reader is already
|
|
14838
|
+
* watching. A test reading `.tn-toast__message` must let that elapse; one
|
|
14839
|
+
* asserting on the call rather than the DOM should use `TnToastMock`.
|
|
14840
|
+
*
|
|
11622
14841
|
* @param message The message to display.
|
|
11623
14842
|
* @param actionOrConfig Optional action button text, or config object.
|
|
11624
14843
|
* @param config Optional config when action is provided as second arg.
|
|
@@ -12001,5 +15220,5 @@ declare const TN_THEME_DEFINITIONS: readonly TnThemeDefinition[];
|
|
|
12001
15220
|
*/
|
|
12002
15221
|
declare const THEME_MAP: Map<TnTheme, TnThemeDefinition>;
|
|
12003
15222
|
|
|
12004
|
-
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, THEME_MAP, THEME_STORAGE_KEY, TN_CALENDAR_INTL, TN_CALENDAR_INTL_DEFAULTS, TN_FORM_FIELD_CONTEXT, TN_FORM_FIELD_ERRORS, TN_RADIO_GROUP, 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, TnCalendarCellHarness, TnCalendarComponent, TnCalendarHarness, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerHarness, 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, TnNestedTreeDataSource, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioGroupComponent, TnRadioGroupHarness, TnRadioHarness, TnRowActionsDefDirective, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnStepperHarness, TnStepperNextDirective, TnStepperPreviousDirective, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTableTesting, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeHarness, TnTreeNodeComponent, TnTreeNodeHarness, TnTreeNodeOutletDirective, TnTreeVirtualScrollNodeOutletDirective, TnTreeVirtualScrollViewComponent, TnTreeVirtualScrollViewHarness, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, allowsCurrentDirectorySelection, composeTestId, controlTestId, createFlatTreeControl, createLucideLibrary, createNestedTreeControl, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, defaultTreeItemSize, formatSize, getSelectableTypes, injectTnCalendarIntl, injectTnFormFieldAria, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
12005
|
-
export type { AutocompleteHarnessFilters, BannerHarnessFilters, ButtonHarnessFilters, ButtonToggleHarnessFilters, CalendarCell, CalendarCellFill, CalendarCellHarnessFilters, CalendarHarnessFilters, CheckboxHarnessFilters, ChipColor, ChipHarnessFilters, DateInputHarnessFilters, DateRange, DateRangeInputHarnessFilters, DialogHarnessFilters, EmptyHarnessFilters, ExpansionPanelHarnessFilters, FileInputHarnessFilters, FilePickerCallbacks, FilePickerCreateAction, FilePickerCreateActionEvent, FilePickerError, FilePickerHarnessFilters, FilePickerMode, FileSystemItem, FileSystemItemType, FlatTreeControlOptions, FormFieldHarnessFilters, FormSectionHarnessFilters, IconButtonHarnessFilters, IconHarnessFilters, IconLibrary, IconLibraryType, IconResult, IconSize, IconSource, IconTestingMockOverrides, InputHarnessFilters, KeyCombination, LabelMarkupSegment, LabelMarkupSegmentType, LabelType, LucideIconOptions, MenuHarnessFilters, MockIconRegistry, MockSpriteLoader, NestedTreeControlOptions, PathSegment, PlatformType, ProgressBarMode, RadioGroupHarnessFilters, RadioHarnessFilters, ResolvedIcon, SelectHarnessFilters, ShortcutHandler, SidePanelHarnessFilters, SizeStandard, SlideToggleColor, SlideToggleHarnessFilters, SpinnerMode, SpriteConfig, StepperHarnessFilters, SubscriptSizing, TabChangeEvent, TabHarnessFilters, TabPanelHarnessFilters, TabsHarnessFilters, TnAutocompleteOption, TnBannerType, TnButtonToggleType, TnCalendarIntl, TnCalendarIntlInput, TnCalendarView, TnCardAction, TnCardControl, TnCardFooterLink, TnCardHeaderStatus, TnChipInputHarnessFilters, TnChipInputOption, TnConfirmDialogData, TnDialogDefaults, TnDialogOpenTarget, TnDrawerMode, TnDrawerPosition, TnEmptySize, TnFlatTreeNode, TnFormFieldAriaBindings, TnFormFieldContext, TnFormFieldErrorMessage, TnFormFieldErrorMessages, TnFormFieldErrorResolver, TnMenuItem, TnOptionTestIdSource, TnRadioGroupApi, TnRadioOption, TnSelectOption, TnSelectOptionGroup, TnSelectionChange, TnSortEvent, TnTableDataProvider, TnTableDataSource, TnTableHarnessFilters, TnTableMobileLayout, TnTablePagerHarnessFilters, TnTablePagerLabels, TnTablePagination, TnTestAttrName, TnTestIdValue, TnThemeDefinition, TnToastCall, TnToastConfig, TnTreeExpansion, TnTreeHarnessFilters, TnTreeNodeHarnessFilters, TnTreeVirtualNodeData, TnTreeVirtualScrollViewHarnessFilters, TooltipPosition, YearCell };
|
|
15223
|
+
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, THEME_MAP, THEME_STORAGE_KEY, TN_AUTOCOMPLETE_DEFAULT_LABELS, TN_AUTOCOMPLETE_LABELS, TN_BRANDED_SPINNER_DEFAULT_LABEL, TN_CALENDAR_INTL, TN_CALENDAR_INTL_DEFAULTS, TN_DIALOG_CHROME_LABELS, TN_DIALOG_DEFAULT_CHROME_LABELS, TN_DIALOG_SHELL_DEFAULT_LABEL, TN_DRAWER_CONTENT_LABEL, TN_DRAWER_DEFAULT_LABEL, TN_FORM_FIELD_CONTEXT, TN_FORM_FIELD_DISMISSIBLE_ERRORS, TN_FORM_FIELD_ERRORS, TN_FORM_LIST_CONTEXT, TN_PARTICLE_PROGRESS_BAR_DEFAULT_LABEL, TN_PROGRESS_BAR_DEFAULT_LABEL, TN_RADIO_GROUP, TN_SELECT_DEFAULT_LABELS, TN_SELECT_LABELS, TN_SIDE_PANEL_CONTENT_LABEL, TN_SIDE_PANEL_DEFAULT_LABEL, TN_SIDE_PANEL_OVERFLOW_TOLERANCE_PX, TN_SPINNER_DEFAULT_LABEL, TN_STEPPER_STATUS_COMPLETED, TN_STEPPER_STATUS_ERROR, TN_TABLE_DEFAULT_LABELS, TN_TABLE_LABELS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TABLE_SCROLL_REGION_LABEL, TN_TAB_PANEL_CONTENT_LABEL, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TN_TOAST_ANNOUNCE_DELAY_MS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerActionHarness, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarCellHarness, TnCalendarComponent, TnCalendarHarness, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerHarness, TnFilePickerPopupComponent, TnFormErrorsComponent, TnFormErrorsHarness, TnFormFieldComponent, TnFormFieldHarness, TnFormListComponent, TnFormListHarness, TnFormListItemComponent, TnFormListItemHarness, 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, TnNestedTreeDataSource, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioGroupComponent, TnRadioGroupHarness, TnRadioHarness, TnRowActionsDefDirective, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnStepperHarness, TnStepperNextDirective, TnStepperPreviousDirective, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTableTesting, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTooltipHarness, TnTooltipTesting, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeHarness, TnTreeNodeComponent, TnTreeNodeHarness, TnTreeNodeOutletDirective, TnTreeVirtualScrollNodeOutletDirective, TnTreeVirtualScrollViewComponent, TnTreeVirtualScrollViewHarness, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, allowsCurrentDirectorySelection, canonicalizeSize, composeTestId, controlTestId, createFlatTreeControl, createLucideLibrary, createNestedTreeControl, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, defaultTreeItemSize, formatSize, formatSizeExact, formatSizeForEditing, getSelectableTypes, injectTnCalendarIntl, injectTnFormFieldAria, injectTnLabels, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, parseSizeParts, registerLucideIcons, resolveErrorMessage, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
15224
|
+
export type { AutocompleteHarnessFilters, BannerActionHarnessFilters, BannerHarnessFilters, ButtonHarnessFilters, ButtonToggleHarnessFilters, CalendarCell, CalendarCellFill, CalendarCellHarnessFilters, CalendarHarnessFilters, CheckboxHarnessFilters, ChipColor, ChipHarnessFilters, DateInputHarnessFilters, DateRange, DateRangeInputHarnessFilters, DialogHarnessFilters, EmptyHarnessFilters, ExpansionPanelHarnessFilters, FileInputHarnessFilters, FilePickerCallbacks, FilePickerCreateAction, FilePickerCreateActionEvent, FilePickerError, FilePickerHarnessFilters, FilePickerMode, FileSystemItem, FileSystemItemType, FlatTreeControlOptions, FormFieldHarnessFilters, FormSectionHarnessFilters, IconButtonHarnessFilters, IconHarnessFilters, IconLibrary, IconLibraryType, IconResult, IconSize, IconSource, IconTestingMockOverrides, InputHarnessFilters, KeyCombination, LabelMarkupSegment, LabelMarkupSegmentType, LabelType, LucideIconOptions, MenuHarnessFilters, MockIconRegistry, MockSpriteLoader, NestedTreeControlOptions, ParsedSize, PathSegment, PlatformType, ProgressBarMode, RadioGroupHarnessFilters, RadioHarnessFilters, ResolveErrorMessageOptions, ResolvedIcon, SelectHarnessFilters, ShortcutHandler, SidePanelHarnessFilters, SizeStandard, SlideToggleColor, SlideToggleHarnessFilters, SpinnerMode, SpriteConfig, StepperHarnessFilters, SubscriptSizing, TabChangeEvent, TabHarnessFilters, TabPanelHarnessFilters, TabsHarnessFilters, TnAutocompleteLabels, TnAutocompleteOption, TnBannerType, TnButtonToggleType, TnCalendarIntl, TnCalendarIntlInput, TnCalendarView, TnCardAction, TnCardControl, TnCardFooterLink, TnCardHeaderStatus, TnChipInputHarnessFilters, TnChipInputOption, TnConfirmDialogData, TnDialogChromeLabels, TnDialogDefaults, TnDialogOpenTarget, TnDrawerMode, TnDrawerPosition, TnEmptySize, TnFlatTreeNode, TnFormErrorsHarnessFilters, TnFormFieldAriaBindings, TnFormFieldContext, TnFormFieldErrorMessage, TnFormFieldErrorMessages, TnFormFieldErrorResolver, TnFormListContext, TnFormListHarnessFilters, TnFormListItemHarnessFilters, TnMenuItem, TnOptionTestIdSource, TnRadioGroupApi, TnRadioOption, TnSelectLabels, TnSelectOption, TnSelectOptionGroup, TnSelectionChange, TnSortEvent, TnTableDataProvider, TnTableDataSource, TnTableHarnessFilters, TnTableLabels, TnTableMobileLayout, TnTablePagerHarnessFilters, TnTablePagerLabels, TnTablePagination, TnTestAttrName, TnTestIdValue, TnThemeDefinition, TnToastCall, TnToastConfig, TnTreeExpansion, TnTreeHarnessFilters, TnTreeNodeHarnessFilters, TnTreeVirtualNodeData, TnTreeVirtualScrollViewHarnessFilters, TooltipHarnessFilters, TooltipPosition, YearCell };
|