@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
@@ -12,7 +12,7 @@ Import: `import { KptFormField } from '@konce-pt/angular';`
12
12
  - `hint`: string — a hint (hidden while there is an error)
13
13
  - `error`: string | null — the error message; when non-empty, the field is marked invalid
14
14
  - `required`: boolean — an asterisk next to the label
15
- - `controlId`: string — the control's id, for the `label[for]` association
15
+ - `controlId`: string — optional; the control's id for `label[for]` (library controls report theirs)
16
16
  - `labelType`: 'fixed' | 'over' | 'in' | 'on' — the label variant (default `fixed`)
17
17
 
18
18
  ## Label variants (labelType)
@@ -48,11 +48,30 @@ reports being filled itself — it provides the `KPT_FORM_FIELD_CONTROL` token w
48
48
  Once a control provides the token, `kpt-form-field` stops looking into the DOM and trusts the
49
49
  `filled` signal alone — which is why the label rises the instant the value changes.
50
50
 
51
+ ## Linking the control (label, hint, error)
52
+ `kpt-form-field` provides the `KPT_FORM_FIELD` context to its content, and every library control
53
+ uses it: the control reports its `id` (its own, or a generated one) and `label[for]` points at it,
54
+ so **`controlId` is optional**. A control on a `div` with a role (`kpt-select`, `kpt-datepicker`,
55
+ `kpt-radio-group`, `kpt-rich-text`…) is not named by `label[for]`, so it gets `aria-labelledby`
56
+ pointing at the label. The visible hint or error has an `id` and lands in the control's
57
+ `aria-describedby` — a screen reader hears it when it reaches the field, not only when the error appears.
58
+
59
+ A custom control links itself with `kptFieldA11y`:
60
+
61
+ import { kptFieldA11y } from '@konce-pt/angular';
62
+
63
+ readonly id = input<string | null>(null);
64
+ protected readonly field = kptFieldA11y(this.id, 'my-control');
65
+ // template: [id]="field.id()" [attr.aria-labelledby]="field.labelledBy()"
66
+ // [attr.aria-describedby]="field.describedBy()"
67
+
68
+ `controlId` given explicitly still wins, and a control without its own `id` takes it.
69
+
51
70
  ## Content projection
52
71
  The control goes into the content (ng-content):
53
72
 
54
- <kpt-form-field label="E-mail" [error]="emailError()" hint="Your work address" required controlId="f-email">
55
- <kpt-input [formField]="loginForm.email" type="email" id="f-email" />
73
+ <kpt-form-field label="E-mail" [error]="emailError()" hint="Your work address" required>
74
+ <kpt-input [formField]="loginForm.email" type="email" />
56
75
  </kpt-form-field>
57
76
 
58
77
  ## Integration with Signal Forms
@@ -77,4 +96,5 @@ The float variants add: `--kpt-form-field-placeholder` (the resting label),
77
96
 
78
97
  ## Accessibility
79
98
  - The error message carries `role="alert"`.
80
- - Tie `controlId` to the control's `id` so the label is clickable (`label[for]`).
99
+ - The label, hint and error are linked to the control automatically (`label[for]` or `aria-labelledby`,
100
+ plus `aria-describedby`) — see "Linking the control" above.
@@ -11,5 +11,17 @@ Import: `import { KptGalleria } from '@konce-pt/angular';`
11
11
  ## Example
12
12
  <kpt-galleria [items]="photos" [(activeIndex)]="idx" />
13
13
 
14
+ ## Keyboard
15
+ Anywhere inside the gallery, `ArrowLeft`/`ArrowRight` move to the neighbouring image (reversed in
16
+ RTL) and `Home`/`End` to the first/last. When focus is on a thumbnail, it follows the active one.
17
+
14
18
  ## Tokens
15
- The stage `--kpt-color-muted`; the active thumbnail `--kpt-color-primary`.
19
+ The stage `--kpt-color-muted`; the active thumbnail `--kpt-color-primary`; the arrows `--kpt-color-surface` (85% via `color-mix`, solid on hover) / `--kpt-color-on-surface` with `--kpt-elevation-2`.
20
+
21
+ ## Accessibility
22
+ The large image carries its `alt`. Each thumbnail is a button named by the item's `alt` (or
23
+ `galleria.image` — “Image 3” — when there is none), with `alt=""` on the thumbnail image so the
24
+ name is not read twice; the active one carries `aria-current`. The arrows use `aria-disabled` at
25
+ the ends instead of `disabled`, so they keep the focus. A change of image is announced politely
26
+ through a hidden live region — `galleria.position` (“Image 2 of 4”) plus the `alt` — only after the
27
+ user acts, never on first render.
@@ -26,9 +26,9 @@ Import: `import { provideKptI18n, KptI18n } from '@konce-pt/angular';`
26
26
  ## The key contract (KptMessages)
27
27
  Namespaces: `common` (close/remove/previous/next/clear/done/all), `paginator` (rowsPerPage/range/prevPage/nextPage),
28
28
  `select`, `listbox`, `dataView`, `dataTable`, `confirm`, `fileUpload`, `chipsInput`, `dateRange`, `datepicker`,
