forty-cdk 0.27.0 → 0.29.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 (185) hide show
  1. package/accordion/README.md +2 -2
  2. package/aspect-ratio/README.md +1 -1
  3. package/avatar/README.md +2 -2
  4. package/breadcrumbs/README.md +2 -0
  5. package/button/README.md +1 -1
  6. package/calendar/README.md +26 -19
  7. package/carousel/README.md +41 -15
  8. package/checkbox/README.md +2 -2
  9. package/combobox/README.md +61 -16
  10. package/context-menu/README.md +3 -2
  11. package/date-field/README.md +32 -25
  12. package/date-picker/README.md +72 -22
  13. package/dialog/README.md +47 -12
  14. package/disclosure/README.md +2 -2
  15. package/drag-drop/README.md +41 -1
  16. package/drawer/README.md +17 -7
  17. package/dropdown-menu/README.md +2 -2
  18. package/fesm2022/forty-cdk-avatar.mjs +4 -0
  19. package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
  20. package/fesm2022/forty-cdk-breadcrumbs.mjs +8 -4
  21. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-breakpoints.mjs +9 -13
  23. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-calendar.mjs +17 -2
  25. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  26. package/fesm2022/forty-cdk-carousel.mjs +28 -11
  27. package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
  28. package/fesm2022/forty-cdk-combobox.mjs +165 -25
  29. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  30. package/fesm2022/forty-cdk-context-menu.mjs +61 -15
  31. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
  32. package/fesm2022/forty-cdk-core-overlay.mjs +188 -119
  33. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  34. package/fesm2022/forty-cdk-core.mjs +270 -29
  35. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  36. package/fesm2022/forty-cdk-date-field.mjs +108 -33
  37. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  38. package/fesm2022/forty-cdk-date-picker.mjs +211 -45
  39. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  40. package/fesm2022/forty-cdk-dialog.mjs +144 -8
  41. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-drag-drop.mjs +11 -4
  43. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  44. package/fesm2022/forty-cdk-drawer.mjs +92 -7
  45. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  46. package/fesm2022/forty-cdk-dropdown-menu.mjs +4 -0
  47. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
  48. package/fesm2022/forty-cdk-field.mjs +140 -7
  49. package/fesm2022/forty-cdk-field.mjs.map +1 -1
  50. package/fesm2022/forty-cdk-hover-card.mjs +11 -6
  51. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
  52. package/fesm2022/forty-cdk-input.mjs +31 -6
  53. package/fesm2022/forty-cdk-input.mjs.map +1 -1
  54. package/fesm2022/forty-cdk-internationalized-date.mjs +18 -2
  55. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  56. package/fesm2022/forty-cdk-listbox.mjs +4 -0
  57. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  58. package/fesm2022/forty-cdk-menu.mjs +4 -0
  59. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  60. package/fesm2022/forty-cdk-menubar.mjs +4 -0
  61. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  62. package/fesm2022/forty-cdk-navigation-menu.mjs +4 -0
  63. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  64. package/fesm2022/forty-cdk-number-input.mjs +4 -0
  65. package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
  66. package/fesm2022/forty-cdk-otp-input.mjs +17 -3
  67. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
  68. package/fesm2022/forty-cdk-pagination.mjs +4 -0
  69. package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
  70. package/fesm2022/forty-cdk-popover.mjs +92 -7
  71. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  72. package/fesm2022/forty-cdk-progress.mjs +7 -3
  73. package/fesm2022/forty-cdk-progress.mjs.map +1 -1
  74. package/fesm2022/forty-cdk-radio-group.mjs +4 -0
  75. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
  76. package/fesm2022/forty-cdk-scroll-area.mjs +4 -0
  77. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
  78. package/fesm2022/forty-cdk-search.mjs +8 -4
  79. package/fesm2022/forty-cdk-search.mjs.map +1 -1
  80. package/fesm2022/forty-cdk-select.mjs +17 -7
  81. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  82. package/fesm2022/forty-cdk-slider.mjs +4 -0
  83. package/fesm2022/forty-cdk-slider.mjs.map +1 -1
  84. package/fesm2022/forty-cdk-stepper.mjs +4 -0
  85. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  86. package/fesm2022/forty-cdk-table.mjs +3 -1
  87. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  88. package/fesm2022/forty-cdk-tabs.mjs +4 -0
  89. package/fesm2022/forty-cdk-tabs.mjs.map +1 -1
  90. package/fesm2022/forty-cdk-time-field.mjs +108 -33
  91. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  92. package/fesm2022/forty-cdk-time-picker.mjs +146 -20
  93. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  94. package/fesm2022/forty-cdk-toast.mjs +76 -21
  95. package/fesm2022/forty-cdk-toast.mjs.map +1 -1
  96. package/fesm2022/forty-cdk-toggle.mjs +19 -3
  97. package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
  98. package/fesm2022/forty-cdk-toolbar.mjs +4 -0
  99. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
  100. package/fesm2022/forty-cdk-tooltip.mjs +17 -5
  101. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
  102. package/fesm2022/forty-cdk-tree.mjs +4 -0
  103. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  104. package/fesm2022/forty-cdk-virtualization.mjs +41 -2
  105. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  106. package/field/README.md +63 -2
  107. package/fieldset/README.md +2 -2
  108. package/file-upload/README.md +2 -2
  109. package/hover-card/README.md +19 -3
  110. package/input/README.md +51 -26
  111. package/listbox/README.md +2 -2
  112. package/menu/README.md +2 -2
  113. package/menubar/README.md +2 -2
  114. package/meter/README.md +2 -2
  115. package/navigation-menu/README.md +2 -2
  116. package/number-input/README.md +1 -1
  117. package/otp-input/README.md +11 -5
  118. package/package.json +1 -1
  119. package/pagination/README.md +1 -1
  120. package/pane-resizer/README.md +1 -1
  121. package/popover/README.md +30 -3
  122. package/progress/README.md +2 -2
  123. package/radio-group/README.md +1 -1
  124. package/scroll-area/README.md +2 -2
  125. package/select/README.md +2 -2
  126. package/separator/README.md +1 -1
  127. package/shared/README.md +27 -1
  128. package/slider/README.md +1 -1
  129. package/stepper/README.md +2 -2
  130. package/switch/README.md +1 -1
  131. package/table/README.md +1 -1
  132. package/tabs/README.md +2 -2
  133. package/time-field/README.md +24 -17
  134. package/time-picker/README.md +74 -26
  135. package/toast/README.md +44 -6
  136. package/toggle/README.md +20 -19
  137. package/toolbar/README.md +2 -2
  138. package/tooltip/README.md +19 -3
  139. package/tree/README.md +4 -2
  140. package/types/forty-cdk-avatar.d.ts +5 -1
  141. package/types/forty-cdk-breadcrumbs.d.ts +8 -3
  142. package/types/forty-cdk-breakpoints.d.ts +13 -9
  143. package/types/forty-cdk-calendar.d.ts +29 -10
  144. package/types/forty-cdk-carousel.d.ts +29 -7
  145. package/types/forty-cdk-combobox.d.ts +138 -26
  146. package/types/forty-cdk-context-menu.d.ts +30 -4
  147. package/types/forty-cdk-core-overlay.d.ts +171 -112
  148. package/types/forty-cdk-core.d.ts +324 -50
  149. package/types/forty-cdk-date-field.d.ts +69 -18
  150. package/types/forty-cdk-date-picker.d.ts +156 -24
  151. package/types/forty-cdk-dialog.d.ts +73 -4
  152. package/types/forty-cdk-drag-drop.d.ts +14 -7
  153. package/types/forty-cdk-drawer.d.ts +66 -4
  154. package/types/forty-cdk-dropdown-menu.d.ts +5 -1
  155. package/types/forty-cdk-field.d.ts +113 -29
  156. package/types/forty-cdk-hover-card.d.ts +24 -6
  157. package/types/forty-cdk-input.d.ts +12 -0
  158. package/types/forty-cdk-internationalized-date.d.ts +16 -4
  159. package/types/forty-cdk-listbox.d.ts +5 -1
  160. package/types/forty-cdk-menu.d.ts +5 -1
  161. package/types/forty-cdk-menubar.d.ts +5 -1
  162. package/types/forty-cdk-navigation-menu.d.ts +5 -1
  163. package/types/forty-cdk-number-input.d.ts +5 -1
  164. package/types/forty-cdk-otp-input.d.ts +12 -2
  165. package/types/forty-cdk-pagination.d.ts +5 -1
  166. package/types/forty-cdk-popover.d.ts +69 -4
  167. package/types/forty-cdk-progress.d.ts +7 -2
  168. package/types/forty-cdk-radio-group.d.ts +5 -1
  169. package/types/forty-cdk-scroll-area.d.ts +5 -1
  170. package/types/forty-cdk-search.d.ts +8 -4
  171. package/types/forty-cdk-select.d.ts +12 -5
  172. package/types/forty-cdk-shared.d.ts +1 -1
  173. package/types/forty-cdk-slider.d.ts +5 -1
  174. package/types/forty-cdk-stepper.d.ts +5 -1
  175. package/types/forty-cdk-tabs.d.ts +5 -1
  176. package/types/forty-cdk-time-field.d.ts +68 -17
  177. package/types/forty-cdk-time-picker.d.ts +92 -16
  178. package/types/forty-cdk-toast.d.ts +78 -21
  179. package/types/forty-cdk-toggle.d.ts +23 -6
  180. package/types/forty-cdk-toolbar.d.ts +5 -1
  181. package/types/forty-cdk-tooltip.d.ts +30 -6
  182. package/types/forty-cdk-tree.d.ts +5 -1
  183. package/types/forty-cdk-virtualization.d.ts +7 -1
  184. package/virtualization/README.md +6 -1
  185. package/visually-hidden/README.md +1 -1
