forty-cdk 0.2.0 → 0.4.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 (220) hide show
  1. package/accordion/README.md +122 -0
  2. package/aspect-ratio/README.md +76 -0
  3. package/avatar/README.md +100 -0
  4. package/breadcrumbs/README.md +49 -0
  5. package/breakpoints/README.md +81 -0
  6. package/button/README.md +49 -0
  7. package/calendar/README.md +458 -0
  8. package/carousel/README.md +358 -0
  9. package/checkbox/README.md +146 -0
  10. package/combobox/README.md +535 -0
  11. package/context-menu/README.md +139 -0
  12. package/date-field/README.md +184 -0
  13. package/date-picker/README.md +338 -0
  14. package/dialog/README.md +388 -0
  15. package/disclosure/README.md +114 -0
  16. package/drag-drop/README.md +359 -0
  17. package/drawer/README.md +560 -0
  18. package/dropdown-menu/README.md +176 -0
  19. package/fesm2022/forty-cdk-accordion.mjs +348 -0
  20. package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
  21. package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
  22. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
  23. package/fesm2022/forty-cdk-avatar.mjs +308 -0
  24. package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
  25. package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
  26. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
  27. package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
  28. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
  29. package/fesm2022/forty-cdk-button.mjs +134 -0
  30. package/fesm2022/forty-cdk-button.mjs.map +1 -0
  31. package/fesm2022/forty-cdk-calendar.mjs +2034 -0
  32. package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-carousel.mjs +968 -0
  34. package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
  35. package/fesm2022/forty-cdk-checkbox.mjs +226 -0
  36. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
  37. package/fesm2022/forty-cdk-combobox.mjs +2596 -0
  38. package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-context-menu.mjs +413 -0
  40. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
  41. package/fesm2022/forty-cdk-core.mjs +9055 -0
  42. package/fesm2022/forty-cdk-core.mjs.map +1 -0
  43. package/fesm2022/forty-cdk-date-field.mjs +744 -0
  44. package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
  45. package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
  46. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
  47. package/fesm2022/forty-cdk-dialog.mjs +707 -0
  48. package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-disclosure.mjs +190 -0
  50. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
  51. package/fesm2022/forty-cdk-drag-drop.mjs +1188 -0
  52. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
  53. package/fesm2022/forty-cdk-drawer.mjs +1641 -0
  54. package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
  55. package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
  56. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
  57. package/fesm2022/forty-cdk-field.mjs +425 -0
  58. package/fesm2022/forty-cdk-field.mjs.map +1 -0
  59. package/fesm2022/forty-cdk-fieldset.mjs +164 -0
  60. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
  61. package/fesm2022/forty-cdk-file-upload.mjs +221 -0
  62. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
  63. package/fesm2022/forty-cdk-hover-card.mjs +496 -0
  64. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
  65. package/fesm2022/forty-cdk-input.mjs +274 -0
  66. package/fesm2022/forty-cdk-input.mjs.map +1 -0
  67. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  68. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-listbox.mjs +1268 -0
  70. package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
  71. package/fesm2022/forty-cdk-menu.mjs +1439 -0
  72. package/fesm2022/forty-cdk-menu.mjs.map +1 -0
  73. package/fesm2022/forty-cdk-menubar.mjs +787 -0
  74. package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
  75. package/fesm2022/forty-cdk-meter.mjs +211 -0
  76. package/fesm2022/forty-cdk-meter.mjs.map +1 -0
  77. package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
  78. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
  79. package/fesm2022/forty-cdk-number-input.mjs +559 -0
  80. package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
  81. package/fesm2022/forty-cdk-otp-input.mjs +527 -0
  82. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
  83. package/fesm2022/forty-cdk-pagination.mjs +323 -0
  84. package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
  85. package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
  86. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
  87. package/fesm2022/forty-cdk-popover.mjs +698 -0
  88. package/fesm2022/forty-cdk-popover.mjs.map +1 -0
  89. package/fesm2022/forty-cdk-progress.mjs +226 -0
  90. package/fesm2022/forty-cdk-progress.mjs.map +1 -0
  91. package/fesm2022/forty-cdk-radio-group.mjs +378 -0
  92. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
  94. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
  95. package/fesm2022/forty-cdk-search.mjs +205 -0
  96. package/fesm2022/forty-cdk-search.mjs.map +1 -0
  97. package/fesm2022/forty-cdk-select.mjs +1661 -0
  98. package/fesm2022/forty-cdk-select.mjs.map +1 -0
  99. package/fesm2022/forty-cdk-separator.mjs +82 -0
  100. package/fesm2022/forty-cdk-separator.mjs.map +1 -0
  101. package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
  102. package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
  103. package/fesm2022/forty-cdk-slider.mjs +803 -0
  104. package/fesm2022/forty-cdk-slider.mjs.map +1 -0
  105. package/fesm2022/forty-cdk-stepper.mjs +886 -0
  106. package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
  107. package/fesm2022/forty-cdk-switch.mjs +137 -0
  108. package/fesm2022/forty-cdk-switch.mjs.map +1 -0
  109. package/fesm2022/forty-cdk-table.mjs +1526 -0
  110. package/fesm2022/forty-cdk-table.mjs.map +1 -0
  111. package/fesm2022/forty-cdk-tabs.mjs +400 -0
  112. package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
  113. package/fesm2022/forty-cdk-time-field.mjs +593 -0
  114. package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
  115. package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
  116. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
  117. package/fesm2022/forty-cdk-toast.mjs +1153 -0
  118. package/fesm2022/forty-cdk-toast.mjs.map +1 -0
  119. package/fesm2022/forty-cdk-toggle.mjs +516 -0
  120. package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
  121. package/fesm2022/forty-cdk-toolbar.mjs +374 -0
  122. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
  123. package/fesm2022/forty-cdk-tooltip.mjs +672 -0
  124. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
  125. package/fesm2022/forty-cdk-tree.mjs +1999 -0
  126. package/fesm2022/forty-cdk-tree.mjs.map +1 -0
  127. package/fesm2022/forty-cdk-virtualization.mjs +1 -1
  128. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  129. package/fesm2022/forty-cdk.mjs +0 -43310
  130. package/fesm2022/forty-cdk.mjs.map +1 -1
  131. package/field/README.md +97 -0
  132. package/fieldset/README.md +86 -0
  133. package/file-upload/README.md +73 -0
  134. package/hover-card/README.md +171 -0
  135. package/input/README.md +156 -0
  136. package/listbox/README.md +448 -0
  137. package/menu/README.md +181 -0
  138. package/menubar/README.md +140 -0
  139. package/meter/README.md +128 -0
  140. package/navigation-menu/README.md +253 -0
  141. package/number-input/README.md +171 -0
  142. package/otp-input/README.md +198 -0
  143. package/package.json +213 -1
  144. package/pagination/README.md +61 -0
  145. package/pane-resizer/README.md +136 -0
  146. package/popover/README.md +262 -0
  147. package/progress/README.md +115 -0
  148. package/radio-group/README.md +129 -0
  149. package/scroll-area/README.md +184 -0
  150. package/search/README.md +42 -0
  151. package/select/README.md +488 -0
  152. package/separator/README.md +84 -0
  153. package/signal-forms/README.md +72 -0
  154. package/slider/README.md +152 -0
  155. package/stepper/README.md +292 -0
  156. package/switch/README.md +116 -0
  157. package/table/README.md +769 -0
  158. package/tabs/README.md +130 -0
  159. package/time-field/README.md +157 -0
  160. package/time-picker/README.md +172 -0
  161. package/toast/README.md +398 -0
  162. package/toggle/README.md +224 -0
  163. package/toolbar/README.md +109 -0
  164. package/tooltip/README.md +274 -0
  165. package/tree/README.md +735 -0
  166. package/types/forty-cdk-accordion.d.ts +242 -0
  167. package/types/forty-cdk-aspect-ratio.d.ts +59 -0
  168. package/types/forty-cdk-avatar.d.ts +133 -0
  169. package/types/forty-cdk-breadcrumbs.d.ts +92 -0
  170. package/types/forty-cdk-breakpoints.d.ts +141 -0
  171. package/types/forty-cdk-button.d.ts +80 -0
  172. package/types/forty-cdk-calendar.d.ts +914 -0
  173. package/types/forty-cdk-carousel.d.ts +530 -0
  174. package/types/forty-cdk-checkbox.d.ts +141 -0
  175. package/types/forty-cdk-combobox.d.ts +1259 -0
  176. package/types/forty-cdk-context-menu.d.ts +313 -0
  177. package/types/forty-cdk-core.d.ts +5800 -0
  178. package/types/forty-cdk-date-field.d.ts +307 -0
  179. package/types/forty-cdk-date-picker.d.ts +622 -0
  180. package/types/forty-cdk-dialog.d.ts +546 -0
  181. package/types/forty-cdk-disclosure.d.ts +127 -0
  182. package/types/forty-cdk-drag-drop.d.ts +456 -0
  183. package/types/forty-cdk-drawer.d.ts +871 -0
  184. package/types/forty-cdk-dropdown-menu.d.ts +242 -0
  185. package/types/forty-cdk-field.d.ts +236 -0
  186. package/types/forty-cdk-fieldset.d.ts +119 -0
  187. package/types/forty-cdk-file-upload.d.ts +124 -0
  188. package/types/forty-cdk-hover-card.d.ts +320 -0
  189. package/types/forty-cdk-input.d.ts +169 -0
  190. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  191. package/types/forty-cdk-listbox.d.ts +533 -0
  192. package/types/forty-cdk-menu.d.ts +629 -0
  193. package/types/forty-cdk-menubar.d.ts +451 -0
  194. package/types/forty-cdk-meter.d.ts +122 -0
  195. package/types/forty-cdk-navigation-menu.d.ts +514 -0
  196. package/types/forty-cdk-number-input.d.ts +319 -0
  197. package/types/forty-cdk-otp-input.d.ts +248 -0
  198. package/types/forty-cdk-pagination.d.ts +214 -0
  199. package/types/forty-cdk-pane-resizer.d.ts +145 -0
  200. package/types/forty-cdk-popover.d.ts +509 -0
  201. package/types/forty-cdk-progress.d.ts +143 -0
  202. package/types/forty-cdk-radio-group.d.ts +222 -0
  203. package/types/forty-cdk-scroll-area.d.ts +258 -0
  204. package/types/forty-cdk-search.d.ts +142 -0
  205. package/types/forty-cdk-select.d.ts +899 -0
  206. package/types/forty-cdk-separator.d.ts +59 -0
  207. package/types/forty-cdk-signal-forms.d.ts +58 -0
  208. package/types/forty-cdk-slider.d.ts +379 -0
  209. package/types/forty-cdk-stepper.d.ts +650 -0
  210. package/types/forty-cdk-switch.d.ts +87 -0
  211. package/types/forty-cdk-table.d.ts +730 -0
  212. package/types/forty-cdk-tabs.d.ts +235 -0
  213. package/types/forty-cdk-time-field.d.ts +307 -0
  214. package/types/forty-cdk-time-picker.d.ts +578 -0
  215. package/types/forty-cdk-toast.d.ts +598 -0
  216. package/types/forty-cdk-toggle.d.ts +310 -0
  217. package/types/forty-cdk-toolbar.d.ts +217 -0
  218. package/types/forty-cdk-tooltip.d.ts +436 -0
  219. package/types/forty-cdk-tree.d.ts +716 -0
  220. package/types/forty-cdk.d.ts +1 -19743