29
- `pickList`, `orderList`, `inputNumber`, `password`, `rating`, `spinner`, `speedDial`, `scrollTop`, `splitButton`, `panel`,
29
+ `pickList`, `orderList`, `inputNumber`, `password`, `rating`, `slider`, `colorPicker`, `inputOtp`, `autocomplete`, `spinner`, `speedDial`, `scrollTop`, `splitButton`, `panel`, `splitter`, `stepper`, `image`, `toast`, `avatarGroup`, `meterGroup`,
30
30
  `breadcrumb`, `richText`, `auth` (including `auth.promo.*`). Interpolations: `paginator.range` ({start},{end},{total}), `dataTable.filter*` ({column}),
31
- `richText.styleHeading` ({level}), `auth.continueWith` ({provider}), `auth.passwordHint` and `auth.errorMinLength` ({min}), `rating.star` ({value},{max}). Day and month names in calendars are derived from `Intl` per locale (not from the dictionary).
31
+ `richText.styleHeading` ({level}), `auth.continueWith` ({provider}), `auth.passwordHint` and `auth.errorMinLength` ({min}), `rating.star` ({value},{max}), `panel.toggleNamed` ({header}), `stepper.step*` ({index},{label}), `carousel.slideOf` and `galleria.position` ({index},{total}), `image.previewOf` ({alt}), `avatarGroup.more` ({count}), `inputOtp.cell` ({index},{count}), `autocomplete.results` ({count}), `chipsInput.added`/`removed`/`duplicate` ({value}), `dateRange.rangeStart`/`rangeEnd`/`inRange`/`pickEnd` ({date}) and `dateRange.picked` ({start},{end}). Day and month names in calendars are derived from `Intl` per locale (not from the dictionary).
32
32
 
33
33
  ## Note
34
34
  Individual labels can also be overridden per component through the matching `input()` (e.g. `emptyMessage`, `acceptLabel`) —
@@ -11,6 +11,7 @@ the color from `currentColor`. Import: `import { KptIcon } from '@konce-pt/angul
11
11
  built-in names: once the Tabler set is registered there are 5130 legal names, so a closed
12
12
  union would reject icons that work at runtime.
13
13
  - `size`: string (optional, e.g. '1.25rem') — otherwise inherited from font-size
14
+ - `label`: string — a name for screen readers; with it the icon is `role="img"`, without it `aria-hidden`
14
15
 
15
16
  ## Available names (built in, no configuration)
16
17
  menu, x, chevron-up, chevron-down, chevron-left, chevron-right, check, search
@@ -23,7 +24,8 @@ clipboard, clipboard-text, table, mail, share, external-link, crosshair
23
24
  adjustments-horizontal, sun, moon, bell, settings, users, user-circle, logout, login
24
25
  layout-dashboard, home, activity, cube, shopping-cart, currency-dollar, arrow-left
25
26
  arrow-right, arrow-up-right, arrow-down-right, chart-bar, chart-line, filter, download
26
- refresh, dots, brand-github, brand-google
27
+ refresh, dots, brand-github, brand-google, layout-grid, grid-dots, checks, paperclip
28
+ send, message-circle, corner-up-left, mood-smile, device-floppy, arrow-back-up, printer
27
29
 
28
30
  These cover both what the components themselves draw and the staples of an application shell
29
31
  (theme, notifications, account, navigation, KPI). A typical admin panel needs nothing else.
@@ -66,3 +68,9 @@ at build time, never straight from runtime/CMS/upload data.
66
68
  ## Icon license
67
69
  Tabler Icons (MIT). The icons are extracted inline into `icons.ts`. See ICONS-LICENSE at the root.
68
70
  Adding an icon: add an entry to `KPT_ICONS`/`KptIconName` (a 24×24 grid, 2px stroke).
71
+
72
+ ## Accessibility
73
+ By default an icon is `aria-hidden` — decorative, which is right next to a text label or inside
74
+ an icon button with its own name. An icon that carries meaning on its own gets `label`: it becomes
75
+ `role="img"` with that `aria-label`. In development builds an unknown name logs one console
76
+ warning per name instead of rendering silently empty.
@@ -4,7 +4,9 @@ An icon-only button. `ariaLabel` is required.
4
4
  Import: `import { KptIconButton } from '@konce-pt/angular';`
5
5
 
6
6
  ## Inputs
7
- - `variant`: 'filled'|'tonal'|'outline'|'text'|'danger'; `size`: 'sm'|'md'|'lg'; `shape`: 'round'|'square'
7
+ - `variant`: 'filled'|'tonal'|'outline'|'text'|'danger' (default 'text'); `size`: 'sm'|'md'|'lg' (default 'md');
8
+ `shape`: 'round'|'square' (default 'round'). The type is the wider `KptButtonVariant` / `KptButtonSize`,
9
+ but only these values have a style
8
10
  - `type`, `disabled`, `loading`, `ariaLabel` (required)
9
11
  - `ariaPressed`: `boolean | null` = `null` — toggle state; lands on the inner `<button>`, so a screen reader announces it (`[attr.aria-pressed]` on the tag would sit on the host, which has no button role)
10
12
  - `ariaExpanded`: `boolean | null` = `null` — disclosure state for a button that opens a panel; lands on the inner `<button>` for the same reason
@@ -12,4 +12,13 @@ Import: `import { KptImage } from '@konce-pt/angular';`
12
12
  <kpt-image src="/photo.jpg" alt="Photo" preview width="240px" />
13
13
 
14
14
  ## Tokens
15
- A semi-transparent preview mask; the `zoom-in` icon; a dark overlay.
15
+ The radius `--kpt-radius-md`; the backdrop and the hover mask `--kpt-color-overlay-scrim`; the × button and the magnifier disc `--kpt-color-surface` / `--kpt-color-on-surface` with `--kpt-elevation-2`.
16
+
17
+ ## Accessibility
18
+ With `preview`, the thumbnail is a `<button aria-haspopup="dialog">` named “Show full size: <alt>”
19
+ (`image.previewOf`, or `image.preview` without `alt`), so it is reached with Tab and opened with
20
+ Enter or Space; the magnifier shows on hover and on `:focus-visible`. The preview is a native modal
21
+ `<dialog>` opened with `showModal()` and named by `alt`: focus moves to its × button and stays inside,
22
+ Escape closes it, the page behind is inert, document scrolling is locked while it is open, and focus
23
+ returns to the thumbnail. It sits in the top layer, so no ancestor with `transform` or
24
+ `overflow: hidden` can clip it.
@@ -39,6 +39,12 @@ The full flow: the `## A form from scratch (Signal Forms)` section in the root l
39
39
  `value` (model, required), `disabled`, `readonly`, `required`, `invalid`, `errors`,