@@ -175,7 +175,7 @@ A disabled item cannot be toggled and is skipped by the arrow keys, while stayin
175
175
 
176
176
  ## Styling
177
177
 
178
- forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed per piece in the [API](#api) section.
178
+ 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).
179
179
 
180
180
  ```css
181
181
  .chevron {
@@ -189,4 +189,4 @@ forty-cdk ships no styles. Add your own class to each piece. The `for*` selector
189
189
 
190
190
  ## Wrapping in a design system
191
191
 
192
- Subclassing the root is the supported pattern; the subclass must re-provide `FOR_ACCORDION_CONTEXT` with `useExisting` pointing at itself, because Angular does not inherit a directive's `providers` and every projected piece resolves its context through that token. See [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md).
192
+ Subclass the root and re-provide `FOR_ACCORDION_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.
@@ -76,7 +76,7 @@ Set `ratio` to `1` to keep a box perfectly square at any width. That is handy fo
76
76
 
77
77
  ## Styling
78
78
 
79
- forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). This primitive is purely structural: its only host effect is the native `aspect-ratio` style, so it reflects no `data-*` attributes and writes no CSS custom properties. Style the host through your own class on `[forAspectRatio]`.
79
+ forty-cdk ships no styles: put your own class on the host rather than styling the `for*` selector ([Styling forty-cdk](../../../docs/styling.md) explains why). This primitive is purely structural: its only host effect is the native `aspect-ratio` style, so it reflects no `data-*` attributes and writes no CSS custom properties.
80
80
 
81
81
  ## Behavior notes
82
82
 
package/avatar/README.md CHANGED
@@ -102,7 +102,7 @@ The directive does not impose a `role`. Pair the avatar with visible name text o
102
102
 
103
103
  ## Styling
104
104
 
