forty-cdk 0.38.0 → 0.40.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 (155) hide show
  1. package/README.md +12 -11
  2. package/accordion/README.md +17 -12
  3. package/aspect-ratio/README.md +1 -1
  4. package/avatar/README.md +21 -0
  5. package/breadcrumbs/README.md +25 -20
  6. package/breakpoints/README.md +17 -25
  7. package/button/README.md +1 -1
  8. package/calendar/README.md +17 -5
  9. package/carousel/README.md +38 -31
  10. package/combobox/README.md +60 -23
  11. package/context-menu/README.md +25 -1
  12. package/date-field/README.md +14 -10
  13. package/date-picker/README.md +37 -9
  14. package/defaults/README.md +1 -1
  15. package/dialog/README.md +33 -1
  16. package/drag-drop/README.md +61 -22
  17. package/drawer/README.md +27 -1
  18. package/dropdown-menu/README.md +27 -1
  19. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  20. package/fesm2022/forty-cdk-carousel.mjs +2 -2
  21. package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-combobox.mjs +250 -191
  23. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
  25. package/fesm2022/forty-cdk-core-overlay.mjs +30 -9
  26. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  27. package/fesm2022/forty-cdk-core.mjs +13 -22
  28. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  29. package/fesm2022/forty-cdk-date-field.mjs +4 -9
  30. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  31. package/fesm2022/forty-cdk-date-picker.mjs +5 -5
  32. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  33. package/fesm2022/forty-cdk-defaults.mjs +4 -4
  34. package/fesm2022/forty-cdk-defaults.mjs.map +1 -1
  35. package/fesm2022/forty-cdk-dialog.mjs +8 -8
  36. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  37. package/fesm2022/forty-cdk-drag-drop.mjs +63 -60
  38. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  39. package/fesm2022/forty-cdk-drawer.mjs +51 -63
  40. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  41. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-field.mjs +2 -2
  43. package/fesm2022/forty-cdk-field.mjs.map +1 -1
  44. package/fesm2022/forty-cdk-fieldset.mjs +2 -2
  45. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
  46. package/fesm2022/forty-cdk-file-upload.mjs +6 -3
  47. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
  48. package/fesm2022/forty-cdk-id-salt.mjs +14 -0
  49. package/fesm2022/forty-cdk-id-salt.mjs.map +1 -0
  50. package/fesm2022/forty-cdk-internationalized-date.mjs +10 -4
  51. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  52. package/fesm2022/forty-cdk-listbox.mjs +199 -135
  53. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  54. package/fesm2022/forty-cdk-menu.mjs +32 -46
  55. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  56. package/fesm2022/forty-cdk-navigation-menu.mjs +27 -25
  57. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  58. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  59. package/fesm2022/forty-cdk-progress.mjs +4 -1
  60. package/fesm2022/forty-cdk-progress.mjs.map +1 -1
  61. package/fesm2022/forty-cdk-scroll-area.mjs +14 -3
  62. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
  63. package/fesm2022/forty-cdk-select.mjs +245 -207
  64. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  65. package/fesm2022/forty-cdk-separator.mjs.map +1 -1
  66. package/fesm2022/forty-cdk-shared.mjs +2 -1
  67. package/fesm2022/forty-cdk-slider.mjs +2 -2
  68. package/fesm2022/forty-cdk-slider.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  70. package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
  71. package/fesm2022/forty-cdk-table.mjs +9 -6
  72. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  73. package/fesm2022/forty-cdk-testing.mjs +35 -13
  74. package/fesm2022/forty-cdk-testing.mjs.map +1 -1
  75. package/fesm2022/forty-cdk-time-field.mjs +9 -11
  76. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  77. package/fesm2022/forty-cdk-time-picker.mjs +2 -2
  78. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  79. package/fesm2022/forty-cdk-toast.mjs +11 -7
  80. package/fesm2022/forty-cdk-toast.mjs.map +1 -1
  81. package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
  82. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
  83. package/fesm2022/forty-cdk-tree.mjs +280 -174
  84. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  85. package/fesm2022/forty-cdk-virtual-reorder.mjs +1 -1
  86. package/fesm2022/forty-cdk-virtual-reorder.mjs.map +1 -1
  87. package/fesm2022/forty-cdk-virtualization.mjs +2 -1
  88. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  89. package/file-upload/README.md +12 -12
  90. package/hover-card/README.md +7 -1
  91. package/id-salt/README.md +41 -0
  92. package/listbox/README.md +60 -41
  93. package/menu/README.md +73 -35
  94. package/menubar/README.md +26 -2
  95. package/navigation-menu/README.md +125 -14
  96. package/number-input/README.md +28 -1
  97. package/package.json +5 -1
  98. package/pagination/README.md +39 -16
  99. package/popover/README.md +8 -8
  100. package/progress/README.md +50 -10
  101. package/radio-group/README.md +21 -2
  102. package/scroll-area/README.md +35 -6
  103. package/search/README.md +23 -9
  104. package/select/README.md +31 -23
  105. package/separator/README.md +2 -2
  106. package/shared/README.md +6 -6
  107. package/slider/README.md +19 -0
  108. package/stepper/README.md +31 -15
  109. package/switch/README.md +19 -6
  110. package/table/README.md +42 -36
  111. package/table-virtualization/README.md +1 -1
  112. package/tabs/README.md +39 -15
  113. package/testing/README.md +1 -1
  114. package/time-field/README.md +13 -9
  115. package/time-picker/README.md +8 -3
  116. package/toast/README.md +6 -2
  117. package/toggle/README.md +88 -68
  118. package/toolbar/README.md +19 -0
  119. package/tree/README.md +70 -58
  120. package/types/forty-cdk-calendar.d.ts +8 -2
  121. package/types/forty-cdk-carousel.d.ts +2 -1
  122. package/types/forty-cdk-combobox.d.ts +184 -87
  123. package/types/forty-cdk-context-menu.d.ts +4 -13
  124. package/types/forty-cdk-core-overlay.d.ts +86 -65
  125. package/types/forty-cdk-core.d.ts +61 -117
  126. package/types/forty-cdk-date-field.d.ts +15 -18
  127. package/types/forty-cdk-defaults.d.ts +33 -33
  128. package/types/forty-cdk-drag-drop.d.ts +10 -6
  129. package/types/forty-cdk-drawer.d.ts +2 -1
  130. package/types/forty-cdk-dropdown-menu.d.ts +3 -11
  131. package/types/forty-cdk-file-upload.d.ts +10 -4
  132. package/types/forty-cdk-id-salt.d.ts +40 -0
  133. package/types/forty-cdk-internationalized-date.d.ts +10 -6
  134. package/types/forty-cdk-listbox.d.ts +177 -63
  135. package/types/forty-cdk-menu.d.ts +25 -38
  136. package/types/forty-cdk-navigation-menu.d.ts +6 -0
  137. package/types/forty-cdk-popover.d.ts +8 -2
  138. package/types/forty-cdk-progress.d.ts +9 -10
  139. package/types/forty-cdk-scroll-area.d.ts +49 -10
  140. package/types/forty-cdk-select.d.ts +220 -127
  141. package/types/forty-cdk-separator.d.ts +1 -1
  142. package/types/forty-cdk-shared.d.ts +2 -1
  143. package/types/forty-cdk-stepper.d.ts +14 -9
  144. package/types/forty-cdk-table.d.ts +2 -2
  145. package/types/forty-cdk-testing.d.ts +38 -5
  146. package/types/forty-cdk-time-field.d.ts +14 -20
  147. package/types/forty-cdk-time-picker.d.ts +15 -6
  148. package/types/forty-cdk-toast.d.ts +37 -18
  149. package/types/forty-cdk-toggle.d.ts +4 -3
  150. package/types/forty-cdk-toolbar.d.ts +6 -16
  151. package/types/forty-cdk-tree.d.ts +331 -69
  152. package/types/forty-cdk-virtual-reorder.d.ts +5 -1
  153. package/virtual-reorder/README.md +3 -1
  154. package/virtualization/README.md +3 -1
  155. package/visually-hidden/README.md +16 -3