40
40
  `touched`; the `touch` output (emitted on blur). Do not set them by hand when used with a form.
41
41
 
42
+ ## Label and description
43
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
44
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
45
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
46
+ `ariaLabel`.
47
+
42
48
  ## Tokens
43
49
  `--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
44
50
 
@@ -16,5 +16,16 @@ Import: `import { KptInputMask } from '@konce-pt/angular';`
16
16
  <kpt-input-mask [formField]="f.phone" mask="(999) 999-99-99" />
17
17
  <kpt-input-mask [(value)]="zip" mask="99-999" />
18
18
 
19
+ ## Screen reader and keyboard
20
+ The on-screen keyboard follows the mask: only `9` tokens (phone, postal code) give
21
+ `inputmode="numeric"`, otherwise `text`; override it with `inputMode`. `autocomplete` (`tel`,
22
+ `postal-code`…) lets the browser fill the field in.
23
+
24
+ ## Label and description
25
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
26
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
27
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
28
+ `ariaLabel`. New: `aria-invalid` after touch.
29
+
19
30
  ## Tokens
20
31
  The field uses `--kpt-form-field-*`.
@@ -62,5 +62,23 @@ and would not show up under any control.
62
62
  the error belongs to. Messages are supplied through `errorFor()` with a `touched()` gate — do not
63
63
  gate the validators. The full submit flow: `## A form from scratch (Signal Forms)` in the root llms.txt.
64
64
 
