@konce-pt/angular 0.9.0 → 0.10.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 (73) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/fesm2022/konce-pt-angular.mjs +5445 -1755
  3. package/fesm2022/konce-pt-angular.mjs.map +1 -1
  4. package/package.json +11 -11
  5. package/src/lib/accordion/llms.txt +6 -0
  6. package/src/lib/alert/llms.txt +7 -0
  7. package/src/lib/app-shell/llms.txt +4 -3
  8. package/src/lib/autocomplete/llms.txt +13 -0
  9. package/src/lib/avatar/llms.txt +6 -3
  10. package/src/lib/avatar-group/llms.txt +6 -0
  11. package/src/lib/badge/llms.txt +11 -2
  12. package/src/lib/bottom-sheet/llms.txt +8 -1
  13. package/src/lib/breadcrumb/llms.txt +5 -0
  14. package/src/lib/button/llms.txt +3 -0
  15. package/src/lib/card/llms.txt +8 -0
  16. package/src/lib/carousel/llms.txt +13 -1
  17. package/src/lib/checkbox/llms.txt +10 -0
  18. package/src/lib/chip/llms.txt +6 -0
  19. package/src/lib/chips-input/llms.txt +12 -0
  20. package/src/lib/color-picker/llms.txt +14 -0
  21. package/src/lib/confirm/llms.txt +5 -0
  22. package/src/lib/context-menu/llms.txt +8 -0
  23. package/src/lib/data-table/llms.txt +5 -4
  24. package/src/lib/date-range/llms.txt +24 -2
  25. package/src/lib/datepicker/llms.txt +25 -5
  26. package/src/lib/dialog/llms.txt +18 -0
  27. package/src/lib/drawer/llms.txt +7 -1
  28. package/src/lib/empty/llms.txt +6 -0
  29. package/src/lib/fab/llms.txt +5 -2
  30. package/src/lib/file-upload/llms.txt +12 -1
  31. package/src/lib/form-field/llms.txt +24 -4
  32. package/src/lib/galleria/llms.txt +13 -1
  33. package/src/lib/i18n/llms.txt +2 -2
  34. package/src/lib/icon/llms.txt +9 -1
  35. package/src/lib/icon-button/llms.txt +3 -1
  36. package/src/lib/image/llms.txt +10 -1
  37. package/src/lib/input/llms.txt +6 -0
  38. package/src/lib/input-mask/llms.txt +11 -0
  39. package/src/lib/input-number/llms.txt +18 -0
  40. package/src/lib/input-otp/llms.txt +14 -0
  41. package/src/lib/knob/llms.txt +8 -0
  42. package/src/lib/listbox/llms.txt +16 -1
  43. package/src/lib/megamenu/llms.txt +21 -1
  44. package/src/lib/menu/llms.txt +14 -4
  45. package/src/lib/menubar/llms.txt +21 -0
  46. package/src/lib/meter-group/llms.txt +10 -0
  47. package/src/lib/order-list/llms.txt +11 -2
  48. package/src/lib/panel/llms.txt +7 -0
  49. package/src/lib/password/llms.txt +9 -1
  50. package/src/lib/pick-list/llms.txt +12 -2
  51. package/src/lib/popover/llms.txt +11 -0
  52. package/src/lib/radio-group/llms.txt +11 -0
  53. package/src/lib/rating/llms.txt +18 -4
  54. package/src/lib/rich-text/llms.txt +18 -0
  55. package/src/lib/scroll-top/llms.txt +12 -0
  56. package/src/lib/select/llms.txt +23 -5
  57. package/src/lib/sidenav/llms.txt +6 -0
  58. package/src/lib/skeleton/llms.txt +2 -1
  59. package/src/lib/slider/llms.txt +13 -0
  60. package/src/lib/speed-dial/llms.txt +8 -0
  61. package/src/lib/spinner/llms.txt +10 -1
  62. package/src/lib/split-button/llms.txt +11 -2
  63. package/src/lib/splitter/llms.txt +10 -1
  64. package/src/lib/stepper/llms.txt +8 -0
  65. package/src/lib/switch/llms.txt +11 -0
  66. package/src/lib/switch-group/llms.txt +11 -0
  67. package/src/lib/tabs/llms.txt +7 -0
  68. package/src/lib/textarea/llms.txt +12 -0
  69. package/src/lib/toast/llms.txt +19 -1
  70. package/src/lib/tooltip/llms.txt +12 -0
  71. package/src/lib/tree/llms.txt +21 -0
  72. package/types/konce-pt-angular.d.ts +1047 -73
  73. package/types/konce-pt-angular.d.ts.map +1 -1
