forty-cdk 0.39.0 → 0.41.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 (160) 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 +19 -0
  5. package/breadcrumbs/README.md +26 -21
  6. package/breakpoints/README.md +29 -27
  7. package/calendar/README.md +10 -4
  8. package/carousel/README.md +37 -30
  9. package/combobox/README.md +37 -2
  10. package/context-menu/README.md +25 -1
  11. package/date-field/README.md +7 -3
  12. package/date-picker/README.md +30 -1
  13. package/defaults/README.md +1 -1
  14. package/dialog/README.md +55 -10
  15. package/drag-drop/README.md +61 -22
  16. package/drawer/README.md +45 -10
  17. package/dropdown-menu/README.md +24 -0
  18. package/fesm2022/forty-cdk-calendar.mjs +14 -4
  19. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  20. package/fesm2022/forty-cdk-combobox.mjs +67 -90
  21. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-context-menu.mjs +42 -38
  23. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-core-anchored.mjs +2076 -0
  25. package/fesm2022/forty-cdk-core-anchored.mjs.map +1 -0
  26. package/fesm2022/forty-cdk-core-overlay.mjs +146 -2199
  27. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  28. package/fesm2022/forty-cdk-core.mjs +62 -39
  29. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  30. package/fesm2022/forty-cdk-date-field.mjs +4 -9
  31. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  32. package/fesm2022/forty-cdk-date-picker.mjs +80 -57
  33. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  34. package/fesm2022/forty-cdk-defaults.mjs +4 -4
  35. package/fesm2022/forty-cdk-defaults.mjs.map +1 -1
  36. package/fesm2022/forty-cdk-dialog.mjs +10 -9
  37. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  38. package/fesm2022/forty-cdk-drag-drop.mjs +53 -46
  39. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  40. package/fesm2022/forty-cdk-drawer.mjs +48 -59
  41. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-dropdown-menu.mjs +48 -33
  43. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
  44. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
  45. package/fesm2022/forty-cdk-hover-card.mjs +52 -35
  46. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
  47. package/fesm2022/forty-cdk-id-salt.mjs +14 -0
  48. package/fesm2022/forty-cdk-id-salt.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-input.mjs +19 -13
  50. package/fesm2022/forty-cdk-input.mjs.map +1 -1
  51. package/fesm2022/forty-cdk-internationalized-date.mjs +10 -4
  52. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  53. package/fesm2022/forty-cdk-listbox.mjs +56 -43
  54. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  55. package/fesm2022/forty-cdk-menu.mjs +64 -52
  56. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  57. package/fesm2022/forty-cdk-menubar.mjs +13 -10
  58. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  59. package/fesm2022/forty-cdk-navigation-menu.mjs +8 -1
  60. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  61. package/fesm2022/forty-cdk-popover.mjs +74 -34
  62. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  63. package/fesm2022/forty-cdk-progress.mjs +4 -1
  64. package/fesm2022/forty-cdk-progress.mjs.map +1 -1
  65. package/fesm2022/forty-cdk-scroll-area.mjs +14 -3
  66. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
  67. package/fesm2022/forty-cdk-select.mjs +68 -78
  68. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-separator.mjs.map +1 -1
  70. package/fesm2022/forty-cdk-shared.mjs +3 -2
  71. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  72. package/fesm2022/forty-cdk-table-virtualization.mjs +8 -8
  73. package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
  74. package/fesm2022/forty-cdk-table.mjs +4 -1
  75. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  76. package/fesm2022/forty-cdk-testing.mjs +35 -13
  77. package/fesm2022/forty-cdk-testing.mjs.map +1 -1
  78. package/fesm2022/forty-cdk-time-field.mjs +9 -11
  79. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  80. package/fesm2022/forty-cdk-time-picker.mjs +53 -23
  81. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  82. package/fesm2022/forty-cdk-toast.mjs +8 -4
  83. package/fesm2022/forty-cdk-toast.mjs.map +1 -1
  84. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
  85. package/fesm2022/forty-cdk-tooltip.mjs +61 -38
  86. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
  87. package/fesm2022/forty-cdk-tree.mjs +106 -63
  88. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  89. package/fesm2022/forty-cdk-virtual-reorder.mjs +1 -1
  90. package/fesm2022/forty-cdk-virtual-reorder.mjs.map +1 -1
  91. package/fesm2022/forty-cdk-virtualization.mjs +14 -12
  92. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  93. package/file-upload/README.md +12 -12
  94. package/hover-card/README.md +7 -2
  95. package/id-salt/README.md +41 -0
  96. package/listbox/README.md +37 -24
  97. package/menu/README.md +73 -35
  98. package/menubar/README.md +25 -1
  99. package/navigation-menu/README.md +123 -12
  100. package/number-input/README.md +19 -0
  101. package/package.json +9 -1
  102. package/pagination/README.md +39 -16
  103. package/popover/README.md +8 -8
  104. package/progress/README.md +41 -9
  105. package/radio-group/README.md +19 -0
  106. package/scroll-area/README.md +35 -6
  107. package/search/README.md +23 -9
  108. package/select/README.md +13 -11
  109. package/separator/README.md +2 -2
  110. package/shared/README.md +7 -7
  111. package/slider/README.md +19 -0
  112. package/stepper/README.md +30 -14
  113. package/table/README.md +8 -8
  114. package/table-virtualization/README.md +1 -1
  115. package/tabs/README.md +39 -15
  116. package/testing/README.md +1 -1
  117. package/time-field/README.md +7 -3
  118. package/toast/README.md +6 -2
  119. package/toggle/README.md +87 -67
  120. package/toolbar/README.md +19 -0
  121. package/tree/README.md +45 -44
  122. package/types/forty-cdk-calendar.d.ts +34 -11
  123. package/types/forty-cdk-combobox.d.ts +20 -37
  124. package/types/forty-cdk-context-menu.d.ts +15 -26
  125. package/types/forty-cdk-core-anchored.d.ts +2855 -0
  126. package/types/forty-cdk-core-overlay.d.ts +230 -2982
  127. package/types/forty-cdk-core.d.ts +37 -85
  128. package/types/forty-cdk-date-field.d.ts +9 -16
  129. package/types/forty-cdk-date-picker.d.ts +29 -20
  130. package/types/forty-cdk-defaults.d.ts +35 -34
  131. package/types/forty-cdk-dialog.d.ts +51 -15
  132. package/types/forty-cdk-drag-drop.d.ts +10 -6
  133. package/types/forty-cdk-drawer.d.ts +53 -16
  134. package/types/forty-cdk-dropdown-menu.d.ts +6 -13
  135. package/types/forty-cdk-file-upload.d.ts +6 -4
  136. package/types/forty-cdk-hover-card.d.ts +12 -9
  137. package/types/forty-cdk-id-salt.d.ts +40 -0
  138. package/types/forty-cdk-internationalized-date.d.ts +10 -6
  139. package/types/forty-cdk-listbox.d.ts +15 -14
  140. package/types/forty-cdk-menu.d.ts +30 -41
  141. package/types/forty-cdk-menubar.d.ts +2 -3
  142. package/types/forty-cdk-popover.d.ts +34 -15
  143. package/types/forty-cdk-progress.d.ts +9 -10
  144. package/types/forty-cdk-scroll-area.d.ts +49 -10
  145. package/types/forty-cdk-select.d.ts +32 -45
  146. package/types/forty-cdk-separator.d.ts +1 -1
  147. package/types/forty-cdk-shared.d.ts +3 -2
  148. package/types/forty-cdk-stepper.d.ts +14 -9
  149. package/types/forty-cdk-table-virtualization.d.ts +1 -1
  150. package/types/forty-cdk-table.d.ts +1 -1
  151. package/types/forty-cdk-testing.d.ts +38 -5
  152. package/types/forty-cdk-time-field.d.ts +9 -16
  153. package/types/forty-cdk-time-picker.d.ts +22 -8
  154. package/types/forty-cdk-toast.d.ts +37 -18
  155. package/types/forty-cdk-toolbar.d.ts +6 -16
  156. package/types/forty-cdk-tooltip.d.ts +25 -15
  157. package/types/forty-cdk-tree.d.ts +40 -20
  158. package/types/forty-cdk-virtual-reorder.d.ts +5 -1
  159. package/virtual-reorder/README.md +2 -0
  160. 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`, `forty-cdk/core-overlay` and `forty-cdk/core-anchored` resolve too, but none 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 three rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the overlay machinery in modules of its own means a lazy route that renders no overlay does not load it, and keeping the positioning engine (`@floating-ui/dom` and the anchored overlay shell) apart from the modal shell means an app that opens a dialog from its initial bundle does not load the engine with it. Measured on a seven-lazy-route app, the first split 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 any of them.
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,6 +96,25 @@ 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.
@@ -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';
@@ -84,23 +84,7 @@ export class BreadcrumbsDefaultExample {
84
84
 
85
85
  ### Collapsing a long trail
86
86
 
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
-
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.
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. Expanding removes the button, so focus moves to the first crumb it revealed instead of falling back to the page. The trail stays a single accessible navigation landmark either way.
104
88
 
105
89
  ## API
106
90
 
@@ -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,34 +31,27 @@ 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';
41
+ import { ForVisuallyHidden } from 'forty-cdk/visually-hidden';
44
42
 
45
43
  type TailwindName = keyof typeof forBreakpointsTailwind;
46
44
 
47
45
  @Component({
48
46
  selector: 'app-breakpoints-active-example',
49
47
  changeDetection: ChangeDetectionStrategy.OnPush,
48
+ imports: [ForVisuallyHidden],
50
49
  template: `