65
+ ## Keyboard
66
+ The field is `role="spinbutton"` with `aria-valuemin`/`aria-valuemax`/`aria-valuenow` (and
67
+ `aria-valuetext` with the `suffix`, "5 pcs"). `↑`/`↓` change the value by `step`,
68
+ `PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step`. A typed value is
69
+ **not** clamped while typing ("150" with `max` 99 stays "150", so the form's `max()` can report it) —
70
+ it is clamped on blur. The `−`/`+` buttons sit outside the Tab order and do not take focus from the
71
+ field, so focus never drops to `<body>` when a button goes disabled at a limit.
72
+
73
+ ## Screen reader and keyboard
74
+ The value is shown in the active locale's notation (PL `12,5`), without thousands separators — the
75
+ field accepts both a dot and a comma, and digit grouping would clash with what the user types.
76
+
77
+ ## Label and description
78
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
79
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
80
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
81
+ `ariaLabel`. New: `id`, `ariaLabel`, `required` and `aria-invalid` on the field.
82
+
65
83
  ## Tokens
66
84
  The field `--kpt-form-field-*`; the buttons `--kpt-color-muted`.
@@ -15,5 +15,19 @@ Typing a character moves to the next box; Backspace goes back; pasting spreads t
15
15
  ## Example
16
16
  <kpt-input-otp [formField]="f.code" [length]="6" integerOnly />
17
17
 
18
+ ## Screen reader and keyboard
19
+ The first cell has `autocomplete="one-time-code"`, so phones offer the code from an SMS; a code that
20
+ arrives in one cell at once (autofill, an on-screen keyboard paste) is spread over the cells like a
21
+ paste. `touch` fires when focus leaves the whole group — not after a paste, and not when moving
22
+ between cells.
23
+
24
+ ## Label and description
25
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
26
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
27
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
28
+ `aria-describedby`. No `controlId` needed. The cells form a named `role="group"`; `id` goes to the
29
+ first cell (clicking the label starts at the beginning), and each cell is named "Character 2 of 6"
30
+ (`inputOtp.cell`).
31
+
18
32
  ## Tokens
19
33
  The boxes use `--kpt-form-field-*`.
@@ -19,5 +19,13 @@ Import: `import { KptKnob } from '@konce-pt/angular';`
19
19
  <kpt-knob [formField]="f.level" [step]="5" /> <!-- bounds from min()/max() in form() -->
20
20
  <kpt-knob [(value)]="level" [min]="0" [max]="100" [step]="5" />
21
21
 
22
+ ## Label and description
23
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
24
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
25
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
26
+ `aria-describedby`. No `controlId` needed. New: `id`, `ariaLabel`; `aria-valuetext` includes
27
+ `suffix` ("40%"), and a disabled knob leaves the Tab order. Use `ariaLabel`, not `aria-label` on the
28
+ host — that one lands on the wrapper.
29
+
22
30
  ## Tokens
23
31
  The background arc `--kpt-color-border`; the value `--kpt-color-primary`; text `--kpt-color-on-surface`.
@@ -7,11 +7,26 @@ Value: `T` (single) or `T[]` (multiple). Import: `import { KptListbox } from '@k
7
7
  - `value`: model<T | T[] | null> (or `formField`)
8
8
  - `options`: KptOption<T>[] — { label, value, disabled? }
9
9
  - `multiple`: boolean; `filter`: boolean (a search field)
10
- - `disabled`/`invalid`/`touched`: boolean; `touch`: output
10
+ - `id`/`ariaLabel`: string|null; `required`: boolean
11
+ - `disabled`/`invalid`/`touched`: boolean; `touch`: output (blur of the list)
11
12
 
12
13
  ## Example
13
14
  <kpt-listbox [formField]="f.role" [options]="roles" filter />
14
15
  <kpt-listbox [(value)]="tags" [options]="tagOptions" multiple />
15
16
 
17
+ ## Keyboard
18
+ The WAI-ARIA listbox pattern: the list is one Tab stop and the active option is announced through
19
+ `aria-activedescendant`. `↑`/`↓`, `Home`/`End` and `PageUp`/`PageDown` move the active option,
20
+ skipping disabled ones (no wrapping); typing letters jumps to the next match, diacritics ignored;
21
+ `Space`/`Enter` picks; in `multiple` mode `Ctrl`/`⌘`+`A` selects every visible option (again —
22
+ clears them). The filter field sits before the list (outside the `listbox`) and `↓` moves focus
23
+ from it into the list; the filter itself ignores diacritics ("lodz" finds "Łódź").
24
+
25
+ ## Label and description
26
+ `id` goes to the list (`role="listbox"`), never the host. A `div` with a role is not named by
27
+ `label[for]`, so inside `kpt-form-field` the list gets `aria-labelledby` pointing at the wrapper's
28
+ label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. New: `id`,
29
+ `ariaLabel`, `required` (→ `aria-required`); `aria-invalid` after touch; `touch` fires on blur of the list.
30
+
16
31
  ## Tokens
17
32
  The frame `--kpt-form-field-*`; selection `--kpt-color-primary`.
@@ -8,7 +8,8 @@ Import: `import { KptMegamenu } from '@konce-pt/angular';`
8
8
 
9
9
  ## Inputs / outputs
10
10
  - `items`: KptMenuItem<T>[] (3 levels: top → columns → links)
11
- - `selected`: KptMenuItem<T> (the clicked link)
11
+ - `ariaLabel`: string — the bar's name for screen readers, e.g. "Main menu"
12
+ - `selected`: KptMenuItem<T> (the clicked link, or a top item without columns)
12
13
 
13
14
  ## Example
14
15
  items = [{
@@ -19,5 +20,24 @@ Import: `import { KptMegamenu } from '@konce-pt/angular';`
19
20
  ],
20
21
  }];
21
22
 
23
+ ## Links (`url`)
24
+ An entry with `url` and no children (and not disabled) renders as a real `<a href>` with the same
25
+ look, so the browser handles navigation — opening in a new tab, the status-bar preview. `selected`
26
+ still fires before the browser follows the link.
27
+
22
28
  ## Tokens
23
29
  The panel `--kpt-color-surface-raised`, `--kpt-elevation-3`.
30
+
31
+ ## Keyboard
32
+ The same as `kpt-menubar`: the bar is a single `Tab` stop (roving `tabindex`), `ArrowLeft`/`ArrowRight`
33
+ and `Home`/`End` move along it, `ArrowDown` (or `Enter`/`Space`) opens the panel with focus on the
34
+ first link, `ArrowUp` on the last one. In the panel `ArrowDown`/`ArrowUp`/`Home`/`End` walk through
35
+ all links column by column, `ArrowLeft`/`ArrowRight` show the neighbouring item's panel, and
36
+ `Escape` or `Tab` close it and return focus to the top item.
37
+
38
+ ## ARIA
39
+ The bar is `role="menubar"` (`aria-orientation="horizontal"`, named by `ariaLabel`); top items with
40
+ columns carry `aria-haspopup="menu"`, `aria-expanded` and `aria-controls`. The panel is
41
+ `role="menu"` named after the open item, each column is `role="group"` named by its title
42
+ (`aria-labelledby`), and links are `role="menuitem"`. A top item without columns acts as an action:
43
+ it emits `selected` instead of opening an empty panel, and has no caret.
@@ -21,7 +21,7 @@ Import: `import { KptMenu, KptMenuItem } from '@konce-pt/angular';`
21
21
  - `header?: boolean` — a header row: a non-clickable block of text instead of a menu entry. It holds the
22
22
  signed-in user's details (`avatar` + `label` + `description`) or a description of what the menu does. Rendered
23
23
  as a `<div role="presentation">` — it does not take focus and is not a `menuitem`.
24
- - `items?`, `url?` — used by the menubar / megamenu
24
+ - `items?`, `url?` — used by the menubar / megamenu; an entry with `url` (no children, not disabled) renders as `<a href>`
25
25
 
26
26
  The `header`, `description`, `badge` and `avatar` fields are honored by `kpt-context-menu` and `kpt-split-button` too.
27
27
 
@@ -43,12 +43,22 @@ The `header`, `description`, `badge` and `avatar` fields are honored by `kpt-con
43
43
  The panel `--kpt-color-surface-raised`, `--kpt-elevation-3`; a danger entry `--kpt-color-danger`;
44
44
  descriptions and icons `--kpt-color-on-surface-muted`, `--kpt-font-size-xs`.
45
45
 
46
+ ## Keyboard
47
+ - `↓` / `↑` on the trigger open the menu with focus on the first / last item; a click, `Enter`
48
+ or `Space` opens it on the first.
49
+ - Inside: `↓` / `↑` move between items and wrap around, `Home` / `End` jump to the ends;
50
+ disabled items, separators and headers are skipped.
51
+ - `Escape` and `Tab` close the menu, and so does picking an item — focus returns to the trigger.
52
+ - Items carry `tabindex="-1"` (roving focus), so an open menu is one Tab stop, not one per item.
53
+
54
+ The logic lives in `menu-keys.ts`, shared with `kpt-split-button`; the other port has a twin file.
55
+
46
56
  ## A11y
47
- The panel is `role="menu"`, entries are `role="menuitem"` (native `<button>` elements — keyboard support with no
48
- extra code). The header sits outside keyboard navigation. The badge value becomes part of the entry's accessible
57
+ The panel is `role="menu"`, entries are `role="menuitem"` (native `<button>` elements). The header sits outside keyboard navigation. The badge value becomes part of the entry's accessible
49
58
  name ("Notifications 4").
50
59
 
51
- The trigger gets `aria-haspopup="menu"` and a live `aria-expanded`. They are placed on the
60
+ The trigger gets `aria-haspopup="menu"`, a live `aria-expanded` and, while open, `aria-controls`
61
+ pointing at the panel. They are placed on the
52
62
  interactive control INSIDE the slot (the projected `<button>`/`<a>`), never on the
53
63
  `.kpt-menu__trigger` wrapper — giving that wrapper a role would create a nested control and a second
54
64
  tab stop, and state attributes on a roleless element violate `aria-allowed-attr`.
@@ -7,6 +7,7 @@ Import: `import { KptMenubar } from '@konce-pt/angular';`
7
7
 
8
8
  ## Inputs / outputs
9
9
  - `items`: KptMenuItem<T>[] — the top-level entries; submenus live in each entry's `items?`
10
+ - `ariaLabel`: string — the bar's name for screen readers, e.g. "Editor menu"
10
11
  - `selected`: KptMenuItem<T>
11
12
 
12
13
  ## Example
@@ -17,5 +18,25 @@ Import: `import { KptMenubar } from '@konce-pt/angular';`
17
18
  { label: 'Help' },
18
19
  ];