@@ -17,11 +17,22 @@ Requires the CDK overlay styles in the application: `import '@angular/cdk/overla
17
17
  - `filter`: boolean — a search field; `multiple`: boolean — multi-select (chips)
18
18
  - `disabled`/`invalid`/`touched`: boolean; the `touch` output.
19
19
  - `open`: model (two-way `[(open)]`, the `(openChange)` output) — the panel state. It lets you open
20
- the list from outside and know when the user is done choosing. For the latter **do not use `touch`**:
21
- it also fires on the trigger's blur, that is, the moment the mouse goes down on an option in the panel (the panel
22
- lives in a CDK Overlay container next to `<body>`, outside the control's DOM). That is what inline editing
23
- in `kpt-data-table` does — see `data-table/llms.txt`.
24
- - Keyboard: Enter/↓ opens, ↑/↓ navigate, Enter selects, Esc closes.
20
+ the list from outside and know when the user is done choosing — that is what inline editing
21
+ in `kpt-data-table` does, see `data-table/llms.txt`. `touch` fires when the panel closes and when
22
+ focus leaves the closed trigger; moving focus into the search field does not count as leaving.
23
+ - `ariaLabel`: string|null — the accessible name of the trigger and the list when no `<label for>`
24
+ describes the field.
25
+
26
+ ## Keyboard
27
+ The select-only combobox pattern: focus stays on the trigger, and the active option is announced
28
+ through `aria-activedescendant` (with `filter`, focus moves to the search field, which is the combobox).
29
+ - Closed: `↓`/`↑`/`Enter`/`Space`/`Home`/`End` open the list; typing letters opens it on the first
30
+ match; in `multiple` mode `Backspace`/`Delete` removes the last chip.
31
+ - Open: `↑`/`↓` move by one, `Home`/`End` to the ends, `PageUp`/`PageDown` by ten — disabled options
32
+ are skipped, the list does not wrap. `Enter` (and `Space` on the trigger) picks; a single select
33
+ closes and returns focus to the trigger, a multiple one stays open. Typing jumps to the next option
34
+ starting with the letters (diacritics ignored). `Escape` and `Tab` close the list.
35
+ - Clicking an option does not take focus from the trigger, so a mouse pick does not blur the field.
25
36
 
26
37
  ## Float label
27
38
  In a `kpt-form-field` with `labelType` `over`/`in`/`on` the label also floats once an option is chosen —
@@ -36,5 +47,12 @@ be read from the DOM). Nothing needs configuring:
36
47
  The "Search…" and "No results" labels and the chip-removal aria text come from i18n (the `select` namespace).
37
48
  English by default; configured through `provideKptI18n` — see `i18n/llms.txt`.
38
49
 
50
+ ## Label and description
51
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
52
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
53
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
54
+ `aria-describedby`. No `controlId` needed. New: `id` and `required` (→ `aria-required`); the list
55
+ gets the same name as the trigger.
56
+
39
57
  ## Tokens
40
58
  `--kpt-form-field-{bg,text,border,border-hover,border-focus,border-error,radius}`.
@@ -39,5 +39,11 @@ hidden technically (visually hidden), so a screen reader still knows the entry's
39
39
 
40
40
  These are plain attributes, not directives — there is nothing to import. Without an icon the rail will be empty.
41
41
 
42
+ ## Links
43
+ Links in the scroll area are `display: flex` with a gap, so an icon and a label never glue together
44
+ even when the template compiler drops the whitespace between them. A link with
45
+ `aria-current="page"` gets the muted background and a medium weight — mark the current page
46
+ yourself, the component does not know which one it is.
47
+
42
48
  ## Tokens
43
49
  `--kpt-sidenav-{bg,fg,border,width,rail-width}` (the width is set by `kpt-app-shell`).
@@ -4,6 +4,7 @@ A loading placeholder (shimmer). Import: `import { KptSkeleton } from '@konce-pt
4
4
 
5
5
  ## Inputs
6
6
  - `variant`: 'text'|'circle'|'rect'; `width`: string; `height`: string; `lines`: number (for text)
7
+ - With `lines` above 1 the last line is 60% wide, so it reads as a paragraph; a single line keeps the full `width`.
7
8
 