package/README.md CHANGED
@@ -42,18 +42,19 @@ Two packages are regular dependencies, installed automatically and never declare
42
42
 
43
43
  ## Entry points
44
44
 
45
- **The package name itself exports nothing.** `import { … } from 'forty-cdk'` resolves to no symbol, and your editor will not auto-import anything under the bare package name — by design, so that the bare name never becomes a second import path for a symbol. There are six specifiers you do import from:
45
+ **The package name itself exports nothing.** `import { … } from 'forty-cdk'` resolves to no symbol, and your editor will not auto-import anything under the bare package name — by design, so that the bare name never becomes a second import path for a symbol. There are seven specifiers you do import from:
46
46
 
47
- | Specifier | What it exports |
48
- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
- | `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below. |
50
- | [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once. Eight ship from their own primitive instead; that README names them. |
51
- | `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional. |
52
- | [`forty-cdk/defaults`](defaults) | Every `provideFor<Primitive>Defaults` with its token and interface. Each primitive re-exports its own pair too, but a root provider imported from the primitive loads the primitive at startup; imported from here, it does not. |
53
- | [`forty-cdk/date-adapter`](date-adapter) | The `DateAdapter` contract, the `FOR_DATE_ADAPTER` token and `provideNativeDateAdapter`. `forty-cdk/shared` re-exports them, but a root provider imported from there loads the code every primitive shares at startup; imported from here, it does not. |
54
- | [`forty-cdk/testing`](testing) | Spec helpers: keyboard and mouse events dispatched the way a browser dispatches them, stubs for the browser APIs jsdom lacks, and dialog and drawer refs for content mounted outside its manager. |
47
+ | Specifier | What it exports |
48
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below. |
50
+ | [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once. Eight ship from their own primitive instead; that README names them. |
51
+ | `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional. |
52
+ | [`forty-cdk/defaults`](defaults) | Every `provideFor<Primitive>Defaults` with its token and interface. Each primitive re-exports its own pair too, but a root provider imported from the primitive loads the primitive at startup; imported from here, it does not. |
53
+ | [`forty-cdk/date-adapter`](date-adapter) | The `DateAdapter` contract, the `FOR_DATE_ADAPTER` token and `provideNativeDateAdapter`. `forty-cdk/shared` re-exports them, but a root provider imported from there loads the code every primitive shares at startup; imported from here, it does not. |
54
+ | [`forty-cdk/id-salt`](id-salt) | `FOR_ID_SALT` and `provideForIdSalt`, the per-app salt for the ids the primitives generate when several forty-cdk apps share a page. `forty-cdk/shared` re-exports them, but a root provider imported from there loads the code every primitive shares at startup; imported from here, it does not. |
55
+ | [`forty-cdk/testing`](testing) | Spec helpers: keyboard and mouse events dispatched the way a browser dispatches them, stubs for the browser APIs jsdom lacks, and dialog and drawer refs for content mounted outside its manager. |
55
56
 
56
- `forty-cdk/core` and `forty-cdk/core-overlay` resolve too, but neither is **public**: together they hold the engines and DI singletons the library refactors freely, and they exist so every primitive resolves that shared implementation to one compiled module. They are two rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the positioning engine (`@floating-ui/dom` and the overlay shells) in its own module means a lazy route that renders no overlay does not load it. Measured on a seven-lazy-route app, that is **41.7 kB raw / 12.1 kB transfer** a non-overlay route no longer pays. If a symbol you need is not exported by the six specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
57
+ `forty-cdk/core` and `forty-cdk/core-overlay` resolve too, but neither is **public**: together they hold the engines and DI singletons the library refactors freely, and they exist so every primitive resolves that shared implementation to one compiled module. They are two rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the positioning engine (`@floating-ui/dom` and the overlay shells) in its own module means a lazy route that renders no overlay does not load it. Measured on a seven-lazy-route app, that is **41.7 kB raw / 12.1 kB transfer** a non-overlay route no longer pays. If a symbol you need is not exported by the seven specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
57
58
 
58
59
  ## Errors
59
60
 
@@ -71,7 +72,7 @@ Fix: Move ForDialogTitle inside a [forDialog] element, declaring any ng-template
71
72
 
72
73
  The code is `FORCDK-<AREA>-<NUMBER>`, where the area is the entry point you imported from — so `FORCDK-DATE-PICKER-003` came from `forty-cdk/date-picker`, and `FORCDK-CORE-*` from machinery shared across primitives (those still print the prefix of the primitive you actually wrote). **A code is stable and always means the same failure**, so it is safe to search for, quote in an issue, or match on in your own error handling; a retired code is never reused for something else.
73
74
 
74
- Warnings are dev-mode only. Errors are not: a piece that resolved no context would fail one line later anyway, so it throws in production too and says why.
75
+ Warnings are dev-mode only, and so are the checks on a value you passed: an invalid column name, a misordered list of snap points or an unbound required input throws in development and is skipped in a production build, where the primitive degrades instead. Do not rely on catching one of those in production. A piece that resolved no context, or an overlay trigger that resolved no root, is different: it would fail one line later anyway, so it throws in every build and says why.
75
76
 
76
77
  ## Primitives
77
78
 
@@ -113,12 +113,13 @@ A disabled item cannot be toggled and is skipped by the arrow keys, while stayin
113
113
 
114
114
  | Property | Type | Description |
115
115
  | ------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116
- | `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** — |
116
+ | `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** `[]` |
117
117
  | `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously.<br>**Default:** `false` |
118
118
  | `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Otherwise once any item is open, exactly one stays open.<br>**Default:** `false` |
119
119
  | `disabled` | `input<boolean>` | When true, disables every item: each trigger reflects the native `disabled` attribute and cannot toggle. Composes with a per-item `[disabled]`.<br>**Default:** `false` |
120
120
  | `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction of the trigger list. In horizontal mode ArrowLeft/Right replace ArrowUp/Down.<br>**Default:** `'vertical'` |
121
121
  | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. Only relevant in horizontal mode, where it swaps the meaning of Left/Right arrows. `null` inherits the nearest `[dir]` / `<html dir>`, else `'ltr'`.<br>**Default:** `null` |
122
+ | `loop` | `input<boolean>` | When true, arrow navigation wraps from the last trigger to the first and back. Set `false` to stop at the ends.<br>**Default:** `true` |
122
123
 
123
124
  | Data attribute | Values |
124
125
  | ------------------ | -------------------------- |
@@ -127,10 +128,10 @@ A disabled item cannot be toggled and is skipped by the arrow keys, while stayin
127
128
 
128
129
  ### `ForAccordionItem`
129
130
 
130
- | Property | Type | Description |
131
- | ---------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
132
- | `value` | `input.required<string>` | Unique identifier within the accordion. Required.<br>**Default:** — |
133
- | `disabled` | `input<boolean>` | When true, the trigger ignores clicks and exposes the native `disabled` attribute.<br>**Default:** — |
131
+ | Property | Type | Description |
132
+ | ---------- | ------------------------ | ---------------------------------------------------------------------------------------------------------- |
133
+ | `value` | `input.required<string>` | Unique identifier within the accordion. Required.<br>**Default:** — |
134
+ | `disabled` | `input<boolean>` | When true, the trigger ignores clicks and exposes the native `disabled` attribute.<br>**Default:** `false` |
134
135
 
135
136
  | Data attribute | Values |
136
137
  | ------------------ | -------------------------- |
@@ -143,6 +144,7 @@ A disabled item cannot be toggled and is skipped by the arrow keys, while stayin
143
144
  | Data attribute | Values |
144
145
  | ------------------ | -------------------------- |
145
146
  | `data-state` | `open` \| `closed` |
147
+ | `data-disabled` | present \| absent |
146
148
  | `data-orientation` | `horizontal` \| `vertical` |
147
149
 
148
150
  ### `ForAccordionContent`
@@ -150,17 +152,20 @@ A disabled item cannot be toggled and is skipped by the arrow keys, while stayin
150
152
  | Data attribute | Values |
151
153
  | ------------------ | -------------------------- |
152
154
  | `data-state` | `open` \| `closed` |
155
+ | `data-disabled` | present \| absent |
153
156
  | `data-orientation` | `horizontal` \| `vertical` |
154
157
 
158
+ `data-disabled` on the item, trigger and content reflects the item's own `[disabled]` or the root's, whichever is set.
159
+
155
160
  ## Keyboard
156
161
 
157
- | Key | Action |
158
- | -------------------------------------------- | ------------------------------------------------------------------------------------------------- |
159
- | <kbd>Enter</kbd> / <kbd>Space</kbd> | Toggle the focused trigger (native button). |
160
- | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Move focus between triggers (vertical, default). Wrap-around, skips disabled. |
161
- | <kbd>ArrowLeft</kbd> / <kbd>ArrowRight</kbd> | Move focus between triggers (horizontal, flipped under `dir='rtl'`). Wrap-around, skips disabled. |
162
- | <kbd>Home</kbd> | Jump to the first trigger. |
163
- | <kbd>End</kbd> | Jump to the last trigger. |
162
+ | Key | Action |
163
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
164
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | Toggle the focused trigger (native button). |
165
+ | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Move focus between triggers (vertical, default). Wraps around unless `loop` is `false`, skips disabled. |
166
+ | <kbd>ArrowLeft</kbd> / <kbd>ArrowRight</kbd> | Move focus between triggers (horizontal, flipped under `dir='rtl'`). Wraps around unless `loop` is `false`, skips disabled. |
167
+ | <kbd>Home</kbd> | Jump to the first trigger. |
168
+ | <kbd>End</kbd> | Jump to the last trigger. |
164
169
 
165
170
  ## Accessibility
166
171
 
@@ -39,7 +39,7 @@ If your ratio is a literal constant, prefer the CSS property directly and keep t
39
39
 
40
40
  ## Examples
41
41
 
42
- Resize the preview and watch the frame hold its 16 / 9 ratio. The primitive writes the ratio and nothing else, so every border, colour and inset below is your own CSS.
42
+ Narrow the browser window until the frame shrinks and watch it hold its 16 / 9 ratio. The primitive writes the ratio and nothing else, so every border, colour and inset below is your own CSS.
43
43
 
44
44
  ```ts
45
45
  import { ChangeDetectionStrategy, Component } from '@angular/core';
package/avatar/README.md CHANGED
@@ -96,10 +96,31 @@ When the image errors, the directive flips `shouldShowFallback()` and the initia
96
96
  | -------------- | ------------------------------------------ |
97
97
  | `data-status` | `idle` \| `loading` \| `loaded` \| `error` |
98
98
 
99
+ ## Scoped defaults
100
+
101
+ `provideForAvatarDefaults` configures avatar defaults for an injector subtree, either at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root).
102
+
103
+ | Key | Library fallback | Meaning |
104
+ | ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
105
+ | `fallbackDelayMs` | `0` | Milliseconds to wait before the fallback shows while the image is idle or loading, for avatars that don't set `fallbackDelayMs` themselves. An `error` status always shows the fallback at once. |
106
+
107
+ Per-instance inputs always win over the scope defaults.
108
+
109
+ ```ts
110
+ import { type ApplicationConfig } from '@angular/core';
111
+ import { provideForAvatarDefaults } from 'forty-cdk/defaults';
112
+
113
+ export const appConfig: ApplicationConfig = {
114
+ providers: [provideForAvatarDefaults({ fallbackDelayMs: 500 })],
115
+ };
116
+ ```
117
+
99
118
  ## Accessibility
100
119
 
101
120
  The directive does not impose a `role`. Pair the avatar with visible name text or `aria-label` on the surrounding element when identity matters. Set `alt=""` on the `<img>` for purely decorative avatars next to a name, or provide a meaningful `alt` description if the avatar stands alone.
102
121
 
122
+ A meaningful `alt` names the avatar only while the image is shown: once it fails, the image is hidden and the fallback initials are all that is left to announce. When an avatar stands alone, name the root instead (`role="img"` with an `aria-label`) and give the `<img>` `alt=""`, so the fallback carries the same name as the loaded image.
123
+
103
124
  ## Styling
104
125
 
105
126
  forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [API](#api), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
@@ -23,7 +23,7 @@ A labelled navigation landmark for a breadcrumb trail: links with aria-current='
23
23
 
24
24
  ## Examples
25
25
 
26
- Walk the trail with `Tab`: the last crumb is the page you are on, so it carries `aria-current="page"` and is not a link back to itself.
26
+ Walk the trail with `Tab`: the last crumb is the page you are on, so it carries `aria-current="page"` and is announced as the current page.
27
27
 
28
28
  ```ts
29
29
  import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
@@ -86,22 +86,6 @@ export class BreadcrumbsDefaultExample {
86
86
 
87
87
  The primitive renders whatever items you give it, so collapsing a deep path is a consumer decision. Here the middle is folded into an expandable ellipsis button that reveals the hidden crumbs. The trail stays a single accessible navigation landmark either way.
88
88
 
89
- ## Localizing the label
90
-
91
- `Breadcrumb` is verbalized by screen readers, so translate it per injector scope with `provideForBreadcrumbsDefaults`. Configure it at the application root, or in any component's `providers` to scope the translation to a subtree. A per-instance `[ariaLabel]` still wins over the scope default.
92
-
93
- <!-- snippet: fragment -->
94
-
95
- ```ts
96
- import { provideForBreadcrumbsDefaults } from 'forty-cdk/defaults';
97
-
98
- bootstrapApplication(App, {
99
- providers: [provideForBreadcrumbsDefaults({ label: 'Ruta de navegación' })],
100
- });
101
- ```
102
-
103
- For a language the app sets or switches after bootstrap, pass `label` as a function and the overrides as a factory, as [Localizing default text](../shared/README.md#localizing-default-text) shows.
104
-
105
89
  ## API
106
90
 
107
91
  ### `ForBreadcrumbs`
@@ -112,9 +96,30 @@ For a language the app sets or switches after bootstrap, pass `label` as a funct
112
96
 
113
97
  ### `ForBreadcrumbItem`
114
98
 
115
- | Property | Type | Description |
116
- | --------- | ---------------- | ------------------------------------------------------------------------ |
117
- | `current` | `input<boolean>` | When true, reflects `aria-current="page"` on the link.<br>**Default:** — |
99
+ | Property | Type | Description |
100
+ | --------- | ---------------- | ------------------------------------------------------------------------------ |
101
+ | `current` | `input<boolean>` | When true, reflects `aria-current="page"` on the link.<br>**Default:** `false` |
102
+
103
+ ## Scoped defaults
104
+
105
+ `provideForBreadcrumbsDefaults` configures breadcrumb defaults for an injector subtree, either at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root).
106
+
107
+ | Key | Library fallback | Meaning |
108
+ | ------- | ---------------- | ------------------------------------------------------------------------------------------------- |
109
+ | `label` | `'Breadcrumb'` | Accessible name of the `navigation` landmark, for trails that don't set `[ariaLabel]` themselves. |
110
+
111
+ `label` is verbalized by screen readers, so translate it here to rename every breadcrumb landmark in the scope. It accepts a string or a function returning one; a function can read a language signal, so the name follows a language the app sets or switches after bootstrap, as [Localizing default text](../shared/README.md#localizing-default-text) shows.
112
+
113
+ Per-instance inputs always win over the scope defaults.
114
+
115
+ ```ts
116
+ import { type ApplicationConfig } from '@angular/core';
117
+ import { provideForBreadcrumbsDefaults } from 'forty-cdk/defaults';
118
+
119
+ export const appConfig: ApplicationConfig = {
120
+ providers: [provideForBreadcrumbsDefaults({ label: 'Ruta de navegación' })],
121
+ };
122
+ ```
118
123
 
119
124
  ## Accessibility
120
125
 
@@ -10,9 +10,15 @@ A signal-first, zoneless, SSR-safe viewport breakpoint observer (injectBreakpoin
10
10
 
11
11
  It is a headless reactive utility, not a UI primitive: no DOM, no ARIA, no template. Configure the breakpoint map **once** via a provider and read it anywhere with `injectBreakpoints()`, so there is no need to repeat the breakpoint set at every call site.
12
12
 
13
- ## Setup
13
+ ## Scoped defaults
14
14
 
15
- Configuring is optional: without a provider the Tailwind scale (`sm` 640, `md` 768, `lg` 1024, `xl` 1280, `2xl` 1536) is used. To define your own:
15
+ `provideForBreakpointsDefaults` sets the scope's `breakpoints` map, so `injectBreakpoints()` can be called with no arguments. Configuring is optional: without a provider the Tailwind scale is used. Provide it in your application config, or again in any component's `providers` array to replace the map for that subtree only. The nearest scope wins, and its map replaces the parent's wholesale: it is never merged key by key.
16
+
17
+ | Key | Library fallback | Meaning |
18
+ | ------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `breakpoints` | `forBreakpointsTailwind`: `sm` 640, `md` 768, `lg` 1024, `xl` 1280, `2xl` 1536 | Each breakpoint name paired with its `min-width` threshold in CSS pixels. The query methods derive from these names, sorted ascending by threshold. |
20
+
21
+ The provider takes the map itself rather than an object with a `breakpoints` key, or a function returning the map when it has to be built where `inject()` is available:
16
22
 
17
23
  ```ts
18
24
  import { ApplicationConfig } from '@angular/core';
@@ -25,21 +31,12 @@ export const appConfig: ApplicationConfig = {
25
31
  };
26
32
  ```
27
33
 
28
- Providing it again on a component injector replaces the map for that subtree only (nearest scope wins; the map is replaced wholesale, never merged key-by-key).
29
-
30
34
  ## Examples
31
35
 
32
- Resize the preview and watch the matched breakpoint change: `injectBreakpoints()` hands back signals, so the template re-renders without a listener of your own.
36
+ Resize the browser window and watch the matched breakpoint change: the breakpoints follow the viewport, not the preview frame. `injectBreakpoints()` hands back signals, so the template, width range included, re-renders without a listener of your own.
33
37
 
34
38
  ```ts
35
- import {
36
- afterNextRender,
37
- ChangeDetectionStrategy,
38
- Component,
39
- DestroyRef,
40
- inject,
41
- signal,
42
- } from '@angular/core';
39
+ import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
43
40
  import { forBreakpointsTailwind, injectBreakpoints } from 'forty-cdk/breakpoints';
44
41
 
45
42
  type TailwindName = keyof typeof forBreakpointsTailwind;
@@ -52,7 +49,7 @@ type TailwindName = keyof typeof forBreakpointsTailwind;
52
49
  <div class="bp-readout">
53
50
  <span class="bp-readout-label">active breakpoint</span>
54
51
  <span class="bp-active">{{ active() ?? 'below sm' }}</span>
55
- <span class="bp-width">viewport ≈ {{ width() }}px</span>
52
+ <span class="bp-width">viewport {{ range() }}</span>
56
53
  </div>
57
54
 
58
55
  <ul class="bp-grid">
@@ -81,17 +78,12 @@ export class BreakpointsActiveExample {
81
78
  only: this.bp.only(name),
82
79
  }));
83
80
 
84
- protected readonly width = signal(0);
85
-
86
- constructor() {
87
- const destroyRef = inject(DestroyRef);
88
- afterNextRender(() => {
89
- const onResize = (): void => this.width.set(globalThis.innerWidth);
90
- onResize();
91
- globalThis.addEventListener('resize', onResize, { passive: true });
92
- destroyRef.onDestroy(() => globalThis.removeEventListener('resize', onResize));
93
- });
94
- }
81
+ protected readonly range = computed(() => {
82
+ const index = this.rows.findIndex((row) => row.name === this.active());
83
+ const from = this.rows[index]?.min ?? 0;
84
+ const to = this.rows[index + 1]?.min;
85
+ return to === undefined ? `${from}px and up` : `${from}px to ${to - 1}px`;
86
+ });
95
87
  }
96
88
  ```
97
89
 
package/button/README.md CHANGED
@@ -5,7 +5,7 @@ archetype: [composable-ui]
5
5
  apgUrl: https://www.w3.org/WAI/ARIA/apg/patterns/button/
6
6
  ---
7
7
 
8
- # ForButton
8
+ # Button
9
9
 
10
10
  Turns any element (a native `<button>` or a custom host like `<div>` / `<span>`) into an accessible button with keyboard activation. Disabled stays focusable (aria-disabled, never the native attribute) and pressed / hovered / focus-visible are reflected as data-\* hooks.
11
11
 
@@ -188,7 +188,7 @@ export class CalendarDefaultExample {
188
188
 
189
189
  ### States
190
190
 
191
- One class and one directive, three states. `disabled` turns off focus movement and selection for the whole calendar; `readonly` keeps days focusable and the grid still pages, but clicking or pressing `Enter` no longer changes the selection, and a click moves only the focused day to the clicked cell. Each reflects a root hook (`data-disabled` and `data-readonly`), and the example's stylesheet keys on nothing else.
191
+ One class and one directive, three states. `disabled` turns off focus movement and selection for the whole calendar; `readonly` keeps days focusable and the grid still pages, but clicking or pressing `Enter` no longer changes the selection, and a click moves only the focused day to the clicked cell. Each reflects a root hook (`data-disabled` and `data-readonly`) that the example's stylesheet keys on. A disabled calendar also disables its prev / next buttons, which reflect `data-disabled` of their own rather than the native attribute.
192
192
 
193
193
  ### Constraints & week start
194
194
 
@@ -341,12 +341,18 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
341
341
  />
342
342
  </svg>
343
343
  </button>
344
- <select forCalendarMonthSelect #m="forCalendarMonthSelect">
344
+ <select forCalendarMonthSelect #m="forCalendarMonthSelect" aria-label="Month">
345
345
  @for (opt of m.options(); track opt.value) {
346
346
  <option [value]="opt.value" [disabled]="opt.disabled">{{ opt.label }}</option>
347
347
  }
348
348
  </select>
349
- <select forCalendarYearSelect #y="forCalendarYearSelect" [minYear]="1900" [maxYear]="2100">
349
+ <select
350
+ forCalendarYearSelect
351
+ #y="forCalendarYearSelect"
352
+ aria-label="Year"
353
+ [minYear]="1900"
354
+ [maxYear]="2100"
355
+ >
350
356
  @for (opt of y.years(); track opt.value) {
351
357
  <option [value]="opt.value" [disabled]="opt.disabled">{{ opt.value }}</option>
352
358
  }
@@ -389,6 +395,8 @@ The default window is **anchored to the current year** (not the visible year), s
389
395
 
390
396
  **Keep `[forCalendarHeading]` in the DOM.** The grid's `aria-labelledby` points at the heading's id. When dropdowns replace the visible heading, keep a visually-hidden `[forCalendarHeading]` so the grid stays named. Removing the heading entirely leaves `aria-labelledby` pointing at a missing element.
391
397
 
398
+ **Name each `<select>` yourself.** Neither directive names the element it sits on, and the heading names the grid, not the dropdowns. Without a name a screen reader announces only the value each one holds, so give each an `aria-label` as the snippet above does, or a `<label for>`.
399
+
392
400
  The lower-level hooks (`visibleMonthNumber()`, `visibleYear()`, `monthOptions()`, `goToMonth()`, `goToYear()`, `isYearDisabled()`) in the table above remain available for any other UI.
393
401
 
394
402
  ## View switching
@@ -536,6 +544,8 @@ Auto-disabled when the entire previous / next page would be outside `[min, max]`
536
544
 
537
545
  ## Scoped defaults
538
546
 
547
+ `provideForCalendarDefaults` configures calendar defaults for an injector subtree, either at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root).
548
+
539
549
  ```ts
540
550
  import { provideForCalendarDefaults } from 'forty-cdk/defaults';
541
551
 
@@ -548,6 +558,8 @@ providers: [
548
558
  ];
549
559
  ```
550
560
 
561
+ `firstDayOfWeek` sets the first column's weekday as a **0-6** index (`0` = Sunday, `1` = Monday) for calendars that don't bind `[firstDayOfWeek]`. Its fallback `null` takes the week start from the date adapter's `getFirstDayOfWeek()`, and a calendar's own `[firstDayOfWeek]` input always wins over the scope default.
562
+
551
563
  `outsideMonthLabel` builds the `aria-label` of a padding day outside the visible month from its formatted full date (default `"<date> (outside month)"`), so assistive tech can tell it apart from the month on screen. A calendar bound to its own `[dateLabel]` formatter ignores it.
552
564
 
553
565
  ## Keyboard
@@ -575,7 +587,7 @@ Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/
575
587
  - **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
576
588
  - **`aria-multiselectable="true"`** is set on the day grid in `selectionMode="range"`, where a committed range selects several cells, and is absent in single mode.
577
589
  - **Roving tabindex**: exactly one cell (the focused date) is tabbable. `Tab` enters and leaves the grid in one stop.
578
- - **Boolean `data-*`** on the cell, present when true and absent when false: `data-selected`, `data-today`, `data-highlighted` (the focused/roving cell), `data-disabled` and `data-outside-month`.
590
+ - **Boolean `data-*`** on the cell, present when true and absent when false: `data-selected`, `data-today`, `data-highlighted` (the roving tab-stop cell, kept after focus leaves the grid), `data-disabled` and `data-outside-month`.
579
591
 
580
592
  ## Styling
581
593
 
@@ -592,7 +604,7 @@ forty-cdk ships no styles: put your own class on each piece and key your CSS off
592
604
  .calendar-cell[data-today] {
593
605
  outline: 1px solid var(--accent);
594
606
  }
595
- .calendar-cell[data-outside-month] {
607
+ .calendar-cell[data-outside-month]:not([data-selected]) {
596
608
  opacity: 0.4;
597
609
  }
598
610
  .calendar-cell[data-disabled] {
@@ -5,7 +5,7 @@ archetype: [composable-ui]
5
5
  apgUrl: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/
6
6
  ---
7
7
 
8
- # ForCarousel
8
+ # Carousel
9
9
 
10
10
  A slideshow of content panels with previous / next controls, an indicator group, optional looping and multi-slide views, and an accessible autoplay mode with a pause control.
11
11
 
@@ -267,7 +267,7 @@ the defaults with `startLabel` / `stopLabel` inputs:
267
267
  ```
268
268
 
269
269
  Both defaults come from the scope's `rotationStartLabel` / `rotationStopLabel`
270
- (see [Localizing the default labels](#localizing-the-default-labels)); set either
270
+ (see [Scoped defaults](#scoped-defaults)); set either
271
271
  input to `null` when the button carries a visible text label and you don't want
272
272
  an `aria-label` overriding it.
273
273
 
@@ -357,40 +357,47 @@ horizontal carousels (allows vertical page scroll) and `pan-x` for vertical
357
357
  carousels (allows horizontal page scroll). A mostly-cross-axis swipe is never
358
358
  captured, so page scrolling on the perpendicular axis is unaffected.
359
359
 
360
- ## Localizing the default labels
360
+ ## Scoped defaults
361
361
 
362
- Each slide's default `aria-label` is the positional `"N of M"` string, each
363
- indicator's is `"Go to slide N"`, and the rotation control's swaps between
364
- `"Start automatic slide show"` and `"Stop automatic slide show"`. The root and
365
- each slide also carry an `aria-roledescription` (`"carousel"` / `"slide"`), which
366
- a screen reader speaks in place of the `group` role. Localize them all centrally
367
- with `provideForCarouselDefaults` instead of setting `ariaLabel` on every slide
368
- and indicator:
362
+ `provideForCarouselDefaults` configures carousel defaults for an injector subtree, either at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root), so you can localize just the labels and inherit the rest.
369
363
 
370
- <!-- snippet: fragment -->
364
+ | Key | Library fallback | Meaning |
365
+ | ---------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
366
+ | `loop` | `false` | Whether `next` past the last slide wraps to the first and `prev` before the first wraps to the last, for carousels that don't set `loop` themselves. |
367
+ | `align` | `'start'` | Alignment of the active slide within the viewport, for carousels that don't set `align` themselves. |
368
+ | `slidesPerView` | `1` | Number of slides visible at once, for carousels that don't set `slidesPerView` themselves. |
369
+ | `containScroll` | `false` | Whether the track offset is clamped so trailing slides sit flush at the viewport edge, for carousels that don't set `containScroll` themselves. See [Contain scroll](#contain-scroll). |
370
+ | `autoplay` | `false` | Whether the carousel rotates automatically on mount (never under reduced motion), for carousels that don't set `autoplay` themselves. |
371
+ | `autoplayInterval` | `5000` | Milliseconds between automatic slide advances, for carousels that don't set `autoplayInterval` themselves. |
372
+ | `slideLabel` | `"N of M"`, e.g. `"2 of 5"` | Builds each slide's default `aria-label` from `position` (1-based) and `total` (the slide count) and returns the string. |
373
+ | `indicatorLabel` | `"Go to slide N"` | Builds each indicator's default `aria-label` from `position` (1-based) and returns the string. |
374
+ | `rotationStartLabel` | `'Start automatic slide show'` | Accessible name of `[forCarouselRotationControl]` while rotation is stopped, for controls that don't set `[startLabel]` themselves. |
375
+ | `rotationStopLabel` | `'Stop automatic slide show'` | Accessible name of `[forCarouselRotationControl]` while rotation is playing, for controls that don't set `[stopLabel]` themselves. |
376
+ | `roleDescription` | `'carousel'` | `aria-roledescription` of the carousel root, spoken in place of its `group` role. An empty string drops the attribute. |
377
+ | `slideRoleDescription` | `'slide'` | `aria-roledescription` of each `[forCarouselSlide]`, spoken in place of its `group` role. An empty string drops the attribute. |
378
+
379
+ Per-instance inputs always win over the scope defaults: an `ariaLabel` on `[forCarouselSlide]` / `[forCarouselIndicator]` overrides the built label, as do `[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`.
380
+
381
+ `rotationStartLabel`, `rotationStopLabel`, `roleDescription` and `slideRoleDescription` each accept a string or a function returning one; a function can read a language signal, so the text follows a language the app sets or switches after bootstrap, as [Localizing default text](../shared/README.md#localizing-default-text) shows. Localize them all centrally instead of setting `ariaLabel` on every slide and indicator:
371
382
 
372
383
  ```ts
373
- providers: [
374
- provideForCarouselDefaults({
375
- slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
376
- indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
377
- rotationStartLabel: 'Iniciar la presentación',
378
- rotationStopLabel: 'Detener la presentación',
379
- roleDescription: 'carrusel',
380
- slideRoleDescription: 'diapositiva',
381
- }),
382
- ];
384
+ import { type ApplicationConfig } from '@angular/core';
385
+ import { provideForCarouselDefaults } from 'forty-cdk/defaults';
386
+
387
+ export const appConfig: ApplicationConfig = {
388
+ providers: [
389
+ provideForCarouselDefaults({
390
+ slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
391
+ indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
392
+ rotationStartLabel: 'Iniciar la presentación',
393
+ rotationStopLabel: 'Detener la presentación',
394
+ roleDescription: 'carrusel',
395
+ slideRoleDescription: 'diapositiva',
396
+ }),
397
+ ],
398
+ };
383
399
  ```
384
400
 
385
- `position` is the 1-based slide index and `total` is the slide count. Overrides
386
- merge with the parent scope, so you can localize just the labels and inherit the
387
- rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
388
- `[forCarouselIndicator]` still takes precedence over the localized default, as do
389
- `[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`. For a language
390
- the app sets or switches after bootstrap, pass the text keys as functions and the
391
- overrides as a factory, as
392
- [Localizing default text](../shared/README.md#localizing-default-text) shows.
393
-
394
401
  ## Indicators map 1:1 to slides
395
402
 
396
403
  The picker assumes **one `[forCarouselIndicator]` per `[forCarouselSlide]`**: the
@@ -487,7 +494,7 @@ Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patte
487
494
  `aria-label="N of M"` by default. Override per slide with the `ariaLabel` input,
488
495
  or localize the default format and both role descriptions app-wide with
489
496
  `provideForCarouselDefaults` (see
490
- [Localizing the default labels](#localizing-the-default-labels)).
497
+ [Scoped defaults](#scoped-defaults)).
491
498
  - Slides outside the viewport receive `aria-hidden="true"` and `inert` to remove them from
492
499
  the accessibility tree and focus order. The visible set follows the laid-out offset, so
493
500
  `align` and `containScroll` decide which slides stay reachable.