@lifecorner/angular-ui-kit 3.31.0 → 3.33.0

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.
Files changed (51) hide show
  1. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs +2 -2
  2. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
  3. package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +21 -5
  4. package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +90 -21
  6. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +7 -6
  8. package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +21 -4
  10. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs +21 -1
  12. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +2 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
  16. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
  17. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +21 -4
  18. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
  19. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
  20. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-select.mjs +5 -4
  22. package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
  23. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +4 -4
  24. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  25. package/fesm2022/lifecorner-angular-ui-kit-table.mjs +2 -2
  26. package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
  27. package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
  28. package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
  29. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
  30. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  31. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs +9 -4
  32. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
  33. package/fesm2022/lifecorner-angular-ui-kit-tooltip.mjs +54 -9
  34. package/fesm2022/lifecorner-angular-ui-kit-tooltip.mjs.map +1 -1
  35. package/package.json +1 -1
  36. package/src/styles/_theme-dark.scss +9 -0
  37. package/src/styles/_theme-light.scss +9 -0
  38. package/src/styles/theme-contrast.spec.ts +14 -6
  39. package/src/styles/theme-modes.spec.ts +44 -0
  40. package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +3 -2
  41. package/types/lifecorner-angular-ui-kit-chat.d.ts +14 -1
  42. package/types/lifecorner-angular-ui-kit-combobox.d.ts +40 -2
  43. package/types/lifecorner-angular-ui-kit-datepicker.d.ts +4 -1
  44. package/types/lifecorner-angular-ui-kit-header.d.ts +26 -0
  45. package/types/lifecorner-angular-ui-kit-internal-a11y.d.ts +12 -1
  46. package/types/lifecorner-angular-ui-kit-list.d.ts +6 -1
  47. package/types/lifecorner-angular-ui-kit-next-step-card.d.ts +15 -0
  48. package/types/lifecorner-angular-ui-kit-select.d.ts +7 -0
  49. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +3 -1
  50. package/types/lifecorner-angular-ui-kit-toast.d.ts +14 -3
  51. package/types/lifecorner-angular-ui-kit-tooltip.d.ts +31 -2
@@ -486,3 +486,12 @@
486
486
  .lc-header--light {
487
487
  @include -light-theme-colors;
488
488
  }
489
+
490
+ // `<lc-sidenav theme="light">` on a dark page: the rail is light, but its
491
+ // badges took the page's dark palette and measured 1.11–1.96:1 (request 051,
492
+ // 050-f2). Re-declared on the badge host they match a light page (4.85–10.97:1).
493
+ // Only the badge: the collapsed rail's label tooltip floats over the page and
494
+ // keeps the page palette. A root-level token override does not reach inside.
495
+ .lc-sidenav-container--light .lc-sidenav__nav-badge {
496
+ @include -light-theme-colors;
497
+ }
@@ -457,13 +457,18 @@ describe('Component pairings of 3.19.1 (request 025)', () => {
457
457
  for (const rule of chipRules) expect(rule[4]).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|:\s*white\b/i);
458
458
  });
459
459
 