19
20
 
21
+ ## Links (`url`)
22
+ An entry with `url` and no children (and not disabled) renders as a real `<a href>` with the same
23
+ look, so the browser handles navigation — opening in a new tab, the status-bar preview. `selected`
24
+ still fires before the browser follows the link.
25
+
20
26
  ## Tokens
21
27
  The bar `--kpt-color-surface-raised`; the dropdown `--kpt-elevation-3`.
28
+
29
+ ## Keyboard
30
+ The bar follows the WAI-ARIA menubar pattern and is a single `Tab` stop (roving `tabindex`).
31
+ - On a top item: `ArrowLeft`/`ArrowRight` move along the bar (wrapping, skipping disabled items),
32
+ `Home`/`End` jump to the ends; `ArrowDown`, `Enter` or `Space` open the submenu with focus on its
33
+ first entry, `ArrowUp` on its last one.
34
+ - In a submenu: `ArrowDown`/`ArrowUp`/`Home`/`End` move between entries, `ArrowLeft`/`ArrowRight`
35
+ open the neighbouring submenu, `Escape` and `Tab` close it and return focus to the top item.
36
+ - While a submenu is open, moving along the bar opens the neighbour's submenu as well.
37
+
38
+ ## ARIA
39
+ The bar is `role="menubar"` with `aria-orientation="horizontal"`, named by `ariaLabel`. Top items
40
+ with a submenu carry `aria-haspopup="menu"`, `aria-expanded` and (while open) `aria-controls`; each
41
+ submenu is `role="menu"` named after its top item. The bar is a plain `<div>`, not `<nav>` — wrap
42
+ it in your own `<nav aria-label="…">` when it is the site navigation.
@@ -7,9 +7,19 @@ Import: `import { KptMeterGroup } from '@konce-pt/angular';`
7
7
  - `values`: KptMeterItem[] — { label, value, color? }