51
50
  <div class="bp-demo">
52
51
  <div class="bp-readout">
53
52
  <span class="bp-readout-label">active breakpoint</span>
54
53
  <span class="bp-active">{{ active() ?? 'below sm' }}</span>
55
- <span class="bp-width">viewport ≈ {{ width() }}px</span>
54
+ <span class="bp-width">viewport {{ range() }}</span>
56
55
  </div>
57
56
 
58
57
  <ul class="bp-grid">
@@ -61,8 +60,16 @@ type TailwindName = keyof typeof forBreakpointsTailwind;
61
60
  <span class="bp-name">{{ row.name }}</span>
62
61
  <span class="bp-min">≥ {{ row.min }}px</span>
63
62
  <span class="bp-flags">
64
- <span class="bp-flag" [class.bp-flag--on]="row.up()">up</span>
65
- <span class="bp-flag" [class.bp-flag--on]="row.only()">only</span>
63
+ <span class="bp-flag" [class.bp-flag--on]="row.up()">
64
+ <span aria-hidden="true">{{ row.up() ? '✓' : '✕' }}</span>
65
+ up
66
+ <span forVisuallyHidden>{{ row.up() ? 'matches' : 'does not match' }}</span>
67
+ </span>
68
+ <span class="bp-flag" [class.bp-flag--on]="row.only()">
69
+ <span aria-hidden="true">{{ row.only() ? '✓' : '✕' }}</span>
70
+ only
71
+ <span forVisuallyHidden>{{ row.only() ? 'matches' : 'does not match' }}</span>
72
+ </span>
66
73
  </span>