105
- forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed per piece in the [API](#api) section.
105
+ 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).
106
106
 
107
107
  ```css
108
108
  .avatar-image:not([data-status='loaded']) {
@@ -122,4 +122,4 @@ forty-cdk ships no styles. Add your own class to each piece. The `for*` selector
122
122
 
123
123
  ## Wrapping in a design system
124
124
 
125
- Subclassing the root is the supported pattern; the subclass must re-provide `FOR_AVATAR_CONTEXT` because Angular does not inherit a directive's `providers`, and every projected piece resolves its context through it. See [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md).
125
+ Subclass the root and re-provide `FOR_AVATAR_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.
@@ -100,6 +100,8 @@ bootstrapApplication(App, {
100
100
  });
101
101
  ```
102
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
+
103
105
  ## API
104
106
 
105
107
  ### `ForBreadcrumbs`
package/button/README.md CHANGED
@@ -115,7 +115,7 @@ Implements the [WAI-ARIA Button pattern](https://www.w3.org/WAI/ARIA/apg/pattern
115
115
 
116
116
  ## Styling
117
117
 
118
- forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed per piece in the [API](#api) section.
118
+ 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).
119
119
 
120
120
  ```css
121
121
  [forButton][data-disabled] {
@@ -210,22 +210,22 @@ Click the heading button to cycle from day → month → year view. Click a mont
210
210
 
211
211
  ### `ForCalendar`
212
212
 
213
- | Property | Type | Description |
214
- | ------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
215
- | `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null` |
216
- | `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'` |
217
- | `range` | `model<DateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null` |
218
- | `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum) |
219
- | `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum) |
220
- | `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null` |
221
- | `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null` |
222
- | `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false` |
223
- | `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days suffixed |
224
- | `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** — |
225
- | `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** — |
226
- | `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`) |
227
- | `locale` | `input<string \| null>` | BCP 47 locale for the heading, weekday headers, month-picker options and cell `aria-label` names. The calendar system stays Gregorian.<br>**Default:** `null` → the runtime's default locale |
228
- | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows |
213
+ | Property | Type | Description |
214
+ | ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
215
+ | `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null` |
216
+ | `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'` |
217
+ | `range` | `model<DateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null` |
218
+ | `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum) |
219
+ | `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum) |
220
+ | `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null` |
221
+ | `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null` |
222
+ | `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false` |
223
+ | `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days through the scope's `outsideMonthLabel` |
224
+ | `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** — |
225
+ | `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** — |
226
+ | `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`) |
227
+ | `locale` | `input<string \| null>` | BCP 47 locale for the heading, weekday headers, month-picker options and cell `aria-label` names. The calendar system stays Gregorian.<br>**Default:** `null` → the adapter's `locale()`, then the runtime locale |
228
+ | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows |
229
229
 
230
230
  ### Data attributes
231
231
 
@@ -538,9 +538,16 @@ Auto-disabled when the entire previous / next page would be outside `[min, max]`
538
538
  import { provideForCalendarDefaults } from 'forty-cdk/calendar';
539
539
 
540
540
  // app config or a component's providers — Monday-first weeks for this scope
541
- providers: [provideForCalendarDefaults({ firstDayOfWeek: 1 })];
541
+ providers: [
542
+ provideForCalendarDefaults({
543
+ firstDayOfWeek: 1,
544
+ outsideMonthLabel: (formattedDate) => `${formattedDate} (fuera del mes)`,
545
+ }),
546
+ ];
542
547
  ```
543
548
 
