@lifecorner/angular-ui-kit 3.24.1 → 3.25.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 (31) hide show
  1. package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs +2 -2
  2. package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs.map +1 -1
  3. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +2 -2
  4. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +7 -7
  6. package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +12 -4
  8. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
  10. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
  12. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs +2 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-password-input.mjs +2 -2
  16. package/fesm2022/lifecorner-angular-ui-kit-password-input.mjs.map +1 -1
  17. package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs +2 -2
  18. package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs.map +1 -1
  19. package/fesm2022/lifecorner-angular-ui-kit-select.mjs +2 -2
  20. package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-slider.mjs +2 -2
  22. package/fesm2022/lifecorner-angular-ui-kit-slider.mjs.map +1 -1
  23. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
  24. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  25. package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs +2 -2
  26. package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs.map +1 -1
  27. package/fesm2022/lifecorner-angular-ui-kit-verification-code-input.mjs +2 -2
  28. package/fesm2022/lifecorner-angular-ui-kit-verification-code-input.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/styles/theme-contrast.spec.ts +235 -7
  31. package/types/lifecorner-angular-ui-kit-description-list.d.ts +17 -5
@@ -1,5 +1,5 @@
1
1
  import { compile } from 'sass';
2
- import { readFileSync } from 'fs';
2
+ import { readFileSync, readdirSync } from 'fs';
3
3
  import { resolve } from 'path';
4
4
 