67
74
  </li>
68
75
  }
@@ -81,17 +88,12 @@ export class BreakpointsActiveExample {
81
88
  only: this.bp.only(name),
82
89
  }));
83
90
 
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
- }
91
+ protected readonly range = computed(() => {
92
+ const index = this.rows.findIndex((row) => row.name === this.active());
93
+ const from = this.rows[index]?.min ?? 0;
94
+ const to = this.rows[index + 1]?.min;
95
+ return to === undefined ? `${from}px and up` : `${from}px to ${to - 1}px`;
96
+ });
95
97
  }
96
98
  ```
97
99
 
@@ -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
 
@@ -393,7 +393,9 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
393
393
 
394
394
  The default window is **anchored to the current year** (not the visible year), so navigating far away never drops the current year off the list. Out-of-`[min, max]` entries have `disabled: true`, matching the `CalendarYearOption` shape. Both directives set the native `disabled` attribute on the `<select>` itself when the calendar is disabled.
395
395
 
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.
396
+ **Keep `[forCalendarHeading]` in the DOM.** The grid's `aria-labelledby` points at the heading's id while a heading is mounted. When dropdowns replace the visible heading, keep a visually-hidden `[forCalendarHeading]` so the grid stays named. Without a heading the grids emit no `aria-labelledby` at all, so they are unnamed unless you name them: a static `aria-labelledby` pointing at your own title, or an `aria-label`, written on each grid.
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>`.
397
399
 
398
400
  The lower-level hooks (`visibleMonthNumber()`, `visibleYear()`, `monthOptions()`, `goToMonth()`, `goToYear()`, `isYearDisabled()`) in the table above remain available for any other UI.