549
+ `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.
550
+
544
551
  ## Keyboard
545
552
 
546
553
  LTR (horizontal arrows mirror under `dir="rtl"`):
@@ -569,7 +576,7 @@ Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/
569
576
 
570
577
  ## Styling
571
578
 
572
- forty-cdk ships no styles. Add your own class to each piece. The `forCalendar*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed under [Data attributes](#data-attributes).
579
+ forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [Data attributes](#data-attributes), not off the `forCalendar*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
573
580
 
574
581
  ```css
575
582
  .calendar-cell {
@@ -615,4 +622,4 @@ export class DatePage {
615
622
 
616
623
  ## Wrapping in a design system
617
624
 
618
- Subclassing the root is the supported pattern; the subclass must re-provide `FOR_CALENDAR_CONTEXT` because Angular does not inherit a directive's `providers`, and every projected piece resolves its context through it. See [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md).
625
+ Subclass the root and re-provide `FOR_CALENDAR_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.
@@ -74,7 +74,8 @@ import {
74
74
 
75
75
  interface Slide {
76
76
  readonly id: number;
77
- readonly label: string;
77
+ readonly title: string;
78
+ readonly summary: string;
78
79
  }
79
80
 
80
81
  @Component({
@@ -98,7 +99,7 @@ interface Slide {
98
99
  loop
99
100
  orientation="horizontal"
100
101
  align="start"
101
- ariaLabel="Featured slides"
102
+ ariaLabel="Featured stories"
102
103
  >
103
104
  <div class="car-controls-row">
104
105
  <button forCarouselPrevious class="car-btn" aria-label="Previous slide">
@@ -131,7 +132,8 @@ interface Slide {
131
132
  <div forCarouselTrack class="car-track">
132
133
  @for (slide of slides; track slide.id; let i = $index) {
133
134
  <div forCarouselSlide class="car-slide" [class]="'car-slide--' + (i + 1)">
134
- <span class="car-slide-label">{{ slide.label }}</span>
135
+ <p class="car-slide-title">{{ slide.title }}</p>
136
+ <p class="car-slide-summary">{{ slide.summary }}</p>
135
137
  </div>
136
138
  }
137
139
  </div>
@@ -151,11 +153,27 @@ interface Slide {
151
153
  })
152
154
  export class CarouselDefaultExample {
153
155
  protected readonly slides: readonly Slide[] = [
154
- { id: 1, label: 'Slide 1' },
155
- { id: 2, label: 'Slide 2' },
156
- { id: 3, label: 'Slide 3' },
157
- { id: 4, label: 'Slide 4' },
158
- { id: 5, label: 'Slide 5' },
156
+ {
157
+ id: 1,
158
+ title: 'The coast road north',
159
+ summary: 'Four days, three ferries and one small car.',
160
+ },
161
+ {
162
+ id: 2,
163
+ title: 'Markets before dawn',
164
+ summary: 'Where the city buys its fish, flowers and coffee.',
165
+ },
166
+ {
167
+ id: 3,
168
+ title: 'A week without screens',
169
+ summary: 'What changed, and the one habit that stuck.',
170
+ },
171
+ { id: 4, title: 'Bread, slowly', summary: 'A starter, a schedule and a forgiving first loaf.' },
172
+ {
173
+ id: 5,
174
+ title: 'Mapping the old river',
175
+ summary: 'Tracing a buried stream through five neighbourhoods.',
176
+ },
159
177
  ];
160
178
 
161
179
  protected readonly activeIndex = signal(0);
@@ -339,9 +357,11 @@ captured, so page scrolling on the perpendicular axis is unaffected.
339
357
 
340
358
  Each slide's default `aria-label` is the positional `"N of M"` string, each
341
359
  indicator's is `"Go to slide N"`, and the rotation control's swaps between
342
- `"Start automatic slide show"` and `"Stop automatic slide show"`. Localize them
343
- all centrally with `provideForCarouselDefaults` instead of setting `ariaLabel` on
344
- every slide and indicator:
360
+ `"Start automatic slide show"` and `"Stop automatic slide show"`. The root and
361
+ each slide also carry an `aria-roledescription` (`"carousel"` / `"slide"`), which
362
+ a screen reader speaks in place of the `group` role. Localize them all centrally
363
+ with `provideForCarouselDefaults` instead of setting `ariaLabel` on every slide
364
+ and indicator:
345
365
 
346
366
  <!-- snippet: fragment -->
347
367
 
@@ -352,6 +372,8 @@ providers: [
352
372
  indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
353
373
  rotationStartLabel: 'Iniciar la presentación',
354
374
  rotationStopLabel: 'Detener la presentación',
375
+ roleDescription: 'carrusel',
376
+ slideRoleDescription: 'diapositiva',
355
377
  }),
356
378
  ];
357
379
  ```
@@ -360,7 +382,10 @@ providers: [
360
382
  merge with the parent scope, so you can localize just the labels and inherit the
361
383
  rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
362
384
  `[forCarouselIndicator]` still takes precedence over the localized default, as do
363
- `[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`.
385
+ `[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`. For a language
386
+ the app sets or switches after bootstrap, pass the text keys as functions and the
387
+ overrides as a factory, as
388
+ [Localizing default text](../shared/README.md#localizing-default-text) shows.
364
389
 
365
390
  ## Indicators map 1:1 to slides
366
391
 
@@ -455,7 +480,8 @@ Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patte
455
480
  should describe the carousel's purpose without using the word "carousel" (APG guidance).
456
481
  - Each slide carries `role="group"`, `aria-roledescription="slide"`, and
457
482
  `aria-label="N of M"` by default. Override per slide with the `ariaLabel` input,
458
- or localize the default format app-wide with `provideForCarouselDefaults` (see
483
+ or localize the default format and both role descriptions app-wide with
484
+ `provideForCarouselDefaults` (see
459
485
  [Localizing the default labels](#localizing-the-default-labels)).
460
486
  - Off-view slides receive `aria-hidden="true"` and `inert` to remove them from the
461
487
  accessibility tree and focus order.
@@ -473,7 +499,7 @@ Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patte
473
499
 
474
500
  ## Styling
475
501
 
476
- forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). The directive publishes geometry as CSS custom properties on the root element so they cascade to the track. The consumer applies the transform and transition.
502
+ forty-cdk ships no styles: put your own class on each piece rather than styling the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why). The directive publishes geometry as CSS custom properties on the root element so they cascade to the track. The consumer applies the transform and transition.
477
503
 
478
504
  ```css
479
505
  [forCarouselViewport] {
@@ -577,4 +603,4 @@ The example CSS above is LTR-only by default.
577
603
 
578
604
  ## Wrapping in a design system
579
605
 
580
- Subclassing the root is the supported pattern; the subclass must re-provide `FOR_CAROUSEL_CONTEXT` with `useExisting` pointing at itself, because Angular does not inherit a directive's `providers` and every projected piece resolves its context through that token. See [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md).
606
+ Subclass the root and re-provide `FOR_CAROUSEL_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.
@@ -188,7 +188,7 @@ Implements the [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patte
188
188
 
189
189
  ## Styling
190
190
 
191
- forty-cdk ships no styles. Add your own class to each piece. The for\* selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected data-\* attributes listed per piece in the [API](#api) section.
191
+ 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).
192
192
 
193
193
  ```css
194
194
  .cb-check {
@@ -213,4 +213,4 @@ forty-cdk ships no styles. Add your own class to each piece. The for\* selectors
213
213
 
214
214
  ## Wrapping in a design system
215
215
 
216
- Both supported wrapper patterns are documented in [Wrapping form primitives](../../../docs/wrapping-form-primitives.md): `hostDirectives` with the exported `FOR_CHECKBOX_HOST_DIRECTIVE_INPUTS` / `FOR_CHECKBOX_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing.
216
+ [Wrapping form primitives](../../../docs/wrapping-form-primitives.md) documents both supported wrapper patterns: `hostDirectives` with the exported `FOR_CHECKBOX_HOST_DIRECTIVE_INPUTS` / `FOR_CHECKBOX_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing.
@@ -207,6 +207,8 @@ Input tables are not yet tabulated for this primitive. See the feature sections
207
207
  | `[forCombobox]` | `data-readonly` | present / absent |
208
208
  | `[forComboboxInput]` | `data-state` | `open` \| `closed` |
209
209
  | `[forComboboxInput]` | `data-disabled` | present / absent |
210
+ | `[forComboboxToggle]` | `data-state` | `open` \| `closed` |
211
+ | `[forComboboxToggle]` | `data-disabled` | present / absent |
210
212
  | `[forComboboxContent]` | `data-state` | `open` \| `closed` |
211
213
  | `[forComboboxOption]` | `data-state` | `checked` \| `unchecked` (membership in `value()`, both modes) |
212
214
  | `[forComboboxOption]` | `data-highlighted` | present / absent (the current `aria-activedescendant`) |
@@ -245,7 +247,31 @@ By default the listbox is positioned against `[forComboboxInput]`. When the inpu
245
247
  </div>
246
248
  ```
247
249
 
248
- `[forComboboxAnchor]` changes **only** positioning. The input keeps `aria-controls` / `aria-expanded` / `aria-activedescendant`, all keyboard interaction, and its exemption from outside-pointer dismissal. Without an anchor the listbox falls back to the input, so existing markup is unaffected. Each `[forCombobox]` takes at most one `[forComboboxAnchor]`, and a second one throws `[forty-cdk/combobox]`. In multi mode, wrap `[forComboboxChips]` (which already wraps the chips + input) to anchor against the full chip cluster.
250
+ `[forComboboxAnchor]` changes **only** positioning. The input keeps `aria-controls` / `aria-expanded` / `aria-activedescendant`, all keyboard interaction, and its exemption from outside-pointer dismissal. Without an anchor the listbox falls back to the input, so existing markup is unaffected. It wins over a surrounding field's [`[forFieldAnchor]`](../field/README.md#positioning-anchor). Each `[forCombobox]` takes one `[forComboboxAnchor]`, and a second one warns in dev mode. In multi mode, wrap `[forComboboxChips]` (which already wraps the chips + input) to anchor against the full chip cluster.
251
+
252
+ ## Toggle button
253
+
254
+ An editable combobox often carries a chevron button next to the input, as in the APG's [editable combobox examples](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/). Put `[forComboboxToggle]` on a real `<button>`:
255
+
256
+ ```html
257
+ <div forCombobox #combobox="forCombobox" [(query)]="query" [(value)]="value">
258
+ <div forComboboxAnchor class="field-box">
259
+ <input forComboboxInput placeholder="Search a fruit…" />
260
+ <button forComboboxToggle>▾</button>
261
+ </div>
262
+ @if (combobox.open()) {
263
+ <div forComboboxContent>
264
+ @for (it of filtered; track it.id) {
265
+ <div forComboboxOption [value]="it.id" [label]="it.label">{{ it.label }}</div>
266
+ }
267
+ </div>
268
+ }
269
+ </div>
270
+ ```
271
+
272
+ A press closes an open listbox, or opens a closed one with the committed selection highlighted (the first enabled option when nothing is selected) and moves focus into the input. The press never takes focus itself, so an input that already has focus keeps it and the combobox is not marked touched. The button is exempt from the listbox's outside-pointer dismissal, so one press is one `(openChange)`.
273
+
274
+ Unlike `[forComboboxTrigger]`, a toggle keeps the editable anatomy: `commitOnSelect` still copies the picked label into the input, and `query` survives a close. The button is out of the Tab sequence (`tabindex="-1"`) because the input already owns the keyboard, reflects `aria-expanded` and `aria-controls` (the listbox, while open), and carries native `disabled` from the combobox's effective disabled. Its accessible name defaults to `'Show options'`; override it per instance with `[ariaLabel]`, or for the scope with `provideForComboboxDefaults({ toggleAriaLabel })`.
249
275
 
250
276
  ## Picker anatomy
251
277
 
@@ -402,6 +428,7 @@ They diverge while the user types and resync on activation:
402
428
  - **Multi mode** → option's value is toggled in/out of `value`. If `commitOnSelect` is on (default), `query` is **cleared** so the user can search the next item. Listbox stays open.
403
429
  - Clear button → both reset.
404
430
  - `clearOnQueryChange` (off by default, **single mode only**): flip on to drop `value` automatically whenever the query is edited (useful when the user editing means "I'm picking a new one").
431
+ - `restoreQueryOnClose` (off by default, **single mode only**): flip on to put the selected label back into the input when the listbox closes without a pick. See [`restoreQueryOnClose`](#restorequeryonclose).
405
432
 
406
433
  ### `commitOnSelect`: single vs multi
407
434
 
@@ -429,6 +456,19 @@ Multi, commitOnSelect=false
429
456
 
430
457
  Disable `commitOnSelect` when your filter logic compares against `query` directly and the listbox should keep showing the just-narrowed set after activation, instead of resetting to "everything matches the picked label".
431
458
 
459
+ ### `restoreQueryOnClose`
460
+
461
+ In the editable anatomy, closing the listbox leaves `query` as the user left it, so typing "ap" over a committed "Banana" and pressing Escape keeps showing "ap". With `[restoreQueryOnClose]="true"`, a single-select combobox restores the selected option's label on every close that is not a pick (Escape, an outside press, Tab, a `[forComboboxToggle]` press, `closeOverlay()`), and clears the input when nothing is selected. The input keeps focus on Escape, and the restored text reaches it even while focused. A pick still follows `commitOnSelect`.
462
+
463
+ ```text
464
+ Single, restoreQueryOnClose=true
465
+ user activates "Banana" → query="Banana" value=["banana"]
466
+ user types "ap" → query="ap" value=["banana"]
467
+ user presses Escape → query="Banana" value=["banana"] ← label restored, listbox closes
468
+ ```
469
+
470
+ The label is the one `selected()` resolves: the option's own label once it has rendered, `[itemToStringLabel]` before that (a value bound before the listbox ever opened). Multi mode and the picker anatomy ignore the input. Enable it for the whole scope with `provideForComboboxDefaults({ restoreQueryOnClose: true })`.
471
+
432
472
  ## Multi mode
433
473
 
434
474
  Pass `multiple` and let the consumer render chips inside `[forComboboxChips]`. The primitive's `selected()` computed returns `{ value, label }` pairs ready for `@for`:
@@ -493,6 +533,10 @@ When the input is empty (no query) and the user presses Backspace, focus jumps t
493
533
  | Auto-highlight first option | `true` | `[autoHighlight]="false"` to require arrowing to an option first |
494
534
  | Commit label / clear query on select | `true` | `[commitOnSelect]="false"` |
495
535
  | Clear value on query edit (single only) | `false` | `[clearOnQueryChange]="true"` |
536
+ | Highlight the selection on open | `first` | `openHighlight="selected"` |
537
+ | Restore label on close (single only) | `false` | `[restoreQueryOnClose]="true"` |
538
+
539
+ `openHighlight` decides where the editable anatomy's highlight lands when the listbox opens from focus, click, ArrowDown / ArrowUp or `openOverlay()` without an argument. With `'selected'`, a single-select combobox showing "Spain" reopens on "Spain" rather than on the first option; with nothing selected, ArrowDown still lands on the first option and ArrowUp on the last. Opening from a typed query always highlights the first match, because the list is a filter result there, and the picker anatomy always opens on the selection. Both inputs take their default from the scope: `provideForComboboxDefaults({ openHighlight: 'selected', restoreQueryOnClose: true })`.
496
540
 
497
541
  ## Autocomplete modes
498
542
 
@@ -735,20 +779,20 @@ A single-select field is modeled as the same `readonly T[]`, kept at length ≤
735
779
 
736
780
  Focus stays in the input throughout: arrow keys move the listbox's _active descendant_ (the highlighted option), not DOM focus.
737
781
 
738
- | Key | Action |
739
- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
740
- | **ArrowDown** | Open listbox + move activedescendant to next enabled option (or first when none). |
741
- | **ArrowUp** | Open listbox + move activedescendant to previous enabled option (or last when none). |
742
- | **Home** _(open)_ | Move activedescendant to first enabled option. |
743
- | **End** _(open)_ | Move activedescendant to last enabled option. |
744
- | **PageUp** _(open)_ | Move activedescendant to first enabled option. |
745
- | **PageDown** _(open)_ | Move activedescendant to last enabled option. |
746
- | **Enter** _(open)_ | Activate the activedescendant (single: replace + close; multi: toggle + stay open). |
747
- | **Escape** _(open)_ | Close the listbox. Focus stays in the input. |
748
- | **Tab** _(open, no action)_ | Close the listbox and let Tab flow to the next focusable. |
749
- | **Tab / Shift+Tab** _(open, action present)_ | Move focus around the input↔actions ring without dismissing (see [Action items](#action-items)). |
750
- | **Backspace** _(empty input, multi only)_ | Focus the last chip; a second Backspace there removes it. |
751
- | Printable keys | Update `query`. With `'inline'` / `'both'` autocomplete, complete the rest of the first match into the input as selected text. |
782
+ | Key | Action |
783
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
784
+ | **ArrowDown** | Open listbox + move activedescendant to next enabled option (or first when none; the selection under `openHighlight="selected"`). |
785
+ | **ArrowUp** | Open listbox + move activedescendant to previous enabled option (or last when none; the selection under `openHighlight="selected"`). |
786
+ | **Home** _(open)_ | Move activedescendant to first enabled option. |
787
+ | **End** _(open)_ | Move activedescendant to last enabled option. |
788
+ | **PageUp** _(open)_ | Move activedescendant to first enabled option. |
789
+ | **PageDown** _(open)_ | Move activedescendant to last enabled option. |
790
+ | **Enter** _(open)_ | Activate the activedescendant (single: replace + close; multi: toggle + stay open). |
791
+ | **Escape** _(open)_ | Close the listbox. Focus stays in the input. |
792
+ | **Tab** _(open, no action)_ | Close the listbox and let Tab flow to the next focusable. |
793
+ | **Tab / Shift+Tab** _(open, action present)_ | Move focus around the input↔actions ring without dismissing (see [Action items](#action-items)). |
794
+ | **Backspace** _(empty input, multi only)_ | Focus the last chip; a second Backspace there removes it. |
795
+ | Printable keys | Update `query`. With `'inline'` / `'both'` autocomplete, complete the rest of the first match into the input as selected text. |
752
796
 
753
797
  Hovering an option also makes it the activedescendant, so mouse and keyboard intent stay synchronized.
754
798
 
@@ -760,6 +804,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
760
804
  - `role="listbox"` lives on `[forComboboxContent]` in the editable anatomy and on `[forComboboxList]` in the picker anatomy; the input's `aria-controls` targets whichever carries it. In the picker anatomy the popup surface (`[forComboboxContent]`) is role-less so it can hold the input next to the list without an `aria-required-owned-elements` violation.
761
805
  - `aria-multiselectable="true"` (multi mode) and the labelled role (`aria-label` / `aria-labelledby`, pointing at the input) sit on whichever element carries `role="listbox"`: content in the editable anatomy, the list in the picker anatomy.
762
806
  - `[forComboboxTrigger]` (picker anatomy) is a real `<button>` reflecting `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls` (the popup surface, while open), and native `disabled` from the combobox's effective disabled. It is exempt from the popup's outside-pointer dismissal layer, like the input.
807
+ - `[forComboboxToggle]` (editable anatomy) is a real `<button>` with `tabindex="-1"`, a localizable `aria-label`, `aria-expanded`, `aria-controls` (the listbox, while open) and native `disabled`. It cancels `mousedown` so focus stays in the input, and it is exempt from the outside-pointer dismissal layer.
763
808
  - In single mode, `aria-selected="true"` follows the activedescendant (the option Enter would activate). In multi mode it follows membership in `value()`, so every selected option carries `aria-selected="true"` simultaneously.
764
809
  - `data-state="checked" | "unchecked"` always reflects membership in `value()`, so consumers can paint a checkmark icon with pure CSS regardless of mode.
765
810
  - `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.
@@ -772,7 +817,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
772
817
 
773
818
  ## Styling
774
819
 
775
- forty-cdk ships no styles. Add your own class to each piece. The for\* selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected data-\* attributes listed under [Data attributes](#data-attributes).
820
+ forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [Data attributes](#data-attributes), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
776
821
 
777
822
  ### CSS custom properties
778
823
 
@@ -122,7 +122,7 @@ Same vetoable dismiss API as DropdownMenu. Call `preventDefault()` on the emitte
122
122
 
123
123
  ## Styling
124
124
 
125
- forty-cdk ships no styles. Add your own class to each piece. The for\* selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected data-\* attributes listed under [Data attributes](#data-attributes).
125
+ forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [Data attributes](#data-attributes), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
126
126
 
127
127
  > The menu content (`[forMenuContent]`, from the [`menu/`](../menu/README.md) folder) portals to `document.body`, so it sits outside the trigger's DOM subtree and descendant selectors won't reach it. Style it with **global CSS** or a class on the content element. The content host also exposes the shared positioner custom properties (`--for-floating-anchor-width` / `--for-floating-anchor-height`, `--for-floating-available-width` / `--for-floating-available-height`, `--for-floating-content-transform-origin`); see [Styling floating content](../../../docs/styling-floating-content.md) for the full list and the animation rules.
128
128
 
@@ -138,6 +138,7 @@ forty-cdk ships no styles. Add your own class to each piece. The for\* selectors
138
138
  - **Virtual anchor.** Right-click captures a 0×0 rect at the pointer location. `Shift+F10` and `ContextMenu` snapshot the bounding rect of the focused element (or the trigger if focus is on it directly), so the menu floats off the element under attention. Both forms feed floating-ui's `flip` and `shift` middleware, so corners and screen edges work without special-casing.
139
139
  - **Keyboard activators only fire while focus is inside the trigger.** Keyboard events dispatch to the focused element, so `Shift+F10` / `ContextMenu` anywhere outside the trigger goes to the browser default. The trigger is focusable by default (host-bound `tabindex="-1"`), so this works out of the box and focus can return to it programmatically when the menu closes. Your own `tabindex` wins over that default: use `tabindex="0"` if you want the region itself reachable via Tab.
140
140
  - **Native menu suppressed.** The trigger calls `event.preventDefault()` on `contextmenu` and on the keyboard activators. Set `disabled` to let the browser's native menu surface for that region.
141
+ - **An open that renders nothing closes again.** When a right-click, `Shift+F10`, the `ContextMenu` key or a long-press opens a menu whose template renders no `[forMenuContent]`, the trigger closes it after the next render with reason `'programmatic'`, and dev mode logs `FORCDK-CONTEXT-MENU-002` once. The native menu cannot come back for that gesture, because it was already suppressed, so bind `[disabled]` to the same condition that renders the content: `[disabled]="!hasActions(col)"` beside `@if (hasActions(col))`.
141
142
  - **Touch long-press.** The trigger runs its own long-press timer (a `touch` `pointerdown` held ~500 ms, without lifting or moving past a small tolerance, opens the menu at the touch point). This is required because iOS Safari never fires the `contextmenu` event a long-press synthesizes elsewhere; where the browser does synthesize it (Android, desktop touch emulation) the two paths stay mutually exclusive, so the menu opens exactly once. For the press to survive on iOS, suppress the native callout / text-selection on the trigger with CSS, since otherwise the OS gesture cancels the press:
142
143
 
143
144
  ```css
@@ -201,4 +202,4 @@ Both triggers carry `[menuPositioning]`, a partial `{ side, align, sideOffset, a
201
202
 
202
203
  ## Wrapping in a design system
203
204
 
204
- Subclassing the root is the supported pattern; the subclass must re-provide `FOR_MENU_CONTEXT` because Angular does not inherit a directive's `providers`, and every projected piece resolves its context through it. See [Wrapping non-form roots](../../../docs/wrapping-non-form-roots.md).
205
+ 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.
@@ -149,17 +149,17 @@ With a time-capable adapter, a `granularity` coarser than `'day'` appends time s
149
149
 
150
150
  ### `ForDateField`
151
151
 
152
- | Property | Type | Description |
153
- | ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
154
- | `value` | `model<D \| null>` | Two-way bindable entered date, or `null` while any segment is empty. The `FormValueControl` backing.<br>**Default:** `null` |
155
- | `minDate` | `input<D \| null>` | Minimum date (inclusive). A composed value below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
156
- | `maxDate` | `input<D \| null>` | Maximum date (inclusive). A composed value above it is clamped down.<br>**Default:** `null` |
157
- | `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` is date-only; coarser-than-day appends time segments. See below.<br>**Default:** `'day'` |
158
- | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
159
- | `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null` |
160
- | `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty. Unspecified parts fall back to `dd` / `mm` / `yyyy` / `hh` / `mm` / `ss` / `--`.<br>**Default:** `{}` |
161
- | `ariaLabel` | `input<string \| null>` | Accessible name for the group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
162
- | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
152
+ | Property | Type | Description |
153
+ | ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
154
+ | `value` | `model<D \| null>` | Two-way bindable entered date, or `null` while any segment is empty. The `FormValueControl` backing.<br>**Default:** `null` |
155
+ | `minDate` | `input<D \| null>` | Minimum date (inclusive). A composed value below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
156
+ | `maxDate` | `input<D \| null>` | Maximum date (inclusive). A composed value above it is clamped down.<br>**Default:** `null` |
157
+ | `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` is date-only; coarser-than-day appends time segments. See below.<br>**Default:** `'day'` |
158
+ | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → the scope's `hourCycle`, then the locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
159
+ | `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → the adapter's `locale()`, then the runtime locale.<br>**Default:** `null` |
160
+ | `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty. Unspecified parts fall back to the scope's `placeholder`, then to `dd` / `mm` / `yyyy` / `hh` / `mm` / `ss` / `--`.<br>**Default:** `{}` |
161
+ | `ariaLabel` | `input<string \| null>` | Accessible name for the group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
162
+ | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
163
163
 
164
164
  Plus the shared `FormUiControl` members from `@angular/forms/signals`: `disabled`, `readonly`, `required`, `invalid`, `name`, `errors`, `touched` (bound automatically by `[formField]`).
165
165
 
@@ -207,18 +207,25 @@ On the AM/PM segment, `a` / `p` set the period and ArrowUp / ArrowDown toggle it
207
207
  ```ts
208
208
  import { provideForDateFieldDefaults } from 'forty-cdk/date-field';
209
209
 
210
- // app config or a component's providers — localize segment labels and the
211
- // empty-segment announcement for every nested [forDateField].
210
+ // app config or a component's providers — localize segment labels, the
211
+ // empty-segment announcement and the placeholders for every nested [forDateField].
212
212
  providers: [
213
213
  provideForDateFieldDefaults({
214
214
  emptySegmentText: 'Vacío',
215
215
  segmentLabels: { day: 'día', month: 'mes', year: 'año', dayPeriod: 'AM/PM' },
216
+ placeholder: { day: 'dd', month: 'mm', year: 'aaaa' },
216
217
  }),
217
218
  ];
218
219
  ```
219
220
 
220
221
  `segmentLabels` supplies each segment's default `aria-label`, keyed by part type. Unset keys keep the library default (the part name, and `'AM/PM'` for the `dayPeriod` segment), so overriding a single key never wipes the rest. A segment's own `[ariaLabel]` still wins over the scope default.
221
222
 
223
+ `placeholder` is the text an empty segment shows, keyed the same way. A field's own `[placeholder]` wins for the parts it names and only those, and a part neither names keeps the letter-repeat default. `provideForDateRangeFieldDefaults` takes the same keys for `[forDateRangeField]`.
224
+
225
+ `hourCycle` sets the 12- or 24-hour cycle of every field that doesn't bind `[hourCycle]`, so a product on a 24-hour clock sets it once instead of on each field. Its fallback `null` derives the cycle from the locale.
226
+
227
+ For a language the app sets or switches after bootstrap, pass the text keys as functions and the overrides as a factory, as [Localizing default text](../shared/README.md#localizing-default-text) shows.
228
+
222
229
  ## Range selection — `ForDateRangeField`
223
230
 
224
231
  For a date range use the dedicated `ForDateRangeField` root (selector `[forDateRangeField]`), shipped from this same entry point. It is the keyboard-first, form-capable counterpart to [DateRangePicker](../date-picker/README.md): two labelled `role="group"` endpoints (start / end) nested inside one outer `role="group"`. Each endpoint holds a row of spinbutton segments built on the same machinery as `ForDateField`. It implements `FormValueControl<DateRange<D> | null>`, the **same** contract as `ForDateRangePicker`, so the committed range auto-wires with `[formField]`. The value stays `null` until **both** endpoints are fully entered and ordered (`start <= end`).
@@ -263,17 +270,17 @@ readonly booking = form(this.model);
263
270
 
264
271
  ### `ForDateRangeField` API
265
272
 
266
- | Property | Type | Description |
267
- | ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
268
- | `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null` |
269
- | `minDate` | `input<D \| null>` | Minimum date (inclusive) for both endpoints. A composed endpoint below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
270
- | `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
271
- | `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision shared by both endpoints. `'day'` is date-only; coarser-than-day appends time segments.<br>**Default:** `'day'` |
272
- | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
273
- | `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null` |
274
- | `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints.<br>**Default:** `{}` |
275
- | `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
276
- | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
273
+ | Property | Type | Description |
274
+ | ------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
275
+ | `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null` |
276
+ | `minDate` | `input<D \| null>` | Minimum date (inclusive) for both endpoints. A composed endpoint below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
277
+ | `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
278
+ | `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision shared by both endpoints. `'day'` is date-only; coarser-than-day appends time segments.<br>**Default:** `'day'` |
279
+ | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → the scope's `hourCycle`, then the locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
280
+ | `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → the adapter's `locale()`, then the runtime locale.<br>**Default:** `null` |
281
+ | `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints. Unspecified parts fall back to the scope's `placeholder`.<br>**Default:** `{}` |
282
+ | `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
283
+ | `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
277
284
 
278
285
  The endpoint groups each accept an `ariaLabel` input for their own group label, falling back to the scope defaults (`'Start date'` / `'End date'`). Plus the shared `FormUiControl` members bound automatically by `[formField]`.
279
286
 
@@ -323,7 +330,7 @@ Composes the [WAI-ARIA Spinbutton pattern](https://www.w3.org/WAI/ARIA/apg/patte
323
330
 
324
331
  The library is styleless, so style the boolean `data-*` hooks yourself: `[data-highlighted]` (the focused/roving segment), `[data-placeholder]` (empty), `[data-disabled]` and `[data-readonly]` on the segments, and `[data-empty]` / `[data-disabled]` / `[data-readonly]` on the root group.
325
332
 
326
- forty-cdk ships no styles. Add your own class to each piece. The for\* selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected data-\* attributes listed under [Data attributes](#data-attributes).
333
+ forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [Data attributes](#data-attributes), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
327
334
 
328
335
  ```css
329
336
  .date-field-segment[data-placeholder] {