8
8
  - `max`: number | undefined — the reference total (by default the sum of the values = 100%)
9
9
  - `showLegend`: boolean (default true)
10
+ - `label`: string — the meter's name; empty = `meterGroup.label`
11
+ - `legendValue`: 'percent' | 'value' (default 'percent') — what the legend shows next to each label
12
+ - `unit`: string — appended to the values in the legend and in `aria-valuetext` (e.g. 'GB')
10
13
 
11
14
  ## Example
12
15
  <kpt-meter-group [values]="[{label:'Used', value:70}, {label:'Free', value:30}]" />
13
16
 
14
17
  ## Tokens
15
18
  Segments: the `--kpt-color-{primary,info,success,warning,danger}` palette or a custom `color`.
19
+
20
+ ## Accessibility
21
+ The track is `role="meter"` named by `label` (or `meterGroup.label`), with `aria-valuemin="0"`,
22
+ `aria-valuenow` set to the sum and `aria-valuemax` set to the real total — `max`, or the sum without
23
+ it. `aria-valuetext` spells out the split (“Photos 42 GB, Videos 28 GB, …”), so the segments reach
24
+ the screen reader even with the legend hidden. Keep the legend visible anyway: it is the text
25
+ alternative to the colours.
@@ -23,6 +23,15 @@ end. Either way the selection follows the item, so the next button press moves t
23
23
  ## Tokens
24
24
  Selection `--kpt-color-primary`; buttons `--kpt-color-surface-raised`.
25
25
 
26
+ ## Keyboard
27
+ The list is a `listbox` with a single Tab stop (roving tabindex), and the selection follows focus:
28
+ - `↓` / `↑` select the next / previous item (wrapping), `Home` / `End` the first / last;
29
+ - `Alt`+`↑` / `Alt`+`↓` move the selected item one place up / down — focus travels with it, so
30
+ repeated presses keep moving the same entry.
31
+ The up / down buttons stay and act on the same selection.
32
+
26
33
  ## Accessibility