@@ -0,0 +1,122 @@
1
+ # Accordion
2
+
3
+ Headless implementation of the [WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).
4
+ A vertical stack of collapsible sections, each with a header button and a panel.
5
+
6
+ ## Pieces
7
+
8
+ | Class | Selector | Role |
9
+ | --------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------- |
10
+ | `ForAccordion` | `[forAccordion]` | Root. Owns the open `value`, the single/multiple mode, and the keyboard navigation between triggers. |
11
+ | `ForAccordionItem` | `[forAccordionItem]` | One section. Requires a unique `value` string. |
12
+ | `ForAccordionTrigger` | `[forAccordionTrigger]` | Header button. Wires ARIA + click + keyboard. |
13
+ | `ForAccordionContent` | `[forAccordionContent]` | Panel. Adds `role="region"` + `aria-labelledby` automatically. |
14
+
15
+ ## Inputs / outputs
16
+
17
+ ### `ForAccordion`
18
+
19
+ | API | Type | Description |
20
+ | ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
21
+ | `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element. |
22
+ | `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously. Defaults to `false`. |
23
+ | `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Defaults to `false` — once any item is open, exactly one stays open. |
24
+ | `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction of the trigger list. Defaults to `'vertical'`; in horizontal mode ArrowLeft/Right replace ArrowUp/Down. |
25
+ | `dir` | `input<'ltr' \| 'rtl'>` | Writing direction. Only relevant in horizontal mode — swaps the meaning of Left/Right arrows. |
26
+
27
+ ### `ForAccordionItem`
28
+
29
+ | API | Type | Description |
30
+ | ---------- | ------------------------ | ---------------------------------------------------------------------------------- |
31
+ | `value` | `input.required<string>` | Unique identifier within the accordion. Required. |
32
+ | `disabled` | `input<boolean>` | When true, the trigger ignores clicks and exposes the native `disabled` attribute. |
33
+
34
+ The host gets `data-state="open" \| "closed"` and `data-disabled` for CSS hooks.
35
+
36
+ ### `ForAccordionTrigger`
37
+
38
+ Reflects on its host: `id`, `aria-expanded`, `aria-controls`, `aria-disabled` (when collapse is disallowed), `disabled` (real, when item is disabled), `data-state`. Toggles on click. Handles `ArrowDown` / `ArrowUp` / `Home` / `End` for navigation between triggers.
39
+
40
+ `aria-controls` is emitted only while the item is expanded — mirroring the overlay triggers' open-only gating — so the reference never dangles at an unmounted panel under the recommended `@if (item.expanded())` mount pattern.
41
+
42
+ Wrap it in a heading element (`<h2>`–`<h6>`) — APG requires that for landmark navigation. Use a real `<button type="button">` so Enter / Space activation comes for free.
43
+
44
+ ### `ForAccordionContent`
45
+
46
+ Reflects on its host: `id`, `role="region"`, `aria-labelledby` (the trigger's id), `data-state`, `aria-hidden` (when closed), `inert` (when closed).
47
+
48
+ The directive does **not** apply `[hidden]`. Two patterns work:
49
+
50
+ - **Mount/unmount with `@if (item.expanded())`** — the panel is absent from the DOM while closed, which is the cleanest path for `animate.enter` / `animate.leave`.
51
+ - **Leave it mounted** — preserve internal state or run CSS-only transitions off `data-state`. While closed, the directive sets `aria-hidden="true"` and `inert` on the host so the panel is removed from the accessibility tree and focus order. Add `display: none` (or your own collapse animation) keyed on `[data-state="closed"]` to also hide it visually.
52
+
53
+ ## Example
54
+
55
+ ```ts
56
+ import { Component, signal } from '@angular/core';
57
+ import {
58
+ ForAccordion,
59
+ ForAccordionContent,
60
+ ForAccordionItem,
61
+ ForAccordionTrigger,
62
+ } from 'forty-cdk/accordion';
63
+
64
+ @Component({
65
+ selector: 'demo-faq',
66
+ imports: [ForAccordion, ForAccordionItem, ForAccordionTrigger, ForAccordionContent],
67
+ template: `
68
+ <div forAccordion [(value)]="open" collapsible>
69
+ <div forAccordionItem value="shipping">
70
+ <h3>
71
+ <button type="button" forAccordionTrigger class="accordion-trigger">Shipping</button>
72
+ </h3>
73
+ <section forAccordionContent>Ships in 24h.</section>
74
+ </div>
75
+ <div forAccordionItem value="returns">
76
+ <h3>
77
+ <button type="button" forAccordionTrigger class="accordion-trigger">Returns</button>
78
+ </h3>
79
+ <section forAccordionContent>Free 30-day returns.</section>
80
+ </div>
81
+ </div>
82
+ `,
83
+ })
84
+ export class DemoFaq {
85
+ readonly open = signal<readonly string[]>([]);
86
+ }
87
+ ```
88
+
89
+ ## Styling
90
+
91
+ 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 below.
92
+
93
+ ### Data attributes
94
+
95
+ | Piece | Attribute | Values |
96
+ | ----------------------- | ------------------ | -------------------------- |
97
+ | `[forAccordion]` | `data-orientation` | `horizontal` \| `vertical` |
98
+ | `[forAccordionItem]` | `data-state` | `open` \| `closed` |
99
+ | `[forAccordionItem]` | `data-disabled` | present \| absent |
100
+ | `[forAccordionItem]` | `data-orientation` | `horizontal` \| `vertical` |
101
+ | `[forAccordionTrigger]` | `data-state` | `open` \| `closed` |
102
+ | `[forAccordionTrigger]` | `data-orientation` | `horizontal` \| `vertical` |
103
+ | `[forAccordionContent]` | `data-state` | `open` \| `closed` |
104
+ | `[forAccordionContent]` | `data-orientation` | `horizontal` \| `vertical` |
105
+
106
+ ```css
107
+ .trigger-chevron {
108
+ transition: transform 150ms ease;
109
+ }
110
+
111
+ .accordion-trigger[data-state='open'] .trigger-chevron {
112
+ transform: rotate(180deg);
113
+ }
114
+ ```
115
+
116
+ ## Accessibility notes
117
+
118
+ - **Heading wrapper is your job.** The library does not render a heading around the trigger — wrap it in the heading level appropriate to your document outline. Without it, screen-reader landmark navigation is broken.
119
+ - **`role="region"`** is added to every panel automatically. APG recommends suppressing it on accordions with 6+ panels to avoid landmark proliferation. An opt-out input will be added to `ForAccordionContent` if this surfaces in real usage.
120
+ - **Keyboard**: Enter and Space toggle the focused trigger (native button). ArrowDown / ArrowUp (vertical, default) or ArrowLeft / ArrowRight (horizontal — flipped under `dir='rtl'`) move focus between triggers (wrap-around, skip disabled). Home / End jump to the first/last trigger.
121
+ - **`aria-disabled`** is applied to the open trigger only when single mode is active and `collapsible=false`, indicating the user cannot collapse it from this trigger.
122
+ - **A truly disabled item (`[disabled]` on `[forAccordionItem]`) uses the native `disabled` attribute on the trigger, by design.** This is the sanctioned exception in [rule #561](https://github.com/tutkli/forty-cdk/issues/561): the trigger is a real single-purpose `<button>`, not a roving-tabindex collection item (each trigger stays independently in the Tab order; arrow-key navigation is the APG-optional enhancement on top). The disabled trigger leaves the Tab order and the arrow-key navigation (which already skips it), but stays in the accessibility tree so screen readers announce it as unavailable in browse mode. The [APG Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) does not require disabled headers to remain focusable.
@@ -0,0 +1,76 @@
1
+ # AspectRatio
2
+
3
+ Pure visual utility — locks an element's box to a fixed `width / height` ratio via the native CSS `aspect-ratio` property.
4
+
5
+ No ARIA semantics. Use it to reserve space for media before it loads (preventing layout shift), to keep cards on a grid uniform, or to wrap responsive iframes.
6
+
7
+ ## Pieces
8
+
9
+ | Class | Selector | Role |
10
+ | ---------------- | ------------------ | --------------------------------------------------------- |
11
+ | `ForAspectRatio` | `[forAspectRatio]` | Single attribute directive. Applies `style.aspect-ratio`. |
12
+
13
+ ## Inputs
14
+
15
+ | API | Type | Description |
16
+ | ------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `ratio` | `input<number>` | Width / height ratio (e.g. `16 / 9`, `4 / 3`, `1`). Accepts both numeric expressions and string attributes. Defaults to `1`. Non-positive or non-finite values fall back to `1`. |
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { Component } from '@angular/core';
23
+ import { ForAspectRatio } from 'forty-cdk/aspect-ratio';
24
+
25
+ @Component({
26
+ selector: 'demo-aspect-ratio',
27
+ imports: [ForAspectRatio],
28
+ template: `
29
+ <div forAspectRatio [ratio]="16 / 9" class="card-cover">
30
+ <img src="cover.jpg" alt="" />
31
+ </div>
32
+
33
+ <div forAspectRatio ratio="1" class="avatar">
34
+ <img src="me.jpg" alt="Me" />
35
+ </div>
36
+
37
+ <div forAspectRatio [ratio]="21 / 9" class="hero">
38
+ <video src="hero.mp4" autoplay loop muted></video>
39
+ </div>
40
+ `,
41
+ styles: [
42
+ `
43
+ .card-cover,
44
+ .avatar,
45
+ .hero {
46
+ width: 100%;
47
+ }
48
+ .card-cover img,
49
+ .avatar img,
50
+ .hero video {
51
+ width: 100%;
52
+ height: 100%;
53
+ object-fit: cover;
54
+ }
55
+ `,
56
+ ],
57
+ })
58
+ export class DemoAspectRatio {}
59
+ ```
60
+
61
+ ## Notes
62
+
63
+ - **Browser support.** Native `aspect-ratio` is in Baseline 2021 (Chrome 88+, Firefox 89+, Safari 15+) — same target as Angular 20+, so no polyfill is needed.
64
+ - **Width still on you.** The directive only sets `aspect-ratio`; you decide width / max-width / display. The height is computed from the ratio.
65
+ - **Children fill the box.** Use `width: 100%; height: 100%; object-fit: cover` on inner media to fill without distortion. The directive imposes no styles on children.
66
+ - **No role, no a11y.** This is a layout utility. The element it sits on keeps whatever semantics you give it (`<div>`, `<figure>`, `<a>`, …).
67
+
68
+ ## Styling
69
+
70
+ 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]`.
71
+
72
+ ### Data attributes
73
+
74
+ | Piece | Attribute | Values |
75
+ | ------------------ | --------- | ------ |
76
+ | `[forAspectRatio]` | _(none)_ | — |
@@ -0,0 +1,100 @@
1
+ # Avatar
2
+
3
+ Headless avatar that tracks the load lifecycle of an `<img>` and lets the consumer choose what to show during loading or after an error.
4
+
5
+ There is no WAI-ARIA pattern for "avatar" — it is a presentational composition. The directive does not impose a `role`; pair the avatar with visible name text or `aria-label` on the surrounding element when identity matters.
6
+
7
+ ## Pieces
8
+
9
+ | Class | Selector | Role |
10
+ | ------------------- | --------------------- | ---------------------------------------------------------------------- |
11
+ | `ForAvatar` | `[forAvatar]` | Root. Owns `status`, `shouldShowFallback`, and `fallbackDelayMs`. |
12
+ | `ForAvatarImage` | `img[forAvatarImage]` | Observes the `<img>` and reports `idle \| loading \| loaded \| error`. |
13
+ | `ForAvatarFallback` | `[forAvatarFallback]` | Marker for fallback content. Reflects `data-status`. |
14
+
15
+ ## Inputs / outputs / models
16
+
17
+ | API | Type | Owner | Description |
18
+ | --------------------- | ------------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
19
+ | `fallbackDelayMs` | `input<number>` | `ForAvatar` | ms to wait before `shouldShowFallback()` flips to `true` while idle/loading. Default `0`. |
20
+ | `status` | `Signal<ForAvatarStatus>` | `ForAvatar` | Read-only current status. |
21
+ | `shouldShowFallback` | `Signal<boolean>` | `ForAvatar` | `true` when the consumer should render the fallback. Drives `@if`. |
22
+ | `(loadStatusChanged)` | `output<ForAvatarStatus>` | `ForAvatarImage` | Emits whenever the lifecycle transitions. |
23
+
24
+ The host element of every piece carries `data-status="idle" \| "loading" \| "loaded" \| "error"`.
25
+
26
+ ## Usage
27
+
28
+ ```ts
29
+ import { Component, signal } from '@angular/core';
30
+ import { ForAvatar, ForAvatarFallback, ForAvatarImage } from 'forty-cdk/avatar';
31
+
32
+ @Component({
33
+ selector: 'demo-avatar',
34
+ imports: [ForAvatar, ForAvatarImage, ForAvatarFallback],
35
+ template: `
36
+ <span forAvatar #a="forAvatar" class="avatar" fallbackDelayMs="500">
37
+ <img forAvatarImage class="avatar-image" [src]="user.avatarUrl" [alt]="user.name" />
38
+ @if (a.shouldShowFallback()) {
39
+ <span forAvatarFallback class="avatar-fallback">{{ initials() }}</span>
40
+ }
41
+ </span>
42
+ `,
43
+ styles: [
44
+ `
45
+ .avatar {
46
+ display: inline-flex;
47
+ width: 40px;
48
+ height: 40px;
49
+ border-radius: 999px;
50
+ overflow: hidden;
51
+ background: #eee;
52
+ font: 600 14px/40px system-ui;
53
+ align-items: center;
54
+ justify-content: center;
55
+ }
56
+ .avatar-image {
57
+ width: 100%;
58
+ height: 100%;
59
+ object-fit: cover;
60
+ }
61
+ .avatar-image[data-status='loading'],
62
+ .avatar-image[data-status='error'] {
63
+ display: none;
64
+ }
65
+ `,
66
+ ],
67
+ })
68
+ export class DemoAvatar {
69
+ readonly user = { name: 'Ada Lovelace', avatarUrl: '/api/avatar/ada.jpg' };
70
+ readonly initials = signal('AL');
71
+ }
72
+ ```
73
+
74
+ ## Notes
75
+
76
+ - **Cached images are detected on first render.** If the browser already has the image cached, `load`/`error` may not fire — the directive checks `<img>.complete` and `naturalWidth` after the first render and reports `loaded` / `error` accordingly. A cached image that is `complete` but has zero intrinsic width (e.g. an SVG without explicit dimensions) is ambiguous, so the directive stays `loading` and confirms validity with `img.decode()` rather than pessimistically flagging `error`.
77
+ - **Multiple images per avatar are not supported.** Each `[forAvatar]` expects exactly one `[forAvatarImage]`. If you need cascading sources (CDN → fallback URL → fallback content), swap `src` on a single image.
78
+ - **`alt` is consumer territory.** Because `<img>` is the host element, the consumer keeps full control of `alt` — set `""` for purely decorative avatars next to a name, or describe the person if the avatar stands alone.
79
+ - **The image stays in the DOM.** Hide it via CSS `[data-status="loading"], [data-status="error"] { display: none }` if your consumer-side styling needs it gone. The fallback uses `@if`, so it only mounts when needed.
80
+
81
+ ## Styling
82
+
83
+ 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 below.
84
+
85
+ ### Data attributes
86
+
87
+ | Piece | Attribute | Values |
88
+ | --------------------- | ------------- | ------------------------------------------ |
89
+ | `[forAvatar]` | `data-status` | `idle` \| `loading` \| `loaded` \| `error` |
90
+ | `img[forAvatarImage]` | `data-status` | `idle` \| `loading` \| `loaded` \| `error` |
91
+ | `[forAvatarFallback]` | `data-status` | `idle` \| `loading` \| `loaded` \| `error` |
92
+
93
+ ```css
94
+ .avatar-image:not([data-status='loaded']) {
95
+ display: none;
96
+ }
97
+ .avatar-fallback[data-status='error'] {
98
+ color: #b00020;
99
+ }
100
+ ```
@@ -0,0 +1,49 @@
1
+ # Breadcrumbs
2
+
3
+ Headless breadcrumb trail implementing the [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/): a labelled `navigation` landmark wrapping a set of links, with [`aria-current="page"`](https://www.w3.org/TR/wai-aria-1.2/#aria-current) on the current page and decorative separators hidden from assistive technology. Ships no styles — apply your own.
4
+
5
+ ## Pieces
6
+
7
+ | Class | Selector | Role |
8
+ | ------------------------ | -------------------------- | ------------------------------------------------------------------------- |
9
+ | `ForBreadcrumbs` | `[forBreadcrumbs]` | Root. `role="navigation"`, labelled `aria-label="Breadcrumb"` by default. |
10
+ | `ForBreadcrumbItem` | `[forBreadcrumbItem]` | A link in the trail. Reflects `aria-current="page"` when `current`. |
11
+ | `ForBreadcrumbSeparator` | `[forBreadcrumbSeparator]` | Decorative divider between items. Reflects `aria-hidden="true"`. |
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { Component } from '@angular/core';
17
+ import { ForBreadcrumbItem, ForBreadcrumbSeparator, ForBreadcrumbs } from 'forty-cdk/breadcrumbs';
18
+
19
+ @Component({
20
+ selector: 'demo-breadcrumbs',
21
+ imports: [ForBreadcrumbs, ForBreadcrumbItem, ForBreadcrumbSeparator],
22
+ template: `
23
+ <nav forBreadcrumbs>
24
+ <ol>
25
+ <li><a forBreadcrumbItem href="/">Home</a></li>
26
+ <li forBreadcrumbSeparator>/</li>
27
+ <li><a forBreadcrumbItem href="/library">Library</a></li>
28
+ <li forBreadcrumbSeparator>/</li>
29
+ <li><a forBreadcrumbItem href="/library/data" current>Data</a></li>
30
+ </ol>
31
+ </nav>
32
+ `,
33
+ })
34
+ export class DemoBreadcrumbs {}
35
+ ```
36
+
37
+ The root defaults its label to `Breadcrumb`. Override it with `ariaLabel="…"` (or point a native `aria-labelledby` at a visible heading) when a page hosts more than one breadcrumb trail.
38
+
39
+ ## Styling
40
+
41
+ forty-cdk ships no styles. Style the current item via `[aria-current="page"]`.
42
+
43
+ ```css
44
+ [forBreadcrumbItem][aria-current='page'] {
45
+ font-weight: bold;
46
+ color: inherit;
47
+ text-decoration: none;
48
+ }
49
+ ```
@@ -0,0 +1,81 @@
1
+ # Breakpoints
2
+
3
+ A signal-first, zoneless, SSR-safe viewport breakpoint observer. Configure the breakpoint map **once** via a provider; read it anywhere with `injectBreakpoints()` — no need to repeat the breakpoint set at every call site.
4
+
5
+ It is a headless reactive utility, not a UI primitive: no DOM, no ARIA, no template. Each query method returns a `Signal<boolean>`.
6
+
7
+ ## Setup
8
+
9
+ 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:
10
+
11
+ ```ts
12
+ import { provideForBreakpoints } from 'forty-cdk/breakpoints';
13
+
14
+ export const appConfig: ApplicationConfig = {
15
+ providers: [provideForBreakpoints({ mobile: 0, tablet: 640, laptop: 1024, desktop: 1280 })],
16
+ };
17
+ ```
18
+
19
+ 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).
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { Component, inject } from '@angular/core';
25
+ import { injectBreakpoints } from 'forty-cdk/breakpoints';
26
+
27
+ @Component({
28
+ selector: 'app-layout',
29
+ template: `
30
+ @if (isDesktop()) {
31
+ <aside>Sidebar</aside>
32
+ }
33
+ <main>Active breakpoint: {{ active() }}</main>
34
+ `,
35
+ })
36
+ export class Layout {
37
+ private bp = injectBreakpoints();
38
+
39
+ protected isDesktop = this.bp.up('lg'); // (min-width: 1024px) and wider
40
+ protected active = this.bp.active; // 'sm' | 'md' | … | null
41
+ }
42
+ ```
43
+
44
+ | Method | Matches |
45
+ | ---------------- | ------------------------------------------------------------------------------ |
46
+ | `up(name)` | the breakpoint and wider — `(min-width: N px)` |
47
+ | `down(name)` | narrower than the breakpoint — `(max-width: (N − 0.02) px)` |
48
+ | `between(a, b)` | from `a` (inclusive) up to but not including `b` |
49
+ | `only(name)` | the breakpoint's own band, up to but not including the next-larger one |
50
+ | `active` | the largest breakpoint whose `min-width` matches, or `null` below the smallest |
51
+ | `matches(query)` | escape hatch for an arbitrary media query (orientation, `prefers-*`, …) |
52
+
53
+ The returned handle captures its injection context, so the query methods can be called lazily from a `computed()` or a template, not only during construction:
54
+
55
+ ```ts
56
+ protected columns = computed(() => (this.bp.up('xl')() ? 4 : this.bp.up('md')() ? 2 : 1));
57
+ ```
58
+
59
+ ## Typed custom names
60
+
61
+ The default map gives you fully-typed names out of the box (`up('md')` autocompletes; `up('foo')` is a type error). When you provide a custom map, recover the same typing by augmenting `BreakpointRegistry` once — derive the keys from your map so you never write them twice:
62
+
63
+ ```ts
64
+ // breakpoints.ts
65
+ export const appBreakpoints = {
66
+ mobile: 0,
67
+ tablet: 640,
68
+ laptop: 1024,
69
+ desktop: 1280,
70
+ } as const;
71
+
72
+ declare module 'forty-cdk' {
73
+ interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}
74
+ }
75
+ ```
76
+
77
+ Now `injectBreakpoints()` autocompletes `'mobile' | 'tablet' | 'laptop' | 'desktop'` across the whole app.
78
+
79
+ ## SSR
80
+
81
+ On the server (or where `matchMedia` is unavailable) every query signal reads `false` and `active` reads `null`. No `matchMedia` access happens server-side, so the helper is safe under Angular Universal.
@@ -0,0 +1,49 @@
1
+ # ForButton
2
+
3
+ Headless implementation of the [WAI-ARIA Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/).
4
+
5
+ A single `[forButton]` directive turns any element into an accessible, interactive button. It works on a native `<button>` host and on any non-button host (e.g. `<div>`, `<span>`). Install from `forty-cdk`.
6
+
7
+ ## Basic usage
8
+
9
+ ```html
10
+ <!-- Native button — platform handles Enter/Space → click synthesis -->
11
+ <button forButton (activate)="save()">Save</button>
12
+
13
+ <!-- Non-button host — role="button", tabindex="0", and keyboard activation added automatically -->
14
+ <div forButton (activate)="save()">Save</div>
15
+ ```
16
+
17
+ ## Disabled
18
+
19
+ Disabled buttons stay focusable so assistive technology can announce them. The native `disabled` attribute is never set; instead `aria-disabled="true"` is reflected.
20
+
21
+ ```html
22
+ <button forButton [disabled]="isSaving()" (activate)="save()">Save</button>
23
+ ```
24
+
25
+ ## Preserve consumer `type`
26
+
27
+ A native `<button>` without an explicit `type` attribute defaults to `type="button"`. A consumer-set `type="submit"` is preserved:
28
+
29
+ ```html
30
+ <button type="submit" forButton>Submit form</button>
31
+ ```
32
+
33
+ ## Data attributes
34
+
35
+ The directive reflects the following boolean `data-*` attributes (present with an empty-string value when true, absent when false). There is no `data-state` — this primitive has no open/closed or checked/unchecked logical state.
36
+
37
+ | Attribute | When present |
38
+ | -------------------- | ----------------------------------------------- |
39
+ | `data-disabled` | `[disabled]="true"` |
40
+ | `data-pressed` | Primary pointer held down, or Enter/Space held |
41
+ | `data-hovered` | Mouse/pen pointer is over the element |
42
+ | `data-focus-visible` | Focused via keyboard (keyboard modality active) |
43
+
44
+ ## API
45
+
46
+ | Member | Type | Default | Description |
47
+ | ---------- | ---------------- | ------- | -------------------------------------------------------------------------------- |
48
+ | `disabled` | `input<boolean>` | `false` | Suppresses activation and reflects `aria-disabled` + `data-disabled`. |
49
+ | `activate` | `output<void>` | — | Fires once per user activation (click, Enter, Space). Never fires when disabled. |