399
401
 
@@ -542,6 +544,8 @@ Auto-disabled when the entire previous / next page would be outside `[min, max]`
542
544
 
543
545
  ## Scoped defaults
544
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
+
545
549
  ```ts
546
550
  import { provideForCalendarDefaults } from 'forty-cdk/defaults';
547
551
 
@@ -554,6 +558,8 @@ providers: [
554
558
  ];
555
559
  ```
556
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
+
557
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.
558
564
 
559
565
  ## Keyboard
@@ -576,7 +582,7 @@ Focus that crosses a month boundary re-pages the visible grid and keeps the focu
576
582
  Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
577
583
 
578
584
  - **`role="grid"`** on the table, `columnheader` weekday headers and `gridcell` days follow the APG Date Picker Dialog technique over a real `<table>`.
579
- - **`aria-labelledby`** wires the grid to the heading so it names the visible period. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
585
+ - **`aria-labelledby`** wires the grid to the heading so it names the visible period. It is emitted only while a `[forCalendarHeading]` is mounted, and a static `aria-labelledby` you write on the grid replaces it. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
580
586
  - **`aria-label`** on every cell carries the full localized date (e.g. `"Monday, June 15, 2026"`) so screen readers announce the whole date, not the bare day number that stays the cell's visible content. Outside-month padding days are suffixed (`" (outside month)"`) so they are distinguishable. Override the format via `ForCalendar`'s `dateLabel` input.
581
587
  - **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
582
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.
@@ -598,7 +604,7 @@ forty-cdk ships no styles: put your own class on each piece and key your CSS off
598
604
  .calendar-cell[data-today] {
599
605
  outline: 1px solid var(--accent);
600
606
  }