460
- it('AC-13: in forced colours the pressed chip keeps a Highlight outline unless it is focused', () => {
460
+ // 3.33 (request 052, 044-n3, AC-16): the pressed mark in forced colours is
461
+ // a Highlight underline (the rich-text-editor's), not the focus ring's
462
+ // 2 px outline, and it stays while the chip is focused.
463
+ it('AC-13: in forced colours the pressed chip is underlined in Highlight, also while focused', () => {
461
464
  const at = ncCss.indexOf('@media (forced-colors: active)');
462
465
  expect(at).toBeGreaterThan(-1);
463
466
  const media = ncCss.slice(at, ncCss.indexOf('}\n}', at));
464
- expect(media).toMatch(/\.lc-nc__filter--active:not\(:focus-visible\) \{/);
465
- expect(media).toMatch(/outline: 2px solid Highlight;/);
466
- expect(media).toMatch(/outline-offset: 1px;/);
467
+ expect(media).toMatch(
468
+ /\.lc-nc__filter\[aria-pressed=true\] \{\s*text-decoration-line: underline;\s*text-decoration-thickness: 2px;\s*text-underline-offset: 4px;\s*text-decoration-color: Highlight;/,
469
+ );
470
+ expect(media).not.toMatch(/:not\(:focus-visible\)/);
471
+ expect(media).not.toMatch(/outline: 2px solid Highlight;/);
467
472
  });
468
473
 
469
474
  /** Design 025 §5, per palette [light, dark]: the minimum over both panel ends. */
@@ -735,12 +740,15 @@ describe('Component pairings of 3.21.1 (request 029)', () => {
735
740
  expect(sheets.nc).not.toMatch(/#3b82f6|#22c55e|#f59e0b|#ef4444/i);
736
741
  });
737
742
 
738
- it('D-2: the level pills stay at full opacity; the pressed pill gets an outline in forced colours', () => {
743
+ // 3.33 (request 052, 044-n3, AC-16): the forced-colours mark is the
744
+ // rich-text-editor's Highlight underline (was a 2 px outline, hidden on focus).
745
+ it('D-2: the level pills stay at full opacity; the pressed pill is underlined in forced colours', () => {
739
746
  expect(fills('log', '.lc-log-viewer__level-pill').opacity).toBeUndefined();
740
747
  expect(fills('log', '.lc-log-viewer__level-pill--active').opacity).toBeUndefined();
741
748
  expect(sheets.log).toMatch(
742
- /@media \(forced-colors: active\) \{\s*\.lc-log-viewer__level-pill--active:not\(:focus-visible\) \{\s*outline: 2px solid Highlight;\s*outline-offset: 1px;\s*\}/,
749
+ /@media \(forced-colors: active\) \{\s*\.lc-log-viewer__level-pill\[aria-pressed=true\] \{\s*text-decoration-line: underline;\s*text-decoration-thickness: 2px;\s*text-underline-offset: 4px;\s*text-decoration-color: Highlight;\s*\}/,
743
750
  );
751
+ expect(sheets.log).not.toMatch(/level-pill--active:not\(:focus-visible\)/);
744
752
  });
745
753
 
746
754
  it('AC-10: the progress track is unchanged; primary reads the theme token (request 039), secondary the data mark (036)', () => {
@@ -98,3 +98,47 @@ describe('Theme scope of a themed header', () => {
98
98
  expect(dark.indexOf('.lc-header--dark {')).toBeGreaterThan(dark.indexOf(':root.system {'));
99
99
  });
100
100
  });
101
+
102
+ /**
103
+ * Request 051 (050-f2, design §1 B, D-3): a sidenav with `theme="light"` /
104
+ * `"dark"` re-declares every colour token of that theme on its badge hosts,
105
+ * so the badges take the rail's palette instead of the page's (1.11–1.96:1 on
106
+ * a light rail on a dark page before). Only the badge host: the collapsed
107
+ * rail's label tooltip keeps the page palette. On a matching page the values
108
+ * equal the root's, so the badge renders exactly as before (AC-4).
109
+ */
110
+ describe('Theme scope of a themed sidenav rail badge (request 051)', () => {
111
+ const COLOUR = /^--(color-|gradient-|shadow-|elevation-|accent-line$|app-background-image$)/;
112
+ const colours = (tokens: Record<string, string>) =>
113
+ Object.fromEntries(Object.entries(tokens).filter(([name]) => COLOUR.test(name)));
114
+ const LIGHT_RAIL = '.lc-sidenav-container--light .lc-sidenav__nav-badge {';
115
+ const DARK_RAIL = '.lc-sidenav-container--dark .lc-sidenav__nav-badge {';
116
+
117
+ it('AC-3/AC-4: the light rail badge declares exactly the colour tokens of the light root (172), with the same values', () => {
118
+ const badge = tokensOf(light, `\n${LIGHT_RAIL}`);
119
+ expect(Object.keys(badge)).toHaveLength(172);
120
+ expect(badge).toEqual(colours(tokensOf(light, ':root,')));
121
+ });
122
+
123
+ it('AC-3/AC-4: the dark rail badge declares exactly the colour tokens of the dark root (172), with the same values', () => {
124
+ const badge = tokensOf(dark, `\n${DARK_RAIL}`);
125
+ expect(Object.keys(badge)).toHaveLength(172);
126
+ expect(badge).toEqual(colours(tokensOf(dark, ':root.dark,')));
127
+ });
128
+
129
+ it('emits each rail rule once, in its own theme file, after the header rule', () => {
130
+ expect(light.split(LIGHT_RAIL)).toHaveLength(2);
131
+ expect(dark.split(DARK_RAIL)).toHaveLength(2);
132
+ expect(light.includes('.lc-sidenav-container--dark')).toBe(false);
133
+ expect(dark.includes('.lc-sidenav-container--light')).toBe(false);
134
+ expect(light.indexOf(LIGHT_RAIL)).toBeGreaterThan(light.indexOf('.lc-header--light {'));
135
+ expect(dark.indexOf(DARK_RAIL)).toBeGreaterThan(dark.indexOf('.lc-header--dark {'));
136
+ });
137
+
138
+ it('scopes the badge host only, not the rail or its label tooltip', () => {
139
+ for (const css of [light, dark]) {
140
+ const selectors = [...css.matchAll(/(^|\n)([^\n{}]*lc-sidenav[^\n{}]*)\{/g)].map((m) => m[2].trim());
141
+ expect(selectors).toEqual([expect.stringMatching(/^\.lc-sidenav-container--(light|dark) \.lc-sidenav__nav-badge$/)]);
142
+ }
143
+ });
144
+ });
@@ -136,8 +136,9 @@ export declare class BottomNavComponent {
136
136
  * tasks on the pages behind it: drawn like an item badge and read after the
137
137
  * label through `badgeAriaLabel`. The kit doesn't add up the hidden
138
138
  * destinations' badges; pass the value to show. It sits on the bar's last
139
- * entry, so keep it to two characters (`'9+'`). `null` or `undefined`: no
140
- * badge.
139
+ * entry; values up to three characters (`'99+'`) fit: on the last entry a
140
+ * badge that would pass the entry's edge moves left until it ends there
141
+ * (3.33). `null` or `undefined`: no badge.
141
142
  *
142
143
  * @default null
143
144
  */
@@ -128,7 +128,12 @@ export declare class ChatComponent {
128
128
  multiple: import("@angular/core").InputSignal<boolean>;
129
129
  /** Maximum file size in bytes. `0` disables the check. */
130
130
  maxFileSize: import("@angular/core").InputSignal<number>;
131
- /** BCP 47 locale of the message times (hour and minute). */
131
+ /**
132
+ * BCP 47 locale of the message times (hour and minute). Attachment sizes
133
+ * follow the host's language (closest `lang`, 3.33), English without one:
134
+ * `240,0 KB` under `lang="de"`.
135
+ * @default 'de-DE'
136
+ */
132
137
  locale: import("@angular/core").InputSignal<string>;
133
138
  /** Header badge while a message is streaming. */
134
139
  streamingLabel: import("@angular/core").InputSignal<string>;
@@ -211,7 +216,15 @@ export declare class ChatComponent {
211
216
  protected openFilePicker(): void;
212
217
  protected onFilesSelected(event: Event): void;
213
218
  protected removeAttachment(id: string): void;
219
+ private readonly host;
220
+ /**
221
+ * Attachment size with one decimal in the host's language (3.33, request
222
+ * 052, 044-n2): the closest `lang`, English without one (or when it is
223
+ * empty or invalid), so `240.0 KB` by default and `240,0 KB` under
224
+ * `lang="de"`. No digit grouping, as before.
225
+ */
214
226
  protected formatBytes(size: number | undefined): string;
227
+ private decimalFormat;
215
228
  protected isImage(att: ChatAttachment): boolean;
216
229
  private addFiles;
217
230
  private matchesAccept;
@@ -1,4 +1,5 @@
1
1
  import { ControlValueAccessor } from "@angular/forms";
2
+ import { ConnectedPosition } from "@angular/cdk/overlay";
2
3
  import { Observable } from "rxjs";
3
4
  import * as i0 from "@angular/core";
4
5
  export interface ComboboxOption {
@@ -42,6 +43,7 @@ export declare class ComboboxComponent implements ControlValueAccessor {
42
43
  protected readonly createOptionId: string;
43
44
  private readonly inputEl;
44
45
  private readonly dropdownEl;
46
+ private readonly inputWrapper;
45
47
  private readonly elRef;
46
48
  private readonly querySubject;
47
49
  /** Sync options */
@@ -76,6 +78,16 @@ export declare class ComboboxComponent implements ControlValueAccessor {
76
78
  readonly createOptionLabel: import("@angular/core").InputSignal<string>;
77
79
  /** Size */
78
80
  readonly size: import("@angular/core").InputSignal<ComboboxSize>;
81
+ /**
82
+ * Whether the list opens when the field gets focus (3.32, request 051).
83
+ * With `false`, focus alone (`focus()` from the page, Tab) leaves the list
84
+ * closed and `aria-expanded="false"`; a click or tap on the field, typing or
85
+ * ArrowDown opens it. Use it in forms that move focus into the field
86
+ * themselves, on open or to the first invalid field after a rejected save.
87
+ * Escape and choosing an option work as before.
88
+ * @default true
89
+ */
90
+ readonly openOnFocus: import("@angular/core").InputSignal<boolean>;
79
91
  /**
80
92
  * The `value` of the selected option, two-way (3.29, regeln row 2):
81
93
  * `[(selectedValue)]="month"`. Shows the matching option's label (from
@@ -110,9 +122,24 @@ export declare class ComboboxComponent implements ControlValueAccessor {
110
122
  /**
111
123
  * `lang` of the open dropdown (3.30.2, WCAG 3.1.2): the overlay renders under
112
124
  * `<body>`, so it takes the host's closest `[lang]`, else none. Read at every
113
- * opening; `null` while closed, so the server never reads the DOM.
125
+ * opening and, since 3.33, again when a `lang` changes while the list is
126
+ * open; `null` while closed, so the server never reads the DOM.
114
127
  */
115
128
  protected readonly panelLang: import("@angular/core").Signal<string | null>;
129
+ /**
130
+ * The field's width in px while the list is open (3.33, request 052,
131
+ * 049-n2), read at every opening: the list's floor (`min-width`), so it is
132
+ * at least as wide as the field and wider content still grows it. `null`
133
+ * while closed.
134
+ */
135
+ protected readonly fieldWidth: import("@angular/core").Signal<number | null>;
136
+ /**
137
+ * Below the field, else above it, start- then end-aligned (the CDK's
138
+ * defaults), 4 px from the field on either side (3.33, 047-n1: the gap was
139
+ * a margin inside the pane, so it was 0 above). The list is never pushed,
140
+ * so the offset can't move it off the field.
141
+ */
142
+ protected readonly positions: ConnectedPosition[];
116
143
  /** Selected values */
117
144
  protected selectedSingle: import("@angular/core").WritableSignal<ComboboxOption | null>;
118
145
  protected selectedMultiple: import("@angular/core").WritableSignal<ComboboxOption[]>;
@@ -162,10 +189,16 @@ export declare class ComboboxComponent implements ControlValueAccessor {
162
189
  * and its buttons keeps focus where it is (no blur, so the list stays open).
163
190
  */
164
191
  protected onFrameMousedown(event: MouseEvent): void;
165
- /** A click on the frame beside the input and its buttons focuses the input. */
192
+ /**
193
+ * A click on the frame beside the input and its buttons focuses the input.
194
+ * With `openOnFocus` off (3.32, request 051), a click or tap on the input or
195
+ * the frame also opens the list, which focus alone no longer does.
196
+ */
166
197
  protected onFrameClick(event: MouseEvent): void;
167
198
  protected onInputChange(event: Event): void;
168
199
  protected onInputFocus(): void;
200
+ /** Opens the list when there is something to show: a long enough query, or options. */
201
+ private openIfAny;
169
202
  protected onInputBlur(): void;
170
203
  protected onKeydown(event: KeyboardEvent): void;
171
204
  protected selectOption(option: ComboboxOption): void;
@@ -254,6 +287,11 @@ export declare class ComboboxComponent implements ControlValueAccessor {
254
287
  "required": false;
255
288
  "isSignal": true;
256
289
  };
290
+ "openOnFocus": {
291
+ "alias": "openOnFocus";
292
+ "required": false;
293
+ "isSignal": true;
294
+ };
257
295
  "selectedValue": {
258
296
  "alias": "selectedValue";
259
297
  "required": false;
@@ -191,10 +191,13 @@ export declare class DatepickerComponent implements ControlValueAccessor {
191
191
  readonly isDisabled: import("@angular/core").Signal<boolean>;
192
192
  /** Month granularity (3.29). */
193
193
  protected readonly isMonthMode: import("@angular/core").Signal<boolean>;
194
+ /** The host's closest `[lang]` while the panel is open, kept live (3.33, 049-n3). */
195
+ private readonly hostLang;
194
196
  /**
195
197
  * `lang` of the open panel (3.30.2, WCAG 3.1.2): the overlay renders under
196
198
  * `<body>`, so it takes the host's closest `[lang]`, else `locale`, else none.
197
- * Read at every opening; `null` while closed, so the server never reads the DOM.
199
+ * Read at every opening and, since 3.33, again when a `lang` changes while
200
+ * the panel is open; `null` while closed, so the server never reads the DOM.
198
201
  */
199
202
  protected readonly panelLang: import("@angular/core").Signal<string | null>;
200
203
  readonly formattedDate: import("@angular/core").Signal<string>;
@@ -25,6 +25,12 @@ export type HeaderVariant = 'default' | 'transparent';
25
25
  * `userAvatarColor`, in a 48 px round button).
26
26
  */
27
27
  export type HeaderUserMenuTrigger = 'icon' | 'avatar';
28
+ /**
29
+ * Where the context button sits up to 640 px (3.33): `inline` in the end
30
+ * group, hidden up to 640 px (as before), or `below`, a row of its own under
31
+ * the title. From 641 px both render it inline.
32
+ */
33
+ export type HeaderContextPlacement = 'inline' | 'below';
28
34
  /**
29
35
  * AppHeaderComponent - Global application header for the LifeCorner shell
30
36
  *
@@ -67,6 +73,9 @@ export type HeaderUserMenuTrigger = 'icon' | 'avatar';
67
73
  * as typed (no uppercase).
68
74
  * - The account menu is at most `min(18rem, 100vw − 2rem)` wide; the name and
69
75
  * email in its header wrap instead of being cut.
76
+ * - `contextPlacement="below"` (3.33): up to 640 px the context button
77
+ * (`contextName`) takes a full-width row of its own under the title instead
78
+ * of being hidden; from 641 px it sits in the end group as before.
70
79
  * - Every built-in label is an input (`homeAriaLabel`, `hamburgerAriaLabel`,
71
80
  * `userMenuAriaLabel`, `navAriaLabel`, `profileLabel`, `logoutLabel`,
72
81
  * `themeToDarkAriaLabel`, `themeToLightAriaLabel`, `contextAriaLabel`);
@@ -201,6 +210,16 @@ export declare class HeaderComponent {
201
210
  readonly autoToggleTheme: import("@angular/core").InputSignal<boolean>;
202
211
  readonly contextName: import("@angular/core").InputSignal<string>;
203
212
  readonly contextLabel: import("@angular/core").InputSignal<string>;
213
+ /**
214
+ * Where the context button sits up to 640 px (3.33, app-shell row 1).
215
+ * `'inline'`: in the end group, hidden up to 640 px (as before). `'below'`:
216
+ * up to 640 px a row of its own under the title, as wide as the header and
217
+ * at least 44 px high; title, `end` slot and account menu stay in the first
218
+ * row. From 641 px both render the button inline. No second row without
219
+ * `contextName`.
220
+ * @default 'inline'
221
+ */
222
+ readonly contextPlacement: import("@angular/core").InputSignal<HeaderContextPlacement>;
204
223
  readonly menuSize: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
205
224
  readonly showProfileMenuItem: import("@angular/core").InputSignal<boolean>;
206
225
  /** Render the account menu (avatar trigger with Profile/Logout). */
@@ -281,6 +300,8 @@ export declare class HeaderComponent {
281
300
  * `contextName`, so a voice-control user can say what they see.
282
301
  */
283
302
  protected readonly contextButtonAriaLabel: import("@angular/core").Signal<string>;
303
+ /** `contextPlacement="below"` with a context button to place (3.33). */
304
+ protected readonly contextBelow: import("@angular/core").Signal<boolean>;
284
305
  /**
285
306
  * Get menu items for profile dropdown
286
307
  */
@@ -459,6 +480,11 @@ export declare class HeaderComponent {
459
480
  "required": false;
460
481
  "isSignal": true;
461
482
  };
483
+ "contextPlacement": {
484
+ "alias": "contextPlacement";
485
+ "required": false;
486
+ "isSignal": true;
487
+ };
462
488
  "menuSize": {
463
489
  "alias": "menuSize";
464
490
  "required": false;
@@ -1,3 +1,4 @@
1
+ import { Signal } from "@angular/core";
1
2
  /**
2
3
  * Accessible names that contain the visible text (WCAG 2.5.3 Label in Name,
3
4
  * AS-33). The containment test follows axe-core 4.14's
@@ -35,4 +36,14 @@ export declare function nameWithText(label: string, text: string): string;
35
36
  * The value is taken as is (`lang=""` is the unknown language, as DOM
36
37
  * inheritance gives it). `null` without a DOM `closest` (server DOM).
37
38
  */
38
- export declare function closestLang(el: Element | null | undefined): string | null;
39
+ export declare function closestLang(el: Element | null | undefined): string | null;
40
+ /**
41
+ * 3.33 (request 052, 049-n3): `closestLang` of the component's host while
42
+ * `open()` is true, `null` while it is false (so the server never reads the
43
+ * DOM). While open, one `MutationObserver` watches every `lang` attribute of
44
+ * the document, so a host language that changes with the panel open shows
45
+ * without reopening; it is disconnected when the panel closes or the
46
+ * component is destroyed. Call it in the component's injection context (it
47
+ * injects the host element and creates an effect).
48
+ */
49
+ export declare function openPanelLang(open: Signal<boolean>): Signal<string | null>;
@@ -77,7 +77,12 @@ export interface ListItem {
77
77
  id?: string;
78
78
  /** Whether the item is disabled */
79
79
  disabled?: boolean;
80
- /** Whether the item is selected / highlighted */
80
+ /**
81
+ * Whether the item is the current choice: a tinted row and, since 3.33,
82
+ * `aria-current="true"` on the row element (the button with `clickable`,
83
+ * the link with `href`, the row otherwise). Pair it with a visible mark such
84
+ * as a badge; the tint alone is colour.
85
+ */
81
86
  selected?: boolean;
82
87
  /** Any additional data */
83
88
  [key: string]: any;
@@ -55,6 +55,9 @@ export type NextStepCardActionSize = 'sm' | 'md' | 'lg';
55
55
  * `stacked` for several equal-width actions; `titleTag` for a heading
56
56
  * - `actionsStyle="equal"` (3.16, BA-5): a question with two answers of the
57
57
  * same weight, both outline buttons in the primary tone, primary first
58
+ * - `focusTitle()` (3.33): moves focus to the title (`tabindex="-1"`, out of
59
+ * the Tab order) after an action replaced the card's state, so focus
60
+ * doesn't fall back to the page (WCAG 2.4.3)
58
61
  *
59
62
  * The host is a size container (`container-type: inline-size`) and takes the
60
63
  * full inline size of its parent.
@@ -168,6 +171,18 @@ export declare class NextStepCardComponent implements AfterViewChecked {
168
171
  /** Emits when the secondary action is clicked. */
169
172
  secondaryClick: import("@angular/core").OutputEmitterRef<void>;
170
173
  private actionsSlot;
174
+ /** The title element of whichever `titleTag` branch is rendered. */
175
+ private titleEl;
176
+ /**
177
+ * Moves focus to the card's title (3.33), e.g. after an action replaced the
178
+ * card's state, so focus doesn't fall back to the page (WCAG 2.4.3). Gives
179
+ * the title `tabindex="-1"` first: it stays out of the Tab order. Call it
180
+ * after the new state has rendered, e.g.
181
+ * `afterNextRender(() => this.card().focusTitle(), { injector })` with
182
+ * `card = viewChild.required(NextStepCardComponent)`. Does nothing while no
183
+ * title is rendered.
184
+ */
185
+ focusTitle(): void;
171
186
  /**
172
187
  * Whether content was projected into `[slot='actions']` — projected actions
173
188
  * fully replace the built-in buttons.
@@ -124,6 +124,13 @@ export declare class SelectComponent implements ControlValueAccessor {
124
124
  * Read at every opening; `null` while closed, so the server never reads the DOM.
125
125
  */
126
126
  protected readonly panelLang: import("@angular/core").Signal<string | null>;
127
+ /**
128
+ * The trigger's width in px while the dropdown is open (3.33, request 052,
129
+ * 050-n4), read at every opening: the panel's floor next to its 200 px
130
+ * (`min-width`), so it is at least as wide as the field and wider content
131
+ * still grows it. `null` while closed.
132
+ */
133
+ protected readonly fieldWidth: import("@angular/core").Signal<number | null>;
127
134
  readonly filteredOptions: import("@angular/core").Signal<SelectOption[]>;
128
135
  /** Keyboard highlight; resets whenever the visible option list changes so it never points at a stale row. */
129
136
  readonly highlightedIndex: import("@angular/core").WritableSignal<number>;
@@ -209,7 +209,9 @@ export declare class SidenavComponent implements OnDestroy {
209
209
  * the collapsed rail, where labels are hidden, the label and this text name
210
210
  * the item. `{value}` is replaced with the badge value, e.g.
211
211
  * `'{value} offen'` → "Stammdaten 1 offen". The badge itself is hidden from
212
- * screen readers. Empty: the badge isn't read.
212
+ * screen readers. Empty: the badge isn't read. Since 3.33 a badged rail
213
+ * item has no `title` (it repeated the label as a description); its
214
+ * tooltip still shows on hover and focus.
213
215
  *
214
216
  * @default '{value}'
215
217
  */
@@ -192,9 +192,13 @@ type ToastLive = 'polite' | 'assertive';
192
192
  * hidden, the bottom offset is the safe-area inset. Without a shell, and in
193
193
  * prerendered HTML, both are `0px`, as before. Every stack is at most as high
194
194
  * as the space between the offsets and scrolls when its toasts need more
195
- * (200 % text), so each toast stays reachable. In the shell, prefer the
196
- * `bottom-*` positions: a top toast can still cover a page title placed
197
- * right under the header.
195
+ * (200 % text), so each toast stays reachable.
196
+ *
197
+ * Bottom stacks show the newest toast nearest the edge (3.33); top stacks the
198
+ * oldest at the top. A `top-*` stack lies over the page on purpose (a toast
199
+ * stays visible wherever the page is scrolled), so in the shell it can cover
200
+ * a title right under the header (at 360 px a toast at y 72 over an h1 at
201
+ * y 80): prefer `bottom-*` there.
198
202
  *
199
203
  * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;
200
204
  * each wins over the shell's value; write lengths, `0px` rather than `0`):
@@ -215,6 +219,13 @@ export declare class ToastOutletComponent {
215
219
  readonly closeAriaLabel: import("@angular/core").InputSignal<string>;
216
220
  /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */
217
221
  private readonly groups;
222
+ /**
223
+ * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its
224
+ * newest toast. That region gets `lc-toast-outlet__region--latest` and is
225
+ * laid out at the edge, so the newest toast sits nearest the edge (and, in
226
+ * a scrolling stack, in view at rest). DOM and announcement order stay.
227
+ */
228
+ protected readonly latestLive: import("@angular/core").Signal<Map<ToastPosition, ToastLive>>;
218
229
  protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[];
219
230
  protected onClosed(id: string): void;
220
231
  /** Errors and warnings interrupt (assertive), everything else waits its turn. */
@@ -17,6 +17,11 @@ export declare class TooltipContentComponent {
17
17
  *
18
18
  * The tooltip takes the `lang` of the host's closest `[lang]` (3.31).
19
19
  *
20
+ * Shows on hover and on keyboard focus of the host or of an element inside it
21
+ * (3.32). On a wrapper such as `lc-button`, the tooltip describes and anchors
22
+ * to the wrapped `<button>`. On a host with several controls, the one that
23
+ * has focus is described (3.33); on hover, the first.
24
+ *
20
25
  * @example
21
26
  * ```html
22
27
  * <button lcTooltip="Click me!" tooltipPosition="top">
@@ -50,10 +55,17 @@ export declare class TooltipDirective implements OnDestroy {
50
55
  * @default false
51
56
  */
52
57
  tooltipDisabled: import("@angular/core").InputSignal<boolean>;
53
- /** Id of the tooltip element; the host's `aria-describedby` points at it while shown. */
58
+ /**
59
+ * Id of the tooltip element; while shown, the `aria-describedby` of the
60
+ * described element (the host, or the focusable element it wraps, such as
61
+ * `lc-button`'s `<button>`) points at it.
62
+ */
54
63
  readonly tooltipId: string;
55
64
  private overlayRef?;
56
65
  private tooltipRef?;
66
+ private positionStrategy?;
67
+ /** The element whose `aria-describedby` holds the tooltip id while shown. */
68
+ private describedEl?;
57
69
  private positionSubscription?;
58
70
  private showTimeout?;
59
71
  private hideTimeout?;
@@ -68,6 +80,14 @@ export declare class TooltipDirective implements OnDestroy {
68
80
  onMouseLeave(): void;
69
81
  onFocus(): void;
70
82
  onBlur(): void;
83
+ /**
84
+ * 3.32 (request 051, 050-f3): `focus` does not bubble, so focus on an
85
+ * element inside the host (`lc-button`'s `<button>`) arrives as `focusin`.
86
+ * The host's own focus is handled by `onFocus`, so it is skipped here.
87
+ */
88
+ onFocusIn(event: FocusEvent): void;
89
+ /** Hides when focus leaves the host from an element inside it (not when it moves within). */
90
+ onFocusOut(event: FocusEvent): void;
71
91
  /**
72
92
  * Show tooltip programmatically
73
93
  */
@@ -86,8 +106,17 @@ export declare class TooltipDirective implements OnDestroy {
86
106
  /** Preferred position first, then the fallbacks the overlay may flip to. */
87
107
  private getPositions;
88
108
  private setPositionClass;
89
- /** Append the tooltip id to the host's aria-describedby without clobbering ids the caller set. */
109
+ /**
110
+ * The described element: the host if it is a control, else the control
111
+ * inside it that has focus (3.33, request 052, 051-n1: a host with several
112
+ * controls), else the first control inside it, else the host.
113
+ */
114
+ private resolveControl;
115
+ /** The overlay origin: the control when it is the host's only element child (a wrapper), else the host. */
116
+ private resolveAnchor;
117
+ /** Append the tooltip id to the element's aria-describedby without clobbering ids the caller set. */
90
118
  private addDescribedBy;
119
+ /** Removes the tooltip id from the element it was added to, keeping the other ids. */
91
120
  private removeDescribedBy;
92
121
  private clearTimeouts;
93
122
  private cleanup;