27
- Native HTML5 drag and drop has no keyboard equivalent, so the up/down buttons stay the full keyboard
28
- path — `dragdrop` adds to them, it never replaces them.
34
+ The list is named after `header` (`aria-label`) and the selected item carries `aria-selected`.
35
+ Both buttons are labelled from the dictionary and go disabled while nothing is selected. Native
36
+ HTML5 drag and drop has no keyboard equivalent — `Alt`+arrows and the buttons cover it, and
37
+ `dragdrop` adds to them, it never replaces them.
@@ -18,3 +18,10 @@ A panel with a header and optional collapsing. Import: `import { KptPanel } from
18
18
 
19
19
  ## Tokens
20
20
  The header `--kpt-color-surface-variant`; the border `--kpt-color-border`.
21
+
22
+ ## Accessibility
23
+ The toggle is a native button with `aria-expanded` and `aria-controls` pointing at the body. Its
24
+ name says which panel it collapses — `panel.toggleNamed` (“Collapse / expand Settings”) when there
25
+ is a `header`, `panel.toggle` otherwise — so several panels on one page are told apart. A collapsed
26
+ body is `inert` and `aria-hidden`. The header title is not a heading — add your own when the panel
27
+ should appear in the document outline.
@@ -7,7 +7,7 @@ Import: `import { KptPassword } from '@konce-pt/angular';`
7
7
  - `value`: model<string> (or `formField`)
8
8
  - `feedback`: boolean — shows the strength meter (4 bars + a label)
9
9
  - `placeholder`: string
10
- - `id`: string — the id of the native field; ties the label to `kpt-form-field [controlId]`
10
+ - `id`: string|null — the id of the native field (the host never carries it); generated when absent
11
11
  - `autocomplete`: string | null = 'current-password' — in sign-up and reset flows set it to
12
12
  `new-password`, otherwise the password manager fills in the old password instead of proposing a new one
13
13
  - `disabled`/`invalid`/`touched`: boolean; `touch`: output
@@ -19,5 +19,13 @@ A 0–4 score: length ≥8, lower+upper case, a digit, a special character. Colo
19
19
  <kpt-password [formField]="f.password" feedback />
20
20
  <kpt-password [(value)]="pass" />
21
21
 
22
+ ## Label and description
23
+ `id` goes to the native field — the host never carries it; without one it is generated. Inside
24
+ `kpt-form-field` the control links itself: the wrapper's `label[for]` points at it and the hint or
25
+ error is added to `aria-describedby`, so `controlId` is not needed. Without a wrapper, name it with
26
+ `ariaLabel`. The host no longer carries the `id` (it used to, so `label[for]` pointed at
27
+ `kpt-password` and the same id existed twice). New: `ariaLabel`, `required`, `readonly`/`readOnly`,
28
+ `aria-invalid` after touch; with `feedback` the strength label is added to `aria-describedby`.
29
+
22
30
  ## Tokens
23
31
  The field `--kpt-form-field-*`; the meter bars `--kpt-color-{danger,warning,info,success}`.
@@ -28,6 +28,16 @@ leaves a panel also leaves that panel's selection.
28
28
  ## Tokens
29
29
  Selection `--kpt-color-primary`; headers `--kpt-color-surface-variant`.
30
30
 
31
+ ## Keyboard
32
+ Each list is a `listbox` with `aria-multiselectable` and a single Tab stop (roving tabindex):
33
+ - `↓` / `↑` move between items (wrapping), `Home` / `End` jump to the ends;
34
+ - `Space` toggles the focused item's selection (`aria-selected`), the same as a click;
35
+ - `Enter` moves the focused item to the other list, the same as a double-click — focus stays at
36
+ that position in the list, on the next item.
37
+ The move buttons act on the same selection, so the whole control works without a mouse.
38
+
31
39
  ## Accessibility
32
- Native HTML5 drag and drop has no keyboard equivalent, so the four move buttons stay the full
33
- keyboard path — `dragdrop` adds to them, it never replaces them.
40
+ Each list is named after its header (`aria-label`). The four move buttons are labelled from the
41
+ dictionary, so "move all right" is distinguishable from "move right" without seeing the double
42
+ chevron. Native HTML5 drag and drop has no keyboard equivalent — `Enter` and the buttons cover
43
+ everything it does, and `dragdrop` adds to them, it never replaces them.
@@ -4,6 +4,10 @@ A popover — arbitrary content in a bubble on CDK Overlay (richer than a toolti
4
4
  Requires `import '@angular/cdk/overlay-prebuilt.css';`
5
5
  Import: `import { KptPopover } from '@konce-pt/angular';`
6
6
 
7
+ ## Inputs / model
8
+ - `placement`: 'bottom' | 'top' (default 'bottom') — the preferred side; CDK flips it when there is no room
9
+ - `open`: model<boolean> (default false) — two-way, so the application can close the bubble from code
10
+
7
11
  ## Slots
8
12
  - the element carrying the `kptPopoverTrigger` attribute = the trigger; everything else = the panel
9
13
 
@@ -13,5 +17,12 @@ Import: `import { KptPopover } from '@konce-pt/angular';`
13
17
  <div>Rich popover content…</div>
14
18
  </kpt-popover>
15
19
 
20
+ ## Focus and keyboard
21
+ The control inside the trigger slot gets `aria-haspopup="dialog"`, a live `aria-expanded` and, while
22
+ open, `aria-controls` — on the real `<button>`, not on the wrapper. Opening moves focus to the first
23
+ interactive element of the bubble; `Escape` or a click outside closes it, and focus returns to the
24
+ trigger when it was inside the bubble. Focus is not trapped — the popover is supporting content, not
25
+ a modal.
26
+
16
27
  ## Tokens
17
28
  `--kpt-color-surface-raised`, `--kpt-elevation-3`.
@@ -11,5 +11,16 @@ Import: `import { KptRadioGroup, KptOption } from '@konce-pt/angular';`
11
11
  - `value`: model<T>; `options`: KptOption<T>[] `{ label, value, disabled? }`
12
12
  - `disabled`/`invalid`/`touched`: boolean; `name`: string (automatic); the `touch` output.
13
13
 
14
+ ## Screen reader and keyboard
15
+ `orientation`: 'vertical' (default) | 'horizontal' — options in a column or in a wrapping row; also
16
+ sets `aria-orientation`.
17
+
18
+ ## Label and description
19
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
20
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
21
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
22
+ `aria-describedby`. No `controlId` needed. New: `id`, `ariaLabel` and `required` (→ `aria-required`)
23
+ on the `radiogroup`.
24
+
14
25
  ## Tokens
15
26
  `--kpt-color-primary`, `--kpt-form-field-border`.
@@ -4,15 +4,29 @@ A star rating on Signal Forms. Import: `import { KptRating } from '@konce-pt/ang
4
4
 
5
5
  ## Inputs
6
6
  - `value`: model<number>; `count`: number (number of stars, default 5)
7
- - `readonly`/`disabled`/`touched`: boolean; `touch`: output
7
+ - `allowHalf`: boolean — half stars, the value goes in steps of 0.5
8
+ - `readonly`/`disabled`/`required`/`invalid`/`touched`: boolean; `touch`: output
9
+ - `id`/`ariaLabel`: string|null — `id` goes to the group, never the host
8
10
 
9
11
  ## Example
10
12
  <kpt-rating [formField]="f.score" [count]="5" />
11
- <kpt-rating [value]="4" readonly />
13
+ <kpt-rating [(value)]="score" allowHalf ariaLabel="Rating" />
14
+ <kpt-rating [value]="4.3" readonly />
12
15
 