8
9
  ## Examples
9
10
  <kpt-skeleton variant="text" [lines]="3" />
@@ -11,4 +12,4 @@ A loading placeholder (shimmer). Import: `import { KptSkeleton } from '@konce-pt
11
12
  <kpt-skeleton variant="rect" width="100%" height="8rem" />
12
13
 
13
14
  ## Tokens
14
- A gradient over `--kpt-color-surface-variant`.
15
+ The base `--kpt-color-muted`; the shimmer is a lighter band mixed with `--kpt-color-surface`; corners `--kpt-radius-md` (blocks) / `--kpt-radius-sm` (text lines).
@@ -21,5 +21,18 @@ A value slider on Signal Forms. Import: `import { KptSlider } from '@konce-pt/an
21
21
  - The `range` mode uses two stacked `input[type=range]` elements; `pointer-events` are active only on the handles (the track does not intercept them).
22
22
  - The handles cannot cross — the lower one is clamped to the upper one and vice versa.
23
23
 
24
+ ## Screen reader and keyboard
25
+ `suffix`: string (" zł", "%", " km") — shown after the value and put into `aria-valuetext` ("40
26
+ zł"), so the unit is read too. `aria-invalid` after touch.
27
+
28
+ ## Label and description
29
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
30
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
31
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
32
+ `ariaLabel`. With `range` the thumbs are named "Minimum"/"Maximum" from i18n
33
+ (`slider.min`/`slider.max`, previously hard-coded and mixed-language); inside `kpt-form-field` each
34
+ thumb reads "<label> Minimum" through `aria-labelledby`, without one `ariaLabel` gives "<ariaLabel>,
35
+ Minimum". `id` goes to the single input or the lower thumb.
36
+
24
37
  ## Tokens
25
38
  The track is filled with `--kpt-color-primary`, the background is `--kpt-color-surface-variant`.
@@ -12,5 +12,13 @@ Import: `import { KptSpeedDial } from '@konce-pt/angular';`
12
12
  ## Example
13
13
  <kpt-speed-dial [items]="actions" direction="up" (selected)="onAction($event)" />
14
14
 
15
+ ## Accessibility
16
+ - The main button carries `aria-expanded` and `aria-controls` pointing at the actions container.
17
+ - Closed actions are `visibility: hidden` (switched after the fade-out), so they are out of the Tab
18
+ order and the accessibility tree — not just transparent.
19
+ - `Escape` anywhere in the dial closes it and returns focus to the main button.
20
+ - Every action is a `<button>` labelled with its own `label`, so the fan works from the keyboard even
21
+ when the actions show icons only.
22
+
15
23
  ## Tokens
16
24
  The main FAB comes from `kpt-fab`; the actions use `--kpt-color-surface-raised`, `--kpt-elevation-2`.
@@ -1,6 +1,6 @@
1
1
  # KptSpinner (kpt-spinner)
2
2
 
3
- Loading indicator. Color from `currentColor`. Import: `import { KptSpinner } from '@konce-pt/angular';`
3
+ Loading indicator. Primary colour by default; `color` on the host changes it (`color: inherit` takes the surrounding text). Import: `import { KptSpinner } from '@konce-pt/angular';`
4
4
 
5
5
  ## Inputs
6
6
  - `size`: string (e.g. '2rem', default '1.5rem'); `label`: string (aria)
@@ -8,3 +8,12 @@ Loading indicator. Color from `currentColor`. Import: `import { KptSpinner } fro
8
8
  ## Example
9
9
  <kpt-spinner />
10
10
  <kpt-spinner size="2rem" label="Loading…" />
11
+
12
+ ## Tokens
13
+ The host sets `color: var(--kpt-color-primary)` and the ring is drawn in `currentColor`; the ring width is 8% of `size`, at least 2px.
14
+
15
+ ## Accessibility
16
+ The ring is `aria-hidden`; next to it sits a visually hidden `.kpt-spinner__label` with
17
+ `role="status"`. Its text — `label`, or `spinner.label` — is written a frame after the spinner
18
+ appears, because a status region that is born already filled in is skipped by most screen readers.
19
+ Under `prefers-reduced-motion` the rotation slows down instead of stopping.
@@ -5,7 +5,9 @@ Import: `import { KptSplitButton } from '@konce-pt/angular';`
5
5
  Requires `import '@angular/cdk/overlay-prebuilt.css';`
6
6
 
7
7
  ## Inputs
8
- - `variant`: KptButtonVariant (default 'filled')
8
+ - `variant`: `KptSplitButtonVariant` = 'filled' | 'tonal' | 'outline' | 'text' (default 'filled') —
9
+ the subset of `KptButtonVariant` that has a style; `outline` and `text` use the `--kpt-button-outline-*`
10
+ and `--kpt-button-text-*` tokens, so they match `kpt-button` in the same row
9
11
  - `items`: KptMenuItem<T>[] — { label, value?, icon?, danger?, disabled?, separator? }
10
12
  It also honors `header` (a non-clickable row with `avatar`/`label`/`description`), `description`
11
13
  (a second line) and `badge` + `badgeVariant` (a badge on the right) — described in full in menu/llms.txt.
@@ -15,6 +17,12 @@ Requires `import '@angular/cdk/overlay-prebuilt.css';`
15
17
  - `primaryClick`: void — a click on the primary button
16
18
  - `selected`: KptMenuItem<T> — an entry picked from the menu
17
19
 
20
+ ## Keyboard
21
+ The toggle is a menu button: `aria-haspopup="menu"`, a live `aria-expanded`, `aria-controls` while
22
+ open. `↓` / `↑` on it open the menu on the first / last item; inside, `↓` / `↑` wrap, `Home` / `End`
23
+ jump, `Escape` / `Tab` close, and closing or picking returns focus to the toggle. Same rules as the
24
+ menu — see menu/llms.txt.
25
+
18
26
  ## Example
19
27
  <kpt-split-button [items]="items" (primaryClick)="save()" (selected)="onItem($event)">Save</kpt-split-button>
20
28
 
@@ -25,4 +33,5 @@ Requires `import '@angular/cdk/overlay-prebuilt.css';`
25
33
  ];
26
34
 
27
35
  ## Tokens
28
- Colors inherited from the button; the panel `--kpt-color-surface-raised`, `--kpt-elevation-3`.
36
+ Colors: `filled` / `tonal` from `--kpt-color-primary` / `--kpt-color-muted`, `outline` / `text`
37
+ from the `--kpt-button-*` tokens of those variants; the panel `--kpt-color-surface-raised`, `--kpt-elevation-3`.
@@ -7,9 +7,18 @@ Import: `import { KptSplitter } from '@konce-pt/angular';`
7
7
  - `orientation`: 'horizontal' | 'vertical' (KptSplitterOrientation)
8
8
  - `startSize`: model<number> — the size of the first pane in % (two-way)
9
9
  - `minSize`: number (%) — the minimum pane size
10
+ - `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
11
+ - `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
12
+ - `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
13
+ - `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
10
14
 
11
15
  ## Accessibility
12
- The handle is a `role="separator"`, arrow keys resize it; dragging works with the mouse.
16
+ The handle is a focusable `role="separator"` (the WAI-ARIA window splitter): `aria-valuenow`,
17
+ `aria-valuemin`/`aria-valuemax` (from `minSize`), `aria-controls` pointing at the first pane, and a
18
+ name from `ariaLabel` or `splitter.resize` in the dictionary. Arrow keys resize by 2%, `Home`/`End`
19
+ jump to the minimum/maximum. `aria-orientation` describes the handle's line, not the layout —
20
+ side-by-side panes have a vertical handle. The host takes `height: 100%`, so a height set on the
21
+ parent reaches the panes.
13
22
 
14
23
  ## Example
15
24
  <kpt-splitter orientation="horizontal" [(startSize)]="size">
@@ -19,3 +19,11 @@ Import: `import { KptStepper, KptStep } from '@konce-pt/angular';`
19
19
 
20
20
  ## Tokens
21
21
  The active/completed step: `--kpt-color-primary` (marker, connector).
22
+
23
+ ## Accessibility
24
+ The header is an `<ol>` of real buttons, so every step is reachable with the keyboard and the list
25
+ announces how many there are. Each marker is named from the dictionary — `stepper.step`
26
+ (“Step 2: Delivery”) or `stepper.stepDone` (“…, completed”) — because the visible marker is only a
27
+ number or a check; the visible label is `aria-hidden` so it is not read twice. The active marker
28
+ carries `aria-current="step"`. After `next()` focus stays where it was — move it into the new step
29
+ yourself when the step is long.
@@ -13,5 +13,16 @@ Import: `import { KptSwitch } from '@konce-pt/angular';`
13
13
  - `labelPosition`: 'start' | 'end' (default 'end') — the `label` to the left or to the right of the switch.
14
14
  - `onLabel`/`offLabel`: string — the state text; shown according to `checked` (the on state highlighted with `--kpt-color-primary`). Can be combined with a static `label`.
15
15
 
16
+ ## Screen reader and keyboard
17
+ The `onLabel`/`offLabel` text is `aria-hidden`: `role="switch"` already announces the state, and
18
+ text inside the `<label>` would change the switch's name on every toggle.
19
+
20
+ ## Label and description
21
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
22
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
23
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
24
+ `ariaLabel`. Inside `kpt-form-field` the switch gets both names: the wrapper label and its own
25
+ `label`.
26
+
16
27
  ## Tokens
17
28
  `--kpt-color-primary`, `--kpt-color-border-strong`, `--kpt-color-surface`, `--kpt-elevation-1`.
@@ -22,3 +22,14 @@ Built on Signal Forms (`FormValueControl<T | T[] | null>`, the `value` model). I
22
22
  Every entry is a `kpt-switch` with a one-way `[checked]`. In single mode turning one on sets `value` to that
23
23
  option, which switches the others off automatically. In `multiple` mode the value is normalized to an array — turning
24
24
  one on adds the option's value, turning it off removes it. It renders `kpt-switch`, so it inherits its look and tokens.
25
+
26
+ ## Screen reader and keyboard
27
+ `aria-invalid` is not supported on `role="group"`, so the error state is carried by each switch. In
28
+ single mode the switches are mutually exclusive and nothing announces that turning one on turned
29
+ another off — for a single choice prefer `kpt-radio-group`.
30
+
31
+ ## Label and description
32
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
33
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
34
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
35
+ `aria-describedby`. No `controlId` needed. New: `id` and `ariaLabel` on the `group`.
@@ -19,3 +19,10 @@ Import: `import { KptTabs, KptTab } from '@konce-pt/angular';`
19
19
 
20
20
  ## Tokens
21
21
  The active tab: `--kpt-color-primary` (text + bottom edge).
22
+
23
+ ## Keyboard and ARIA
24
+ The tab row is a single `Tab` stop (roving `tabindex`): `ArrowLeft`/`ArrowRight` move between tabs,
25
+ wrapping at the ends, `Home`/`End` jump to the first/last one, and disabled tabs are skipped.
26
+ Moving activates the tab straight away; the next `Tab` goes to the panel (`tabindex="0"`).
27
+ Each tab has an `id`, the active one points at the panel with `aria-controls`, and the panel is
28
+ named by the active tab (`aria-labelledby`).
@@ -10,5 +10,17 @@ Import: `import { KptTextarea } from '@konce-pt/angular';`
10
10
  ## Example
11
11
  <kpt-textarea [formField]="f.notes" placeholder="Notes…" autoResize />
12
12
 
13
+ ## Screen reader and keyboard
14
+ `maxLength`: number — the native `maxlength` (under `[formField]` it comes from `maxLength()` in the
15
+ schema); `counter`: boolean — a "12 / 280" counter under the field, read together with it through
16
+ `aria-describedby`.
17
+
18
+ ## Label and description
19
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
20
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
21
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
22
+ `ariaLabel`. New: `id`, `ariaLabel` and `required` (→ `aria-required`); `[formField]` binds
23
+ `required`.
24
+
13
25
  ## Tokens
14
26
  `--kpt-form-field-*`.
@@ -13,10 +13,28 @@ Place the container once in the application: `<kpt-toast-container />`.
13
13
  toast.info(msg, opts); toast.warning(msg, opts);
14
14
  toast.show(msg, { variant, title, duration }); // duration in ms, 0 = no auto-dismiss
15
15
  toast.dismiss(id); toast.clear();
16
+ toast.success('Deleted', { duration: 8000, action: { label: 'Undo', run: () => restore() } });
17
+ toast.pause(); toast.resume();
16
18
 
17
19
  ## KptToast
18
- `{ id, message, title?, variant: 'info'|'success'|'warning'|'danger', duration }`
20
+ `{ id, message, title?, variant: 'info'|'success'|'warning'|'danger', duration, action?: { label, run } }`
21
+
22
+ ## Actions and pausing
23
+ `action: { label, run }` adds one button to the toast (typically “Undo”); a click calls `run` and
24
+ dismisses the toast. Give such toasts a longer `duration`, and keep the action available elsewhere.
25
+ `pause()` / `resume()` stop and continue every timer; the container calls them on hover and focus,
26
+ and resumes by itself when the stack empties.
19
27
 
20
28
  ## Tokens
21
29
  `--kpt-color-surface-raised`, `--kpt-elevation-3`, the accent `--kpt-color-{info,success,warning,danger}`,
22
30
  stacking `--kpt-z-toast`.
31
+
32
+ ## Accessibility
33
+ Announcements go through two permanent, visually hidden live regions next to the stack —
34
+ `aria-live="polite"` for ordinary toasts and `aria-live="assertive"` for `danger` — and not through
35
+ the toasts themselves: a live region has to exist before content appears in it, and nested regions
36
+ are read twice by some screen readers. Each region is cleared before it is written, so the same
37
+ text twice is announced twice, and a dismissed toast never re-announces an older one. While there
38
+ are toasts the stack is `role="region"` named `toast.region` (“Notifications”). Pointer or focus on
39
+ the stack pauses every timer (WCAG 2.2.1); the timers resume with the time that was left. The close
40
+ button is labelled from `common.close`.
@@ -29,5 +29,17 @@ current after a change in width, zoom or layout, with no `ResizeObserver`.
29
29
  <kpt-button kptTooltip="Save changes" kptTooltipPosition="bottom">Save</kpt-button>
30
30
  <span kptTooltip="More information">?</span>
31
31
 
32
+ ## Accessibility
33
+ - The bubble appears on hover and on keyboard focus. Focus is tracked with `focusin`/`focusout`,
34
+ so it also works when the focus lands on an inner element (`kpt-button`, `kpt-icon-button`).
35
+ - The text reaches screen readers through a persistent, hidden description linked with
36
+ `aria-describedby` to the element that actually takes focus (the bubble itself only exists while
37
+ shown). It is skipped when it would repeat the element's name (an icon button whose label equals
38
+ the tooltip) and with `onlyWhenTruncated`, where the text is already in the DOM; the visible
39
+ bubble is then `aria-hidden`.
40
+ - `Escape` closes the bubble, and the pointer can move onto it without it disappearing (a 120 ms
41
+ grace period) — WCAG 1.4.13.
42
+ - It does NOT replace an accessible name — an icon-only button still needs its own label.
43
+
32
44
  ## Tokens
33
45
  Background `--kpt-color-on-surface`, text `--kpt-color-surface` (contrast in both themes), `--kpt-elevation-2`.
@@ -13,5 +13,26 @@ Import: `import { KptTree, KptTreeNode } from '@konce-pt/angular';`
13
13
  ## Example
14
14
  <kpt-tree [nodes]="tree" selectable [(selected)]="node" (nodeClick)="open($event)" />
15
15
 
16
+ ## Expansion is component state, not data
17
+ `expanded` in a node is the initial state only. The component keeps what the user opened or closed
18
+ itself and never writes to the node objects — so two trees fed the same nodes open and close
19
+ independently, and replacing `nodes` with new objects brings their own initial state.
20
+
21
+ ## Keyboard
22
+ The tree is one Tab stop (roving tabindex on the `treeitem` elements), following the WAI-ARIA tree view
23
+ pattern:
24
+ - `↓` / `↑` — the next / previous visible row, `Home` / `End` — the first / last;
25
+ - `→` — expands a closed branch, on an open one moves to its first child;
26
+ - `←` — collapses an open branch, otherwise moves to the parent;
27
+ - `Enter` / `Space` — the same as a click: selects (with `selectable`) or toggles the branch.
28
+ The logic is the pure function `treeKeyAction` in `tree-keys.ts`; the other port has a twin file.
29
+
30
+ ## Accessibility
31
+ `role="tree"`, `treeitem` and `group`; branches carry `aria-expanded`, every row `aria-level`,
32
+ selectable trees `aria-selected`, disabled rows `aria-disabled`. The chevron is mouse-only
33
+ (`aria-hidden`): a button inside a `treeitem` would be a nested control and a second Tab stop per
34
+ row, and the keyboard expands with the arrows anyway. The focus ring is drawn on the row, not on the
35
+ `<li>`, which would outline the whole subtree.
36
+
16
37
  ## Tokens
17
38
  Row hover `--kpt-color-surface-variant`; the selected row `--kpt-color-primary`.