@lifecorner/angular-ui-kit 3.16.0 → 3.18.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.
@@ -302,3 +302,60 @@ describe('Component pairings of 3.16 (request 022)', () => {
302
302
  });
303
303
  });
304
304
  });
305
+
306
+ // Request 021 (3.17): the lc-list status badges (12 px / 500, so 4.5:1) and the
307
+ // focus ring of a loading lc-button, which its 0.5 opacity fades. The token
308
+ // names come from the compiled component stylesheets, the values from the themes.
309
+ describe('Component pairings of 3.17 (request 021)', () => {
310
+ const listCss = compile(resolve(__dirname, '../lib/list/list.component.scss')).css;
311
+ const buttonCss = compile(resolve(__dirname, '../lib/button/button.component.scss')).css;
312
+
313
+ /** The background and ink token of a list badge variant (a fallback after the name is ignored). */
314
+ function badgeTokens(variant: string): [string, string] {
315
+ const body = listCss.match(new RegExp(`\\n\\.lc-list__badge--${variant} \\{([^}]*)\\}`))?.[1] ?? '';
316
+ const bg = body.match(/background-color: var\((--[\w-]+)/)?.[1] ?? '';
317
+ const ink = body.match(/\scolor: var\((--[\w-]+)/)?.[1] ?? '';
318
+ expect([bg, ink]).toEqual([expect.stringMatching(/^--color-/), expect.stringMatching(/^--color-/)]);
319
+ return [bg, ink];
320
+ }
321
+
322
+ /** Design 021 §5: the minimum over the bases, per palette [light, dark]. */
323
+ const badges: [string, [string, string], [number, number]][] = [
324
+ ['success', ['--color-success-subtle', '--color-on-success-subtle'], [4.85, 5.17]],
325
+ ['warning', ['--color-warning-subtle', '--color-on-warning-subtle'], [5.52, 4.61]],
326
+ ['error', ['--color-error-100', '--color-on-error-subtle'], [6.33, 7.17]],
327
+ ['primary', ['--color-primary-100', '--color-primary-700'], [7.85, 9.68]],
328
+ ['default', ['--color-surface-hover', '--color-text-secondary'], [6.87, 8.18]],
329
+ ];
330
+
331
+ describe.each(palettes.map(([name, tokens], i) => [name, tokens, i] as const))('%s palette', (_palette, tokens, i) => {
332
+ /** The grounds a list sits on: the page, the card surfaces and a hovered row (over the surface). */
333
+ const bases: Rgba[] = [
334
+ color(tokens, '--color-background'),
335
+ color(tokens, '--color-surface'),
336
+ over(color(tokens, '--color-surface-2'), color(tokens, '--color-surface')),
337
+ over(color(tokens, '--color-surface-hover'), color(tokens, '--color-surface')),
338
+ ].map((c) => over(c, color(tokens, '--color-background')));
339
+
340
+ it.each(badges)('AC-12: the %s list badge reaches 4.5:1 on every ground', (variant, pair, values) => {
341
+ expect(badgeTokens(variant)).toEqual(pair);
342
+ const [bg, ink] = pair;
343
+ const min = Math.min(
344
+ ...bases.map((base) => {
345
+ const fill = over(color(tokens, bg), base);
346
+ return ratio(over(color(tokens, ink), fill), fill);
347
+ }),
348
+ );
349
+ expect(min).toBeCloseTo(values[i], 2);
350
+ expect(min).toBeGreaterThanOrEqual(4.5);
351
+ });
352
+
353
+ it('AC-7: the ring of a focused loading button (--color-text-primary at 50 %) reaches 3:1 on the page and the surfaces', () => {
354
+ expect(buttonCss).toMatch(/\n\.btn\.btn-loading:focus-visible \{\s*outline-color: var\(--color-text-primary\);/);
355
+ const ink = color(tokens, '--color-text-primary');
356
+ for (const base of bases.slice(0, 3)) {
357
+ expect(ratio(over([ink[0], ink[1], ink[2], 0.5], base), base)).toBeGreaterThanOrEqual(3);
358
+ }
359
+ });
360
+ });
361
+ });
@@ -47,6 +47,8 @@ interface BottomNavEntry {
47
47
  * - More than `maxItems` destinations: the first `maxItems − 1` stay, the
48
48
  * rest move behind a "More" entry (`moreLabel`, `moreIcon`, `moreRoute`);
49
49
  * without `moreRoute` the entry is a button that emits `moreClick`.
50
+ * - `showMore`: "More" on request, also when every destination fits (fixed
51
+ * slots); it takes one of the `maxItems` entries.
50
52
  * - Tapping the current item emits `reselect` (scroll the page to the top).
51
53
  * - `variant="floating"`: an inset pill 16 px from the sides and 16 px above
52
54
  * the safe-area inset, radius 28 px, elevated. `docked` (default) spans the
@@ -89,17 +91,41 @@ export declare class BottomNavComponent {
89
91
  */
90
92
  readonly fixed: import("@angular/core").InputSignal<boolean>;
91
93
  /**
92
- * Most entries the bar shows. With more destinations the first
93
- * `maxItems − 1` stay and the rest go behind the "More" entry.
94
+ * Most entries the bar shows, "More" included. With more destinations, or
95
+ * with `showMore`, the first `maxItems − 1` stay and the rest go behind the
96
+ * "More" entry.
97
+ *
98
+ * @default 5
94
99
  */
95
100
  readonly maxItems: import("@angular/core").InputSignal<number>;
96
- /** Label of the overflow entry. */
101
+ /**
102
+ * Always end the bar with the "More" entry (`moreLabel`, `moreIcon`, and
103
+ * `moreRoute` or `moreClick`), even when every destination fits: for a bar
104
+ * with fixed slots, such as two destinations plus "More". "More" takes one
105
+ * of the `maxItems` entries, so the first `maxItems − 1` destinations stay
106
+ * and any others move behind it, as on overflow.
107
+ *
108
+ * @default false
109
+ */
110
+ readonly showMore: import("@angular/core").InputSignal<boolean>;
111
+ /**
112
+ * Label of the "More" entry (on overflow or with `showMore`).
113
+ *
114
+ * @default 'More'
115
+ */
97
116
  readonly moreLabel: import("@angular/core").InputSignal<string>;
98
- /** Icon of the overflow entry. */
117
+ /**
118
+ * Icon of the "More" entry (on overflow or with `showMore`).
119
+ *
120
+ * @default 'dots'
121
+ */
99
122
  readonly moreIcon: import("@angular/core").InputSignal<string>;
100
123
  /**
101
- * Route of the overflow entry (a page that lists the remaining
102
- * destinations). Empty renders the entry as a button that emits `moreClick`.
124
+ * Route of the "More" entry (on overflow or with `showMore`): a page that
125
+ * lists the remaining destinations. Empty renders the entry as a button that
126
+ * emits `moreClick`.
127
+ *
128
+ * @default ''
103
129
  */
104
130
  readonly moreRoute: import("@angular/core").InputSignal<string>;
105
131
  /**
@@ -109,13 +135,16 @@ export declare class BottomNavComponent {
109
135
  readonly badgeAriaLabel: import("@angular/core").InputSignal<string>;
110
136
  /** The current item was tapped again (e.g. scroll the page to the top). */
111
137
  readonly reselect: import("@angular/core").OutputEmitterRef<BottomNavItem>;
112
- /** The overflow entry was tapped while it has no `moreRoute`. */
138
+ /** The "More" entry was tapped while it has no `moreRoute`. */
113
139
  readonly moreClick: import("@angular/core").OutputEmitterRef<void>;
114
140
  /** Destinations in display order. */
115
141
  private readonly sortedItems;
116
142
  /** Path of `activeRoute` without query and fragment. */
117
143
  private readonly activePath;
118
- /** Entries as rendered: destinations plus, on overflow, the "More" entry. */
144
+ /**
145
+ * Entries as rendered: destinations plus, on overflow or with `showMore`,
146
+ * the "More" entry, which then takes one of the `maxItems` entries.
147
+ */
119
148
  protected readonly entries: import("@angular/core").Signal<BottomNavEntry[]>;
120
149
  protected readonly navClasses: import("@angular/core").Signal<string>;
121
150
  /** Visually hidden badge text for screen readers. */
@@ -156,6 +185,11 @@ export declare class BottomNavComponent {
156
185
  "required": false;
157
186
  "isSignal": true;
158
187
  };
188
+ "showMore": {
189
+ "alias": "showMore";
190
+ "required": false;
191
+ "isSignal": true;
192
+ };
159
193
  "moreLabel": {
160
194
  "alias": "moreLabel";
161
195
  "required": false;
@@ -28,7 +28,28 @@ export declare class ButtonComponent {
28
28
  */
29
29
  readonly tone: import("@angular/core").InputSignal<ButtonTone>;
30
30
  readonly size: import("@angular/core").InputSignal<ButtonSize>;
31
+ /**
32
+ * Natively disabled: the button leaves the focus order and ignores clicks.
33
+ * For a temporary busy state that keeps focus, use `loading`.
34
+ * @default false
35
+ */
31
36
  readonly disabled: import("@angular/core").InputSignal<boolean>;
37
+ /**
38
+ * Shows a spinner in place of the icon slots and makes the button
39
+ * unavailable without removing it from the focus order (3.17). The inner
40
+ * `<button>` gets `aria-disabled="true"` and `aria-busy="true"` instead of
41
+ * the native `disabled`, so a focused button keeps focus. While it is
42
+ * loading, click, Enter and Space are ignored: `clicked` is not emitted, the
43
+ * click event stops at the button (a `(click)` on the host doesn't fire),
44
+ * and a `type="submit"`/`"reset"` button doesn't submit or reset its form.
45
+ * The look is unchanged: spinner, 50 % opacity, no hover. Screen readers may
46
+ * announce the state change ("unavailable"/"dimmed"), but the button doesn't
47
+ * announce the outcome: the page does that, for example with a polite live
48
+ * region ("USM wird geladen.", then the result). `disabledReason` also
49
+ * applies while loading. If `disabled` is set too, the native `disabled`
50
+ * wins.
51
+ * @default false
52
+ */
32
53
  readonly loading: import("@angular/core").InputSignal<boolean>;
33
54
  readonly iconOnly: import("@angular/core").InputSignal<boolean>;
34
55
  readonly fullWidth: import("@angular/core").InputSignal<boolean>;
@@ -72,7 +93,13 @@ export declare class ButtonComponent {
72
93
  * `title` otherwise.
73
94
  */
74
95
  readonly effectiveTitle: import("@angular/core").Signal<string | null>;
75
- handleClick(): void;
96
+ /**
97
+ * Emits `clicked` unless the button is disabled or loading. While loading
98
+ * (and not natively disabled) the event is cancelled and stopped, so a form
99
+ * isn't submitted and a host `(click)` doesn't fire. The parameter is
100
+ * optional, so callers without an argument still work.
101
+ */
102
+ handleClick(event?: Event): void;
76
103
  handleFocus(): void;
77
104
  handleBlur(): void;
78
105
  focus(): void;
@@ -12,6 +12,13 @@ export type ProgressBarVariant = 'linear' | 'circular';
12
12
  * - Animated value transitions
13
13
  * - Accessible with ARIA progressbar role
14
14
  *
15
+ * The bar is a visual aid; the text next to it carries the meaning. Write the
16
+ * status as text ('USM wird geladen …') and announce changes from a live
17
+ * region (`role="status"`). Screen readers don't announce a progress bar's
18
+ * changes by themselves. Give `ariaLabel` the same words as the status. Under
19
+ * `forced-colors: active` the fill is `Highlight`, the linear track gets a
20
+ * 1 px `CanvasText` outline and the circle's track is `GrayText` (3.17).
21
+ *
15
22
  * @example
16
23
  * ```html
17
24
  * <lc-progress-bar [value]="75" color="primary" size="md"></lc-progress-bar>
@@ -57,7 +64,11 @@ export declare class ProgressBarComponent {
57
64
  */
58
65
  inline: import("@angular/core").InputSignal<boolean>;
59
66
  /**
60
- * Whether the progress is indeterminate (animated, no fixed value).
67
+ * Progress of unknown length: no value (`aria-valuenow` is absent) and no
68
+ * label. The linear bar moves a 40 % segment across the track, and the
69
+ * circle spins. Under `prefers-reduced-motion: reduce` nothing moves: the
70
+ * linear bar shows a full-width striped fill and the circle a dashed ring,
71
+ * so it still reads as busy, not as a value (3.17).
61
72
  * @default false
62
73
  */
63
74
  indeterminate: import("@angular/core").InputSignal<boolean>;
@@ -37,7 +37,11 @@ export interface TableColumn {
37
37
  width?: string;
38
38
  /** Optional CSS class(es) applied to both th and td cells */
39
39
  cssClass?: string;
40
- /** Optional CSS class(es) for td cells only (string or resolver callback) */
40
+ /**
41
+ * Optional CSS class(es) for td cells only (string or resolver callback).
42
+ * A class colour or font size loses to the kit's cell rule; set them with
43
+ * `cellStyle`.
44
+ */
41
45
  cellClass?: TableCellClass;
42
46
  /** Optional inline styles for td cells only (object or resolver callback) */
43
47
  cellStyle?: TableCellStyle;
@@ -49,7 +53,21 @@ export interface TableColumn {
49
53
  editType?: 'text' | 'number' | 'select';
50
54
  /** Options for select edit type */
51
55
  editOptions?: string[];
56
+ /**
57
+ * Horizontal alignment of the column's header and cells. `end` aligns both
58
+ * at the end edge (right in LTR) and sets `font-variant-numeric:
59
+ * tabular-nums`, for amounts, counts and percentages. A sort arrow then sits
60
+ * before the label, so the label stays flush with the figures. Adds
61
+ * `lc-table__cell--end` to the `th` and every `td`. It wins over a
62
+ * `cellClass` that sets `text-align`; use `cellStyle` to override it. A
63
+ * custom `lcTableCell` template inherits the alignment, but a flex or block
64
+ * child needs its own `justify-content: flex-end`. (3.17)
65
+ * @default 'start'
66
+ */
67
+ align?: TableColumnAlign;
52
68
  }
69
+ /** Horizontal alignment of a table column (3.17). */
70
+ export type TableColumnAlign = 'start' | 'end';
53
71
  /**
54
72
  * A row-level action rendered in the table's actions column (e.g. Freigeben, Ablehnen).
55
73
  * Action button clicks never trigger the row's `rowClick` output.
@@ -22,7 +22,9 @@ export interface Toast extends Required<Omit<ToastConfig, 'action'>> {
22
22
  * Toast service for showing ephemeral notifications.
23
23
  *
24
24
  * The toasts are rendered by `<lc-toast-outlet />`, which the application
25
- * places once (e.g. in its shell); without it nothing is shown.
25
+ * places once (e.g. in its shell); without it nothing is shown. The options,
26
+ * their defaults, the timing and how a toast is announced to screen readers
27
+ * are documented under *Feedback/Toast Outlet* (3.17).
26
28
  *
27
29
  * @example
28
30
  * ```typescript