13
16
  ## Accessibility
14
- `role="slider"` with `aria-valuenow`/`aria-valuemax`; each star is a real button labelled from
15
- `rating.star` (`{value}`, `{max}`) — EN `1 of 5`, PL `1 z 5`.
17
+ `role="radiogroup"` named by `ariaLabel` or the `kpt-form-field` label; each star is
18
+ `role="radio"` with `aria-checked`, labelled from `rating.star` (`{value}`, `{max}`) — EN `1 of 5`,
19
+ PL `1 z 5`. One Tab stop: `→`/`↑` raise the score by one, `←`/`↓` lower it (mirrored in RTL),
20
+ `Home`/`End` set the extremes, `Delete`/`Backspace` clear it; clicking the chosen star also clears it.
21
+ `readonly` renders `role="img"` named "4 of 5" with no buttons, so a screen reader does not announce
22
+ a row of disabled controls. `touch` fires when focus leaves the group.
23
+
24
+ ## Half stars
25
+ `allowHalf` splits every star into two halves, each its own `role="radio"` ("3.5 of 5" — the number
26
+ follows the page language, PL "3,5 z 5"), so the group keeps the radio pattern: clicking the left half
27
+ gives x.5, and the arrows step by 0.5. Read-only stars show the value rounded to a half (4.3 → four
28
+ and a half stars) while the name carries the exact score ("4.3 of 5"), so an average can be shown
29
+ as it is.
16
30
 
17
31
  ## Tokens
18
32
  Filled stars `--kpt-color-warning`; empty ones `--kpt-color-border-strong`.
@@ -51,6 +51,9 @@ they are inert, so they open no new door.
51
51
  - `link` (a URL prompt), `clear` (remove formatting), `html` (a toggle for the HTML source view — editing raw HTML)
52
52
 
53
53
  ## Keyboard
54
+ - The toolbar is a named `role="toolbar"` ("Formatting", `richText.toolbar`) with one Tab stop:
55
+ `←`/`→` (mirrored in RTL) and `Home`/`End` move between tools, skipping disabled ones; the stop
56
+ remembers the last tool, so Tab from the toolbar goes straight to the editor.
54
57
  - Tab / Shift+Tab inside a list item = indent / one level up (the same as `indent`/`outdent`).
55
58
  - Tab / Shift+Tab inside a table cell = the next / previous cell (with its content selected).
56
59
  A list inside a cell takes precedence — only once it can no longer be indented does Tab move to another cell.
@@ -77,3 +80,18 @@ potentially untrusted (a database, another user, a CMS); on the way out of the c
77
80
  The "paste"/"paste without formatting" clipboard tools use the async Clipboard API (which needs browser permission).
78
81
  An exception to execCommand: list indentation is done with our own DOM code — `execCommand('indent')` produces
79
82
  invalid `ul > ul` in Chrome (or a `blockquote` with a margin, which the sanitizer would strip anyway).
83
+
84
+ ## Screen reader and keyboard
85
+ `link` opens a small dialog instead of the browser's `prompt()`: a labelled URL field (prefilled
86
+ with the current link), `Enter` inserts, `Escape` returns to the text with the selection restored.
87
+ The table context menu is a `role="menu"` of `menuitem`s: it also opens from the keyboard (the menu
88
+ key or `Shift+F10` in a cell), the arrows and `Home`/`End` move, `Escape`/`Tab` close it, and focus
89
+ goes back to the cell. Colour swatches are named ("Dark grey", `richText.swatches.*`) instead of
90
+ their hex codes. The table size grid is still mouse-only.
91
+
92
+ ## Label and description
93
+ `id` goes to the focusable element — the host never carries it; without one it is generated. A `div`
94
+ with a role is not named by `label[for]`, so inside `kpt-form-field` it gets `aria-labelledby`
95
+ pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error is added to
96
+ `aria-describedby`. No `controlId` needed. New: `id`, `ariaLabel` and `required` (→ `aria-required`)
97
+ on the `role="textbox"` area.
@@ -11,5 +11,17 @@ Import: `import { KptScrollTop } from '@konce-pt/angular';`
11
11
  <kpt-scroll-top [threshold]="300" />
12
12
  <kpt-scroll-top target="#panel" />
13
13
 
14
+ ## The scrolled element
15
+ Without `target` the button watches the window. `target` is a selector, resolved after render, so
16
+ the element must already be in the page; changing it re-attaches the listener, and the listener is
17
+ removed when the component is destroyed.
18
+
14
19
  ## Tokens
15
20
  The button uses `--kpt-color-primary` / `--kpt-color-on-primary`, `--kpt-elevation-3`.
21
+
22
+ ## Accessibility
23
+ A real `<button>` labelled from `scrollTop.label`; while hidden it is `visibility: hidden`, so it is
24
+ out of the tab order and the accessibility tree. It sits in the bottom-end corner
25
+ (`inset-inline-end`), so it moves to the left in RTL. With `prefers-reduced-motion` the scroll is a
26
+ jump instead of smooth and the button appears without a transition. Focus stays on the button after
27
+ the jump — move it to your page heading if that matters.