601
- .calendar-cell[data-outside-month] {
607
+ .calendar-cell[data-outside-month]:not([data-selected]) {
602
608
  opacity: 0.4;
603
609
  }
604
610
  .calendar-cell[data-disabled] {
@@ -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.
@@ -239,6 +239,41 @@ The content anchors to the field box when a `[forComboboxAnchor]` is present; se
239
239
 
240
240
  Focus stays on the `<input>` the whole time the listbox is open, so options never get `:focus`. `data-highlighted` is the canonical hook for styling the keyboard-active option. A mouse press anywhere on the popup that lands on no focusable element (padding, a group label, an empty or status row) does not move focus either, so typing and the arrow keys keep working after it; the trade-off is that popup text cannot be selected with the mouse, as in a native combobox. A press on a `[forComboboxAction]` still focuses it in every browser. A press on your own `<button>` inside the popup is left to the browser, and macOS Safari and Firefox do not focus a `<button>` on a press.
241
241
 
242
+ ## Scoped defaults
243
+
244
+ `provideForComboboxDefaults` configures combobox 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).
245
+
246
+ | Key | Library fallback | Meaning |
247
+ | --------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
248
+ | `side` | `'bottom'` | Anchor side for comboboxes that don't set `side` themselves. |
249
+ | `align` | `'start'` | Alignment along `side` for comboboxes that don't set `align` themselves. `start` and `end` follow the writing direction. |
250
+ | `sideOffset` | `4` | Main-axis gap (px) for comboboxes that don't set `sideOffset` themselves. |
251
+ | `collisionPadding` | `8` | Collision-middleware padding (px) for comboboxes that don't set it themselves. |
252
+ | `openHighlight` | `'first'` | Where the editable anatomy's highlight lands on open (`'first'` or `'selected'`), for comboboxes that don't set `openHighlight` themselves. See [Open / close behavior](#open--close-behavior). |
253
+ | `restoreQueryOnClose` | `false` | Whether a single-select editable combobox writes the selected option's label back into `query` when the listbox closes without a pick, for comboboxes that don't set it themselves. |
254
+ | `loop` | `true` | Whether arrow navigation wraps past the first / last enabled option, for comboboxes that don't set `loop` themselves. |
255
+ | `toggleAriaLabel` | `'Show options'` | Accessible name of a `[forComboboxToggle]` that sets no `[ariaLabel]`. |
256
+ | `chipsAriaLabel` | `'Selected items'` | Accessible name of a multi-mode `[forComboboxChips]` group that sets no `[ariaLabel]`. |
257
+ | `clearAriaLabel` | `'Clear'` | Accessible name of a `[forComboboxClear]` that sets no `[ariaLabel]`. |
258
+ | `chipRemoveLabel` | `` (label) => `Remove ${label}` `` | Builds the accessible name of every `[forComboboxChipRemove]` from its chip's resolved option label. The piece takes no per-instance `[ariaLabel]`, so this key is the only way to change it. |
259
+
260
+ Per-instance inputs always win over the scope defaults. `toggleAriaLabel`, `chipsAriaLabel` and `clearAriaLabel` accept a string or a function returning one; the library calls the function each time it renders the name, so a function that reads a language signal keeps the name in the current language. See [Localizing default text](../shared/README.md#localizing-default-text).
261
+
262
+ ```ts
263
+ import { type ApplicationConfig } from '@angular/core';
264
+ import { provideForComboboxDefaults } from 'forty-cdk/defaults';
265
+
266
+ export const appConfig: ApplicationConfig = {
267
+ providers: [
268
+ provideForComboboxDefaults({
269
+ openHighlight: 'selected',
270
+ clearAriaLabel: 'Clear selection',
271
+ chipRemoveLabel: (label) => `Remove ${label} from the selection`,
272
+ }),
273
+ ],
274
+ };
275
+ ```
276
+
242
277
  ## Mount/visibility convention
243
278
 
244
279
  `[forComboboxContent]` follows the floating-overlay convention: the consumer's signal drives `@if`, the directive emits dismiss events when it wants to be unmounted. No `[hidden]`. The visible input lives outside the overlay; only the listbox surface portals.
@@ -751,7 +786,7 @@ When `[forComboboxVirtualized]` is absent or its `[totalCount]` is omitted, the
751
786
 
752
787
  ## Writing direction
753
788
 
754
- `[forCombobox]` exposes a `dir: 'ltr' | 'rtl'` input (default `'ltr'`). It drives:
789
+ `[forCombobox]` exposes a `dir: 'ltr' | 'rtl' | null` input (default `null`: inherited from the nearest `[dir]` ancestor or `<html dir>`, falling back to `'ltr'`). It drives:
755
790
 
756
791
  - **Chip keyboard navigation**: ArrowLeft / ArrowRight roles swap so they follow the visual order of the chip cluster, not the DOM order. See _Chip keyboard_ above.
757
792
  - **Default popover placement**: `align` defaults to `'start'` and `side` to `'bottom'`. `start` and `end` resolve against the writing direction, so the default anchors the listbox to the visually-leading edge of the input in both LTR and RTL, and a consumer-provided `[align]` follows the direction the same way. `provideForComboboxDefaults({ align, side })` sets either for a whole scope.
@@ -851,7 +886,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
851
886
  - `data-state="checked" | "unchecked"` always reflects membership in `value()`, so consumers can paint a checkmark icon with pure CSS regardless of mode.
852
887
  - `data-highlighted=""` marks the option that is the current `aria-activedescendant`. Because focus stays on the `<input>`, there is no `:focus` on the option to style. `data-highlighted` is the canonical CSS hook.
853
888
  - Disabled options keep the host `aria-disabled="true"`. Click and hover (activedescendant pinning) are no-ops on disabled options.
854
- - `[forComboboxSeparator]` never registers with the listbox's option collection, so keyboard navigation skips it automatically. It carries `role="separator"` and emits `aria-orientation` only for `orientation="vertical"`, because `horizontal` is the ARIA default; `data-orientation` is always stamped for styling. Set `decorative` when the surrounding options already convey the split. Setting it switches the line to `role="none"` and drops `aria-orientation`, matching the [shared separator emission policy](../separator/README.md#accessibility).
889
+ - `[forComboboxSeparator]` is skipped by keyboard navigation. It is always presentational: `role="none"` and no `aria-orientation`, because it sits inside the `role="listbox"` (the content in the editable anatomy, the list in the picker anatomy), which may only own `option` / `group` children. `data-orientation` is always stamped for styling. When the split must be announced, wrap the options on each side in a `[forComboboxGroup]` with a `[forComboboxGroupLabel]`.
855
890
  - `[forComboboxGroup]` is purely advisory grouping: options inside still register flatly with the root, so navigation flows through groups without interruption.
856
891
  - `[forComboboxEmpty]` and `[forComboboxStatus]` carry `role="status"` and nothing else. The role already implies `aria-live="polite"` and `aria-atomic="true"`, so the empty-state message is announced, whole, when filtering removes all matches. The role is the channel they keep (rather than the attribute pair) because `[forComboboxEmpty]` self-hides and comes back with its message already in the DOM: a live _role_ is what screen readers read reliably on insertion.
857
892
  - Non-option pieces (`[forComboboxAction]`, and ideally `[forComboboxEmpty]` / `[forComboboxStatus]`) belong inside `[forComboboxContent]` but **outside** `[forComboboxList]`, because `role="listbox"` may only own `option` / `group` children (`aria-required-owned-elements`). Wrapping the options in a `[forComboboxList]` (the "editable + list" shape) makes those pieces siblings of the listbox. `[forComboboxAction]` **requires** a `[forComboboxList]` and throws `[forty-cdk/combobox]` without one; `[forComboboxEmpty]` / `[forComboboxStatus]` stay lenient in the bare editable anatomy (documented compromise, see the [editable-anatomy caveat](#anatomy)).
@@ -119,6 +119,30 @@ Same vetoable dismiss API as DropdownMenu. Call `preventDefault()` on the emitte
119
119
  | `[forContextMenuTrigger]` | `data-state` | `open` \| `closed` |
120
120
  | `[forContextMenuTrigger]` | `data-disabled` | present \| absent |
121
121
 
122
+ ## Scoped defaults
123
+
124
+ `provideForContextMenuDefaults` configures context-menu 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).
125
+
126
+ | Key | Library fallback | Meaning |
127
+ | --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
128
+ | `side` | `'bottom'` | Anchor side, relative to the pointer, for context menus that don't set `side` themselves. |
129
+ | `align` | `'start'` | Alignment along `side` for context menus that don't set `align` themselves. |
130
+ | `sideOffset` | `0` | Main-axis gap (px) between the pointer and the menu for context menus that don't set `sideOffset` themselves. |
131
+ | `collisionPadding` | `8` | Collision-middleware padding (px) for context menus that don't set it themselves. |
132
+ | `fallbackAxisSideDirection` | `'none'` | Perpendicular side `flip` may drop to when both sides of the preferred axis overflow. `'none'` keeps only the opposite same-axis placement. Only consulted while `avoidCollisions` is on. |
133
+ | `loop` | `true` | Whether arrow navigation wraps past the first / last enabled item, for context menus that don't set `loop` themselves. |
134
+
135
+ Per-instance inputs always win over the scope defaults. Submenus inside a context menu take their timings and placement from `provideForMenuDefaults`; see the [Menu README](../menu/README.md#scoped-defaults).
136
+
137
+ ```ts
138
+ import { type ApplicationConfig } from '@angular/core';
139
+ import { provideForContextMenuDefaults } from 'forty-cdk/defaults';
140
+
141
+ export const appConfig: ApplicationConfig = {
142
+ providers: [provideForContextMenuDefaults({ sideOffset: 2, fallbackAxisSideDirection: 'end' })],
143
+ };
144
+ ```
145
+
122
146
  ## Accessibility
123
147
 
124
148
  `[forContextMenu]` implements the [WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/). The trigger captures `contextmenu`, `Shift+F10`, and the `ContextMenu` key; the menu surface and item roles come from the shared [`menu/`](../menu/README.md) primitives. Pointer activations anchor at the cursor; keyboard activations anchor at the bounding rect of the focused element so keyboard-only users get the menu next to whatever they're working on.
@@ -208,4 +232,4 @@ Both triggers carry `[menuPositioning]`, a partial `{ side, align, sideOffset, a
208
232
 
209
233
  ## Wrapping in a design system
210
234
 
211
- Subclass the root and re-provide `FOR_MENU_CONTEXT` with `useExisting` pointing at the subclass, since Angular does not inherit a directive's `providers`; [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md) walks the pattern.
235
+ Subclass the root and re-provide both of its tokens, `FOR_MENU_CONTEXT` and `FOR_CONTEXT_MENU_CONTEXT`, with `useExisting` pointing at the subclass, since Angular does not inherit a directive's `providers`. Leaving out the second one throws `FORCDK-CONTEXT-MENU-001` from the trigger. [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md) walks the pattern.