5
5
  /**
@@ -743,10 +743,13 @@ describe('Component pairings of 3.21.1 (request 029)', () => {
743
743
  );
744
744
  });
745
745
 
746
- it('AC-10: the progress track is unchanged; primary stays raw (secondary: request 036)', () => {
746
+ it('AC-10: the progress track is unchanged; primary reads the theme token (request 039), secondary the data mark (036)', () => {
747
747
  // The base rule (the forced-colours block repeats the selector with an outline).
748
748
  expect(sheets.progress).toMatch(/\n\.progress-bar__track \{\s*flex: 1;\s*background-color: var\(--color-surface-hover\);/);
749
- expect(fills('progress', '.progress-bar--primary .progress-bar__fill')['background-color']).toMatch(/^#[0-9a-f]{6}$/i);
749
+ // 039 (F-14): the raw #208497 was 2.89:1 against the dark track; primary
750
+ // now reads --color-primary-500 (same hex in light, 6.69:1 dark). This
751
+ // line asserted the raw hex before 3.25.
752
+ expect(fills('progress', '.progress-bar--primary .progress-bar__fill')['background-color']).toBe('var(--color-primary-500)');
750
753
  // 036 (F-11): the raw secondary-500 was 2.83:1 against the light track;
751
754
  // secondary now reads the data-mark token (4.19 light, 4.05 dark).
752
755
  expect(fills('progress', '.progress-bar--secondary .progress-bar__fill')['background-color']).toBe('var(--color-secondary-graphic)');
@@ -1325,7 +1328,8 @@ describe('Component pairings of 3.23.1 (request 036)', () => {
1325
1328
  ['dl', '.lc-dl__value--primary', { color: 'var(--color-on-primary-subtle)', 'font-weight': 'var(--font-weight-semibold)' }],
1326
1329
  ['dl', '.lc-dl__link', { color: 'var(--color-link)', 'text-decoration': 'none' }],
1327
1330
  ['dl', '.lc-dl__link:hover', { color: 'var(--color-link-hover)', 'text-decoration': 'underline' }],
1328
- ['dl', '.lc-dl__row--clickable:focus-visible', { outline: '2px solid var(--color-primary-500)', 'outline-offset': '2px' }],
1331
+ // 039 (F-17): the ring moved from the row to the row's button (same box).
1332
+ ['dl', '.lc-dl__action:focus-visible', { outline: '2px solid var(--color-primary-500)', 'outline-offset': '2px' }],
1329
1333
  // F-8: unread message and time in the secondary ink.
1330
1334
  ['nc', '.lc-nc__item--unread .lc-nc__item-message', { color: secondary }],
1331
1335
  ['nc', '.lc-nc__item--unread .lc-nc__item-time', { color: secondary }],
@@ -1347,10 +1351,11 @@ describe('Component pairings of 3.23.1 (request 036)', () => {
1347
1351
  });
1348
1352
 
1349
1353
  it('AC-2: the clickable row has no halo any more; forced colours draw the outline in Highlight', () => {
1350
- const clickable = rulesOf(sheets.dl).filter((r) => r.selectors.some((s) => s.includes('lc-dl__row--clickable')));
1354
+ // 039 (F-17): the focus rules moved to the row's button `.lc-dl__action`.
1355
+ const clickable = rulesOf(sheets.dl).filter((r) => r.selectors.some((s) => /lc-dl__row--clickable|lc-dl__action/.test(s)));
1351
1356
  for (const r of clickable) expect(decls(r.body)['box-shadow']).toBeUndefined();
1352
1357
  expect(sheets.dl).not.toMatch(/color-mix/);
1353
- expect(forced('dl')).toMatch(/\.lc-dl__row--clickable:focus-visible \{\s*outline-color: Highlight;\s*\}/);
1358
+ expect(forced('dl')).toMatch(/\.lc-dl__action:focus-visible \{\s*outline-color: Highlight;\s*\}/);
1354
1359
  });
1355
1360
 
1356
1361
  it('AC-9: forced colours paint every search match, the terminal one too, with Mark / MarkText', () => {
@@ -1416,7 +1421,8 @@ describe('Component pairings of 3.23.1 (request 036)', () => {
1416
1421
 
1417
1422
  function measure() {
1418
1423
  const dlInk = (selector: string) => value(fills('dl', selector).color);
1419
- const ringInk = value(fills('dl', '.lc-dl__row--clickable:focus-visible').outline.replace(/^2px solid /, ''));
1424
+ // 039 (F-17): the ring is the row button's (it covers the row's padding box).
1425
+ const ringInk = value(fills('dl', '.lc-dl__action:focus-visible').outline.replace(/^2px solid /, ''));
1420
1426
  const dl = {
1421
1427
  linkPage: lowest(dlInk('.lc-dl__link'), pages),
1422
1428
  linkHover: lowest(dlInk('.lc-dl__link'), hovered),
@@ -1550,3 +1556,225 @@ describe('Component pairings of 3.23.1 (request 036)', () => {
1550
1556
  expect(Math.min(...Object.values(terminal))).toBeGreaterThanOrEqual(4.5);
1551
1557
  });
1552
1558
  });
1559
+
1560
+ /**
1561
+ * Request 039 (3.25.0): F-14 the progress-bar primary fill, F-15 focus rings
1562
+ * instead of 20–40 % halos, F-16 the dependency-viewer node and toggle
1563
+ * borders. Design 039 §3 (rings) and §5 (ratios), floored, [light, dark].
1564
+ */
1565
+ describe('Component pairings of 3.25.0 (request 039)', () => {
1566
+ const lib = (path: string) => resolve(__dirname, '../lib', path);
1567
+ const sheets = {
1568
+ progress: compile(lib('progress-bar/progress-bar.component.scss')).css,
1569
+ ring: compile(lib('progress-ring/progress-ring.component.scss')).css,
1570
+ nc: compile(lib('notification-center/notification-center.component.scss')).css,
1571
+ log: compile(lib('log-viewer/log-viewer.component.scss')).css,
1572
+ code: compile(lib('code-block/code-block.component.scss')).css,
1573
+ tree: compile(lib('tree-view/tree-view.component.scss')).css,
1574
+ slider: compile(lib('slider/slider.component.scss')).css,
1575
+ select: compile(lib('select/select.component.scss')).css,
1576
+ digits: compile(lib('verification-code-input/verification-code-input.component.scss')).css,
1577
+ password: compile(lib('password-input/password-input.component.scss')).css,
1578
+ textarea: compile(lib('textarea/textarea.component.scss')).css,
1579
+ number: compile(lib('number-input/number-input.component.scss')).css,
1580
+ combobox: compile(lib('combobox/combobox.component.scss')).css,
1581
+ dep: compile(lib('dependency-viewer/dependency-viewer.component.scss')).css,
1582
+ dl: compile(lib('description-list/description-list.component.scss')).css,
1583
+ };
1584
+ type Sheet = keyof typeof sheets;
1585
+ const depTs = readFileSync(lib('dependency-viewer/dependency-viewer.component.ts'), 'utf8');
1586
+ const depHtml = readFileSync(lib('dependency-viewer/dependency-viewer.component.html'), 'utf8');
1587
+
1588
+ const rulesOf = (css: string) =>
1589
+ [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => ({
1590
+ selectors: m[1].split(/,(?![^(]*\))/).map((s) => s.trim()),
1591
+ body: m[2],
1592
+ }));
1593
+ const decls = (body: string): Record<string, string> =>
1594
+ Object.fromEntries([...body.matchAll(/([\w-]+):\s*([^;]+);/g)].map((m) => [m[1], m[2].replace(/\s+/g, ' ').trim()]));
1595
+ const forcedBlock = /@media \(forced-colors: active\) \{((?:[^{}]*\{[^{}]*\})*)\s*\}/g;
1596
+ /** Every rule naming `selector`, merged in source order, forced-colours blocks left out. */
1597
+ const fills = (sheet: Sheet, selector: string): Record<string, string> => {
1598
+ const found = rulesOf(sheets[sheet].replace(forcedBlock, '')).filter((r) => r.selectors.includes(selector));
1599
+ expect([sheet, selector, found.length > 0]).toEqual([sheet, selector, true]);
1600
+ return Object.assign({}, ...found.map((r) => decls(r.body)));
1601
+ };
1602
+ /** The same, inside the forced-colours blocks only. */
1603
+ const forcedFills = (sheet: Sheet, selector: string): Record<string, string> => {
1604
+ const inner = [...sheets[sheet].matchAll(forcedBlock)].map((m) => m[1]).join('\n');
1605
+ return Object.assign({}, ...rulesOf(inner).filter((r) => r.selectors.includes(selector)).map((r) => decls(r.body)));
1606
+ };
1607
+ const primary = '2px solid var(--color-primary-500)';
1608
+ const halo = /color-mix\(in srgb, var\(--color-(primary-500|error-600|error-default)\) \d+%|rgba\(32, 132, 151, 0?\.\d+\)/;
1609
+
1610
+ // Design 039 §3: [sheet, selector, outline, offset]. Every row was a halo or
1611
+ // had no focus style (D-4: NC action, log tool buttons, dependency toolbar).
1612
+ const rings: [Sheet, string, string, string][] = [
1613
+ ['nc', '.lc-nc__item-title:focus-visible', primary, '2px'],
1614
+ ['nc', '.lc-nc__item-action:focus-visible', primary, '2px'],
1615
+ ['nc', '.lc-nc__item-dismiss:focus-visible', primary, '2px'],
1616
+ ['log', '.lc-log-viewer__tool-btn:focus-visible', primary, '2px'],
1617
+ ['log', '.lc-log-viewer__line--clickable:focus-visible', primary, '-2px'],
1618
+ ['log', '.lc-log-viewer__jump-bottom:focus-visible', primary, '2px'],
1619
+ ['code', '.code-block__copy:focus-visible', primary, '-2px'],
1620
+ ['tree', '.lc-tree-view__tool-btn:focus-visible', primary, '2px'],
1621
+ ['tree', '.lc-tree-view__item:focus-visible', primary, '-2px'],
1622
+ ['select', '.lc-select__option.lc-select__option--highlighted', primary, '-2px'],
1623
+ ['digits', '.lc-digit-input:focus', primary, '0'],
1624
+ ['dep', '.dep-viewer__btn:focus-visible', primary, '2px'],
1625
+ ['dl', '.lc-dl__action:focus-visible', primary, '2px'],
1626
+ ['password', '.lc-input.lc-input-error:focus', '2px solid var(--color-error-600)', '0'],
1627
+ ['textarea', '.lc-textarea--error:focus', '2px solid var(--color-error-600)', '0'],
1628
+ ['number', '.number-input--error .number-input__control:focus-within', '2px solid var(--color-error-600)', '0'],
1629
+ ['combobox', '.lc-combobox--error .lc-combobox__input-wrapper:focus-within', '2px solid var(--color-error-default)', '0'],
1630
+ ];
1631
+
1632
+ it.each(rings)('AC-2: %s %s draws a solid 2 px outline, no halo', (sheet, selector, outline, offset) => {
1633
+ const found = fills(sheet, selector);
1634
+ expect([found.outline, found['outline-offset']]).toEqual([outline, offset]);
1635
+ expect(found['box-shadow'] ?? 'none').not.toMatch(halo);
1636
+ });
1637
+
1638
+ it.each(rings)('AC-3: %s %s turns Highlight in forced colours', (sheet, selector) => {
1639
+ expect(forcedFills(sheet, selector)['outline-color']).toBe('Highlight');
1640
+ });
1641
+
1642
+ it('AC-2, AC-3: an error digit keeps the error colour, and forced colours name its selector too', () => {
1643
+ expect(fills('digits', '.lc-digit-input.lc-digit-input-error:focus')).toEqual({ 'outline-color': 'var(--color-error-600)' });
1644
+ expect(forcedFills('digits', '.lc-digit-input.lc-digit-input-error:focus')['outline-color']).toBe('Highlight');
1645
+ });
1646
+
1647
+ it('AC-2: the log tool buttons show the ring at full opacity (0.7 gave 2.66:1)', () => {
1648
+ expect(fills('log', '.lc-log-viewer__tool-btn:focus-visible').opacity).toBe('1');
1649
+ });
1650
+
1651
+ it('AC-2, AC-3: the slider thumb ring is solid primary-500; forced colours outline the input', () => {
1652
+ for (const thumb of ['::-webkit-slider-thumb', '::-moz-range-thumb']) {
1653
+ expect(fills('slider', `.slider__input:focus-visible${thumb}`)['box-shadow']).toBe('0 1px 3px rgba(0, 0, 0, 0.15), 0 0 0 3px var(--color-primary-500)');
1654
+ }
1655
+ expect(forcedFills('slider', '.slider__input:focus-visible')).toEqual({ outline: '2px solid Highlight', 'outline-offset': '8px' });
1656
+ });
1657
+
1658
+ it('AC-11: the rings add no transition', () => {
1659
+ for (const [sheet, selector] of rings) expect([selector, fills(sheet, selector).transition]).toEqual([selector, undefined]);
1660
+ });
1661
+
1662
+ /**
1663
+ * AC-5 guard (design 039 §9): no focus, `--focused` or `--highlighted` rule
1664
+ * in lib/**\/*.scss draws a ≤ 40 % primary or error halo unless the same rule
1665
+ * also changes the border or draws an outline (the "keep" rows of the
1666
+ * inventory: the field border turns primary-500, ≥ 4.17 / 7.57).
1667
+ */
1668
+ it('AC-5: no focus style is a translucent halo alone', () => {
1669
+ const root = resolve(__dirname, '../lib');
1670
+ const files = (readdirSync(root, { recursive: true }) as string[]).filter((f) => /\.scss$/.test(f) && !/(^|\/)_/.test(f));
1671
+ expect(files.length).toBeGreaterThan(100);
1672
+ const offenders: string[] = [];
1673
+ let focusRules = 0;
1674
+ for (const file of files) {
1675
+ for (const r of rulesOf(compile(resolve(root, file)).css)) {
1676
+ if (!r.selectors.some((s) => /:focus|--focused|--highlighted/.test(s))) continue;
1677
+ focusRules++;
1678
+ const d = decls(r.body);
1679
+ const shadow = d['box-shadow'] ?? '';
1680
+ const weak = [...shadow.matchAll(/color-mix\(in srgb, var\(--color-(?:primary-500|error-600|error-default)\) (\d+)%/g)].some((m) => Number(m[1]) <= 60)
1681
+ || [...shadow.matchAll(/rgba\(32, 132, 151, (0?\.\d+)\)/g)].some((m) => Number(m[1]) <= 0.4);
1682
+ const outline = (d.outline !== undefined && !/^(none|0)\b/.test(d.outline)) || d['outline-color'] !== undefined;
1683
+ if (weak && !d['border-color'] && !outline) offenders.push(`${file}: ${r.selectors.join(', ')}`);
1684
+ }
1685
+ }
1686
+ expect(focusRules).toBeGreaterThan(100);
1687
+ expect(offenders).toEqual([]);
1688
+ });
1689
+
1690
+ it('AC-1: the progress primary reads the theme token in the bar and the circle, as the ring does', () => {
1691
+ expect(fills('progress', '.progress-bar--primary .progress-bar__fill')['background-color']).toBe('var(--color-primary-500)');
1692
+ expect(fills('progress', '.progress-bar-circular--primary .progress-bar-circular__fill').stroke).toBe('var(--color-primary-500)');
1693
+ expect(fills('ring', '.lc-progress-ring__value--primary').stroke).toBe('var(--color-primary-500)');
1694
+ expect(sheets.progress).not.toMatch(/#208497/i);
1695
+ });
1696
+
1697
+ it('AC-6: default and muted nodes and the toggle ring read the field border, active primary-600', () => {
1698
+ const border = (status: string) => depTs.match(new RegExp(`\\n ${status}:\\s*\\{ bg: '[^']+', border: '([^']+)'`))?.[1];
1699
+ expect(['default', 'active', 'muted'].map(border)).toEqual(['var(--color-field-border)', 'var(--color-primary-600)', 'var(--color-field-border)']);
1700
+ expect(depHtml.match(/<circle[^>]*fill="var\(--color-neutral-100\)" stroke="var\(--color-field-border\)" stroke-width="1"/g)).toHaveLength(2);
1701
+ expect(depHtml).not.toMatch(/--color-neutral-300|--color-neutral-200/);
1702
+ // The selected stroke and the edge, label and legend colours of 036 stay.
1703
+ expect(depHtml).toContain(`[attr.stroke]="selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)"`);
1704
+ });
1705
+
1706
+ const expected = {
1707
+ progressTrack: [3.96, 6.69],
1708
+ ringPages: [4.17, 7.57],
1709
+ ringHovered: [3.96, 6.69],
1710
+ ringUnread: [3.75, 5.77],
1711
+ // Design §5 gave 3.23 light with the hover composited over the tints; the
1712
+ // tint replaces the hover fill, so the lowest real line is the error tint.
1713
+ ringLogLines: [3.49, 7.47],
1714
+ ringTerminalLines: [3.28, 7.58],
1715
+ ringLogToolbar: [4.02, 9.12],
1716
+ ringCodeHeader: [3.78, 8.74],
1717
+ ringTreeRows: [3.75, 6.96],
1718
+ errorPages: [8.02, 7.21],
1719
+ depField: [4.83, 6.7],
1720
+ depFieldOwnFill: [4.39, 5.69],
1721
+ depActive: [5.77, 10.8],
1722
+ };
1723
+ const floor = (n: number) => Math.floor(n * 100) / 100;
1724
+
1725
+ describe.each(palettes.map(([name, tokens], i) => [name, tokens, i] as const))('%s palette', (_palette, tokens, i) => {
1726
+ const c = (name: string) => color(tokens, name);
1727
+ const lowest = (fg: Rgba, bases: Rgba[]) => Math.min(...bases.map((bg) => ratio(over(fg, bg), bg)));
1728
+
1729
+ function measure() {
1730
+ const S = c('--color-surface');
1731
+ const pages = [c('--color-background'), S, c('--color-surface-2')];
1732
+ const hovered = pages.map((p) => over(c('--color-surface-hover'), p));
1733
+ const ring = c('--color-primary-500');
1734
+
1735
+ const log = (selector: string) => parse(fills('log', selector)['background-color']);
1736
+ const logBg = S;
1737
+ const T = log('.lc-log-viewer--terminal');
1738
+ const hover = log('.lc-log-viewer__line:hover');
1739
+ // A tint (0,2,0, later) replaces the hover fill on warn and error lines,
1740
+ // so hover only lightens the untinted lines.
1741
+ const lines = (base: Rgba, variant: string) => [
1742
+ base,
1743
+ over(hover, base),
1744
+ over(log(`${variant} .lc-log-viewer__line--warn`), base),
1745
+ over(log(`${variant} .lc-log-viewer__line--error`), base),
1746
+ ];
1747
+
1748
+ const codeVar = (name: string) => parse(sheets.code.match(new RegExp(`${name}: (#[0-9a-f]{6})`, 'i'))?.[1] ?? '');
1749
+ const header = over(parse(fills('code', '.code-block__header')['background-color']), codeVar('--lc-code-block-bg'));
1750
+ const copyHover = over(parse(fills('code', '.code-block__copy:hover')['background-color']), header);
1751
+
1752
+ const treeRows = [S, over(c('--color-surface-hover'), S), over(c('--color-primary-50'), S), over(c('--color-primary-100'), S)];
1753
+ const field = c('--color-field-border');
1754
+
1755
+ return {
1756
+ progressTrack: lowest(ring, pages.map((p) => over(c('--color-surface-hover'), p))),
1757
+ ringPages: lowest(ring, pages),
1758
+ ringHovered: lowest(ring, hovered),
1759
+ ringUnread: lowest(ring, [S, c('--color-surface-2')].map((p) => over(c('--color-surface-selected'), p))),
1760
+ ringLogLines: lowest(ring, lines(logBg, '.lc-log-viewer--log')),
1761
+ ringTerminalLines: lowest(ring, lines(T, '.lc-log-viewer--terminal')),
1762
+ ringLogToolbar: lowest(ring, [S, parse('#181825')]),
1763
+ ringCodeHeader: lowest(ring, [header, copyHover]),
1764
+ ringTreeRows: lowest(ring, treeRows),
1765
+ errorPages: Math.min(lowest(c('--color-error-600'), pages), lowest(c('--color-error-default'), pages)),
1766
+ depField: lowest(field, [S]),
1767
+ depFieldOwnFill: lowest(field, [over(c('--color-neutral-50'), S), over(c('--color-neutral-100'), S)]),
1768
+ depActive: lowest(c('--color-primary-600'), [S, over(c('--color-primary-50'), S)]),
1769
+ };
1770
+ }
1771
+
1772
+ it('AC-1, AC-2, AC-6: the design 039 §5 ratios; fills and boundaries ≥ 3:1', () => {
1773
+ const m = measure();
1774
+ for (const key of Object.keys(expected) as (keyof typeof expected)[]) {
1775
+ expect([key, floor(m[key])]).toEqual([key, expected[key][i]]);
1776
+ }
1777
+ expect(Math.min(...Object.values(m))).toBeGreaterThanOrEqual(3);
1778
+ });
1779
+ });
1780
+ });
@@ -49,7 +49,8 @@ export type DescriptionListSeparator = 'line' | 'divider' | 'none';
49
49
  * - `stacked` layout: term above value, for narrow columns.
50
50
  * - Per-row value emphasis and optional links.
51
51
  * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`
52
- * rows are focusable and keyboard-activatable.
52
+ * rows hold a native button (named by term and value) that the keyboard
53
+ * reaches, and the `dl` keeps its term and value pairs.
53
54
  *
54
55
  * @example
55
56
  * ```html
@@ -87,17 +88,28 @@ export declare class DescriptionListComponent {
87
88
  */
88
89
  readonly separator: import("@angular/core").InputSignal<DescriptionListSeparator>;
89
90
  /**
90
- * Whether rows are interactive. When set, each row is focusable and
91
- * Enter / Space emit `itemClick`, so the click handler is reachable from
92
- * the keyboard. Set this whenever you handle `itemClick`.
91
+ * Whether rows are interactive. When set, each row holds a native button,
92
+ * named by its term and value, that Tab reaches; Enter, Space and a click
93
+ * anywhere on the row emit `itemClick`. The `dl` keeps its term and value
94
+ * pairs. Set this whenever you handle `itemClick`.
93
95
  * @default false
94
96
  */
95
97
  readonly clickable: import("@angular/core").InputSignal<boolean>;
96
98
  /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */
97
99
  readonly itemClick: import("@angular/core").OutputEmitterRef<DescriptionListItem>;
100
+ /** Generated id prefix, taken once per instance, so later ids don't shift. */
101
+ private readonly uid;
102
+ /** Id of row `index`'s term (only rendered when `clickable`). */
103
+ protected termId(index: number): string;
104
+ /** Id of row `index`'s value (only rendered when `clickable`). */
105
+ protected valueId(index: number): string;
98
106
  protected readonly hostClasses: import("@angular/core").Signal<string>;
99
107
  protected valueClass(item: DescriptionListItem): string;
100
- /** Keyboard activation of a clickable row (only from the row itself, not a nested link). */
108
+ /**
109
+ * Keydown dispatched on the row itself (a consumer's test, a script). Keys
110
+ * from the row's button or a nested link are theirs: the button's native
111
+ * activation clicks it, and the click bubbles to the row.
112
+ */
101
113
  protected onRowKeydown(event: KeyboardEvent, item: DescriptionListItem): void;
102
114
  static ɵfac: i0.ɵɵFactoryDeclaration<DescriptionListComponent, never>;
103
115
  static ɵcmp: i0.ɵɵComponentDeclaration<DescriptionListComponent, "lc-description-list", never, {