forty-cdk 0.0.2 → 0.0.4

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.
package/README.md CHANGED
@@ -1,343 +1,343 @@
1
- # forty-cdk
2
-
3
- Headless / styleless UI primitives for Angular with WAI-ARIA accessibility built in.
4
- Inspired by Radix UI and Base UI but reinterpreted idiomatically for modern Angular.
5
-
6
- **New here?** [Your first overlay](../../docs/your-first-overlay.md) walks one Popover from empty markup to styled-and-animated and explains the two concepts every overlay shares: the `@if` / open-state model and the portal → global CSS requirement.
7
-
8
- **Styling these primitives?** [Styling forty-cdk](../../docs/styling.md) explains the three hooks you style against — your own class (not the directive selector), `data-*` state attributes, and `--for-*` custom properties — and links to each primitive's styling reference.
9
-
10
- ## Installation
11
-
12
- ```bash
13
- npm install forty-cdk
14
- ```
15
-
16
- ### Peer dependencies
17
-
18
- Required:
19
-
20
- - `@angular/common` `^22.0.0`
21
- - `@angular/core` `^22.0.0`
22
-
23
- Optional — install only if you use the matching entry point / primitives:
24
-
25
- | Peer | Needed by |
26
- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | `@angular/forms` `^22.0.0` | Form-control primitives (`Switch`, `Checkbox`, `RadioGroup`, `Listbox`, `Select`, `Slider`, `Combobox`, …). They implement `FormValueControl` / `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring. The contract is type-only, so the published bundle never references the package — consumers using only non-form primitives can skip it. |
28
- | `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves only depend on the abstract `DateAdapter` contract from the main entry point — install this peer only when you import that entry point. |
29
-
30
- `@angular/forms/signals` is stable as of Angular 22, so the peer follows the standard major range (`^22.0.0`).
31
-
32
- ### Regular dependencies
33
-
34
- `@floating-ui/dom` is a regular dependency, installed automatically with the package. Positioned overlays (`Tooltip`, `Popover`, `Menu`, `Combobox`, `Select`, etc.) import it statically from the main entry point, so every consumer's build must be able to resolve it — but it is internal-only (no floating-ui value crosses the public API) and tree-shakes out of your bundle when you don't use any positioned primitive.
35
-
36
- ## Primitives
37
-
38
- Each primitive lives under [`src/lib/<primitive>/`](src/lib) with its own `README.md` and a minimal styleless usage example.
39
-
40
- The library ships one main entry point (`forty-cdk`) plus a single secondary entry point, `forty-cdk/internationalized-date`, which holds the `@internationalized/date` adapters so that optional peer stays truly optional. Standalone directives plus `"sideEffects": false` let tree-shakers drop primitives you don't import.
41
-
42
- ## Directive → host element matrix
43
-
44
- Quick reference for "which HTML element should I put this directive on?". Recommendations are derived from each primitive's WAI-ARIA pattern (e.g. focusable triggers as `<button type="button">`, the combobox input as a real `<input>` so caret/selection work) and from each primitive's README usage example. `any element` means the directive is element-agnostic — pick the tag that matches your semantics.
45
-
46
- Selectors marked with `(element)` use an element selector instead of an attribute selector; everything else is `[attribute]`. Form-control hosts (`forSwitch`, `forCheckbox`, `forRadio`, `forToggle`) deliberately render as `<button type="button">` — the directive forces `type="button"` and provides `role="switch"` / `"checkbox"` / `"radio"` / `aria-pressed` so the consumer keeps full keyboard, focus, and form-state behaviour without an `<input>` whose chrome can't be styled.
47
-
48
- ### Accordion
49
-
50
- | Selector | Host |
51
- | ----------------------- | ---------------------------------------------- |
52
- | `[forAccordion]` | `<div>` |
53
- | `[forAccordionItem]` | `<div>` |
54
- | `[forAccordionTrigger]` | `<button>` (wrapped in `<h2>`–`<h6>`, per APG) |
55
- | `[forAccordionContent]` | `<section>` |
56
-
57
- ### Aspect Ratio
58
-
59
- | Selector | Host |
60
- | ------------------ | ------- |
61
- | `[forAspectRatio]` | `<div>` |
62
-
63
- ### Avatar
64
-
65
- | Selector | Host |
66
- | --------------------- | ------------------------------ |
67
- | `[forAvatar]` | `<span>` |
68
- | `img[forAvatarImage]` | `<img>` (selector enforces it) |
69
- | `[forAvatarFallback]` | `<span>` |
70
-
71
- ### Checkbox
72
-
73
- | Selector | Host |
74
- | ------------------------ | --------------------------------- |
75
- | `[forCheckbox]` | `<button type="button">` (forced) |
76
- | `[forCheckboxIndicator]` | `<span>` |
77
-
78
- ### Combobox
79
-
80
- | Selector | Host |
81
- | ------------------------- | ------------------------------------------------------------------- |
82
- | `[forCombobox]` | `<div>` |
83
- | `[forComboboxInput]` | `<input>` (a real text field — `role="combobox"` + caret semantics) |
84
- | `[forComboboxContent]` | `<div>` |
85
- | `[forComboboxOption]` | `<div>` |
86
- | `[forComboboxIndicator]` | `<span>` |
87
- | `[forComboboxEmpty]` | `<div>` |
88
- | `[forComboboxStatus]` | `<div>` |
89
- | `[forComboboxClear]` | `<button>` |
90
- | `[forComboboxChips]` | `<div>` |
91
- | `[forComboboxChip]` | `<span>` |
92
- | `[forComboboxChipRemove]` | `<button>` |
93
- | `[forComboboxGroup]` | `<div>` |
94
- | `[forComboboxGroupLabel]` | `<div>` |
95
- | `[forComboboxSeparator]` | `<div>` |
96
-
97
- ### Context Menu
98
-
99
- | Selector | Host |
100
- | ------------------------------------------------------------ | ---------------- |
101
- | `[forContextMenu]` | `<div>` |
102
- | `[forContextMenuTrigger]` | any element |
103
- | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ below |
104
-
105
- ### Dialog
106
-
107
- | Selector | Host |
108
- | ------------------------ | ---------------------------------------------------------- |
109
- | `[forDialog]` | `<div>` |
110
- | `[forDialogTrigger]` | `<button>` |
111
- | `[forDialogTitle]` | `<h2>` (any heading level works; pick by document outline) |
112
- | `[forDialogDescription]` | `<p>` |
113
- | `[forDialogClose]` | `<button>` |
114
- | `[forDialogBackdrop]` | `<div>` |
115
-
116
- ### Disclosure
117
-
118
- | Selector | Host |
119
- | ------------------------ | ------------------------ |
120
- | `[forDisclosure]` | `<div>` |
121
- | `[forDisclosureTrigger]` | `<button>` |
122
- | `[forDisclosureContent]` | `<div>` (or `<section>`) |
123
-
124
- ### Dropdown Menu
125
-
126
- | Selector | Host |
127
- | ------------------------------------------------------------ | ---------------- |
128
- | `[forDropdownMenu]` | `<div>` |
129
- | `[forDropdownMenuTrigger]` | `<button>` |
130
- | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ below |
131
-
132
- ### Hover Card
133
-
134
- | Selector | Host |
135
- | ----------------------- | ----------- |
136
- | `[forHoverCard]` | `<div>` |
137
- | `[forHoverCardTrigger]` | any element |
138
- | `[forHoverCardContent]` | `<div>` |
139
- | `[forHoverCardArrow]` | `<div>` |
140
-
141
- ### Listbox
142
-
143
- | Selector | Host |
144
- | ----------------------------- | -------- |
145
- | `[forListbox]` | `<div>` |
146
- | `[forListboxOption]` | `<div>` |
147
- | `[forListboxOptionIndicator]` | `<span>` |
148
- | `[forListboxGroup]` | `<div>` |
149
- | `[forListboxGroupLabel]` | `<div>` |
150
-
151
- ### Menu
152
-
153
- | Selector | Host |
154
- | -------------------------- | -------- |
155
- | `[forMenu]` | `<div>` |
156
- | `[forMenuContent]` | `<div>` |
157
- | `[forMenuItem]` | `<div>` |
158
- | `[forMenuItemIndicator]` | `<span>` |
159
- | `[forMenuCheckboxItem]` | `<div>` |
160
- | `[forMenuRadioItem]` | `<div>` |
161
- | `[forMenuRadioGroup]` | `<div>` |
162
- | `[forMenuSeparator]` | `<div>` |
163
- | `[forMenuGroup]` | `<div>` |
164
- | `[forMenuGroupLabel]` | `<div>` |
165
- | `[forMenuSub]` | `<div>` |
166
- | `[forMenuSubTrigger]` | `<div>` |
167
- | `[forMenuHorizontalArrow]` | `<span>` |
168
-
169
- ### Menubar
170
-
171
- | Selector | Host |
172
- | ------------------------------------------------------------ | ---------------- |
173
- | `[forMenubar]` | `<div>` |
174
- | `[forMenubarTrigger]` | `<button>` |
175
- | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ above |
176
-
177
- ### Meter
178
-
179
- | Selector | Host |
180
- | --------------------- | ------- |
181
- | `[forMeter]` | `<div>` |
182
- | `[forMeterIndicator]` | `<div>` |
183
-
184
- ### Navigation Menu
185
-
186
- | Selector | Host |
187
- | ------------------------------ | ---------------------------------------- |
188
- | `[forNavigationMenu]` | `<nav>` |
189
- | `[forNavigationMenuList]` | `<ul>` (or `<div>`) |
190
- | `[forNavigationMenuItem]` | `<li>` (or `<div>`, matching the list) |
191
- | `[forNavigationMenuTrigger]` | `<button>` |
192
- | `[forNavigationMenuLink]` | `<a>` (or `<button>` for in-app actions) |
193
- | `[forNavigationMenuContent]` | `<div>` |
194
- | `[forNavigationMenuViewport]` | `<div>` |
195
- | `[forNavigationMenuIndicator]` | `<div>` |
196
-
197
- ### Pane Resizer
198
-
199
- | Selector | Host |
200
- | ------------------ | ------- |
201
- | `[forPaneResizer]` | `<div>` |
202
-
203
- ### Popover
204
-
205
- | Selector | Host |
206
- | ------------------------- | ------------------------------------- |
207
- | `[forPopover]` | `<div>` |
208
- | `[forPopoverTrigger]` | `<button>` |
209
- | `[forPopoverContent]` | `<div>` |
210
- | `[forPopoverTitle]` | `<h2>` (any heading; pick by outline) |
211
- | `[forPopoverDescription]` | `<p>` |
212
- | `[forPopoverClose]` | `<button>` |
213
- | `[forPopoverArrow]` | `<div>` |
214
- | `[forPopoverAnchor]` | any element |
215
-
216
- ### Progress
217
-
218
- | Selector | Host |
219
- | ------------------------ | ------- |
220
- | `[forProgress]` | `<div>` |
221
- | `[forProgressIndicator]` | `<div>` |
222
-
223
- ### Radio Group
224
-
225
- | Selector | Host |
226
- | ----------------- | --------------------------------- |
227
- | `[forRadioGroup]` | `<div>` |
228
- | `[forRadio]` | `<button type="button">` (forced) |
229
-
230
- ### Scroll Area
231
-
232
- | Selector | Host |
233
- | -------------------------- | ------- |
234
- | `[forScrollArea]` | `<div>` |
235
- | `[forScrollAreaViewport]` | `<div>` |
236
- | `[forScrollAreaContent]` | `<div>` |
237
- | `[forScrollAreaScrollbar]` | `<div>` |
238
- | `[forScrollAreaThumb]` | `<div>` |
239
- | `[forScrollAreaCorner]` | `<div>` |
240
-
241
- ### Select
242
-
243
- | Selector | Host |
244
- | ----------------------- | ---------- |
245
- | `[forSelect]` | `<div>` |
246
- | `[forSelectTrigger]` | `<button>` |
247
- | `[forSelectValue]` | `<span>` |
248
- | `[forSelectContent]` | `<div>` |
249
- | `[forSelectOption]` | `<div>` |
250
- | `[forSelectIndicator]` | `<span>` |
251
- | `[forSelectGroup]` | `<div>` |
252
- | `[forSelectGroupLabel]` | `<div>` |
253
- | `[forSelectSeparator]` | `<div>` |
254
-
255
- ### Separator
256
-
257
- | Selector | Host |
258
- | ---------------- | --------------------------------------------------- |
259
- | `[forSeparator]` | `<div>` (or `<hr>` for the static, decorative case) |
260
-
261
- ### Slider
262
-
263
- | Selector | Host |
264
- | ------------------ | ------- |
265
- | `[forSlider]` | `<div>` |
266
- | `[forSliderTrack]` | `<div>` |
267
- | `[forSliderRange]` | `<div>` |
268
- | `[forSliderThumb]` | `<div>` |
269
-
270
- ### Switch
271
-
272
- | Selector | Host |
273
- | ------------- | --------------------------------- |
274
- | `[forSwitch]` | `<button type="button">` (forced) |
275
-
276
- ### Tabs
277
-
278
- | Selector | Host |
279
- | ------------------ | ---------- |
280
- | `[forTabs]` | `<div>` |
281
- | `[forTabsList]` | `<div>` |
282
- | `[forTabsTrigger]` | `<button>` |
283
- | `[forTabsContent]` | `<div>` |
284
-
285
- ### Toast
286
-
287
- | Selector | Host |
288
- | ------------------------------------------------------ | ---------- |
289
- | `for-toast-viewport` (element) or `[forToastViewport]` | `<div>` |
290
- | `[forToast]` | `<div>` |
291
- | `[forToastTitle]` | `<div>` |
292
- | `[forToastDescription]` | `<div>` |
293
- | `[forToastAction]` | `<button>` |
294
- | `[forToastClose]` | `<button>` |
295
-
296
- ### Toggle
297
-
298
- | Selector | Host |
299
- | ---------------------- | --------------------------------- |
300
- | `[forToggle]` | `<button type="button">` (forced) |
301
- | `[forToggleGroup]` | `<div>` |
302
- | `[forToggleGroupItem]` | `<button type="button">` (forced) |
303
-
304
- ### Toolbar
305
-
306
- | Selector | Host |
307
- | ----------------------- | ---------- |
308
- | `[forToolbar]` | `<div>` |
309
- | `[forToolbarButton]` | `<button>` |
310
- | `[forToolbarLink]` | `<a>` |
311
- | `[forToolbarSeparator]` | `<div>` |
312
-
313
- ### Tooltip
314
-
315
- | Selector | Host |
316
- | --------------------- | ----------- |
317
- | `[forTooltip]` | `<div>` |
318
- | `[forTooltipTrigger]` | any element |
319
- | `[forTooltipContent]` | `<div>` |
320
- | `[forTooltipArrow]` | `<div>` |
321
-
322
- ## Building
323
-
324
- ```bash
325
- ng build forty-cdk
326
- ```
327
-
328
- Build artifacts land in `dist/forty-cdk` (consumed locally via the `forty-cdk` path alias in the root `tsconfig.json`).
329
-
330
- ## Testing
331
-
332
- Tests run on Vitest via the Angular CLI builder `@angular/build:unit-test`:
333
-
334
- ```bash
335
- pnpm test # all specs, single pass
336
- pnpm exec ng test forty-cdk --watch # watch mode
337
- pnpm exec ng test forty-cdk --include "projects/forty-cdk/src/lib/accordion/accordion.spec.ts" # single file
338
- pnpm exec ng test forty-cdk --filter "Enter and Space select" # tests by name (regex)
339
- ```
340
-
341
- The `-- <path>` / `-- -t "<name>"` passthrough forms do **not** work on this setup (pnpm mangles the quoted `--`, so `ng` rejects it) — use the builder's own `--include` (repeatable) and `--filter` (regex) flags instead.
342
-
343
- Every primitive's test suite includes a case running under `provideZonelessChangeDetection()` to keep reactivity working without Zone.js.
1
+ # forty-cdk
2
+
3
+ Headless / styleless UI primitives for Angular with WAI-ARIA accessibility built in.
4
+ Inspired by Radix UI and Base UI but reinterpreted idiomatically for modern Angular.
5
+
6
+ **New here?** [Your first overlay](../../docs/your-first-overlay.md) walks one Popover from empty markup to styled-and-animated and explains the two concepts every overlay shares: the `@if` / open-state model and the portal → global CSS requirement.
7
+
8
+ **Styling these primitives?** [Styling forty-cdk](../../docs/styling.md) explains the three hooks you style against — your own class (not the directive selector), `data-*` state attributes, and `--for-*` custom properties — and links to each primitive's styling reference.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install forty-cdk
14
+ ```
15
+
16
+ ### Peer dependencies
17
+
18
+ Required:
19
+
20
+ - `@angular/common` `^22.0.0`
21
+ - `@angular/core` `^22.0.0`
22
+
23
+ Optional — install only if you use the matching entry point / primitives:
24
+
25
+ | Peer | Needed by |
26
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | `@angular/forms` `^22.0.0` | Form-control primitives (`Switch`, `Checkbox`, `RadioGroup`, `Listbox`, `Select`, `Slider`, `Combobox`, …). They implement `FormValueControl` / `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring. The contract is type-only, so the published bundle never references the package — consumers using only non-form primitives can skip it. |
28
+ | `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves only depend on the abstract `DateAdapter` contract from the main entry point — install this peer only when you import that entry point. |
29
+
30
+ `@angular/forms/signals` is stable as of Angular 22, so the peer follows the standard major range (`^22.0.0`).
31
+
32
+ ### Regular dependencies
33
+
34
+ `@floating-ui/dom` is a regular dependency, installed automatically with the package. Positioned overlays (`Tooltip`, `Popover`, `Menu`, `Combobox`, `Select`, etc.) import it statically from the main entry point, so every consumer's build must be able to resolve it — but it is internal-only (no floating-ui value crosses the public API) and tree-shakes out of your bundle when you don't use any positioned primitive.
35
+
36
+ ## Primitives
37
+
38
+ Each primitive lives under [`src/lib/<primitive>/`](src/lib) with its own `README.md` and a minimal styleless usage example.
39
+
40
+ The library ships one main entry point (`forty-cdk`) plus a single secondary entry point, `forty-cdk/internationalized-date`, which holds the `@internationalized/date` adapters so that optional peer stays truly optional. Standalone directives plus `"sideEffects": false` let tree-shakers drop primitives you don't import.
41
+
42
+ ## Directive → host element matrix
43
+
44
+ Quick reference for "which HTML element should I put this directive on?". Recommendations are derived from each primitive's WAI-ARIA pattern (e.g. focusable triggers as `<button type="button">`, the combobox input as a real `<input>` so caret/selection work) and from each primitive's README usage example. `any element` means the directive is element-agnostic — pick the tag that matches your semantics.
45
+
46
+ Selectors marked with `(element)` use an element selector instead of an attribute selector; everything else is `[attribute]`. Form-control hosts (`forSwitch`, `forCheckbox`, `forRadio`, `forToggle`) deliberately render as `<button type="button">` — the directive forces `type="button"` and provides `role="switch"` / `"checkbox"` / `"radio"` / `aria-pressed` so the consumer keeps full keyboard, focus, and form-state behaviour without an `<input>` whose chrome can't be styled.
47
+
48
+ ### Accordion
49
+
50
+ | Selector | Host |
51
+ | ----------------------- | ---------------------------------------------- |
52
+ | `[forAccordion]` | `<div>` |
53
+ | `[forAccordionItem]` | `<div>` |
54
+ | `[forAccordionTrigger]` | `<button>` (wrapped in `<h2>`–`<h6>`, per APG) |
55
+ | `[forAccordionContent]` | `<section>` |
56
+
57
+ ### Aspect Ratio
58
+
59
+ | Selector | Host |
60
+ | ------------------ | ------- |
61
+ | `[forAspectRatio]` | `<div>` |
62
+
63
+ ### Avatar
64
+
65
+ | Selector | Host |
66
+ | --------------------- | ------------------------------ |
67
+ | `[forAvatar]` | `<span>` |
68
+ | `img[forAvatarImage]` | `<img>` (selector enforces it) |
69
+ | `[forAvatarFallback]` | `<span>` |
70
+
71
+ ### Checkbox
72
+
73
+ | Selector | Host |
74
+ | ------------------------ | --------------------------------- |
75
+ | `[forCheckbox]` | `<button type="button">` (forced) |
76
+ | `[forCheckboxIndicator]` | `<span>` |
77
+
78
+ ### Combobox
79
+
80
+ | Selector | Host |
81
+ | ------------------------- | ------------------------------------------------------------------- |
82
+ | `[forCombobox]` | `<div>` |
83
+ | `[forComboboxInput]` | `<input>` (a real text field — `role="combobox"` + caret semantics) |
84
+ | `[forComboboxContent]` | `<div>` |
85
+ | `[forComboboxOption]` | `<div>` |
86
+ | `[forComboboxIndicator]` | `<span>` |
87
+ | `[forComboboxEmpty]` | `<div>` |
88
+ | `[forComboboxStatus]` | `<div>` |
89
+ | `[forComboboxClear]` | `<button>` |
90
+ | `[forComboboxChips]` | `<div>` |
91
+ | `[forComboboxChip]` | `<span>` |
92
+ | `[forComboboxChipRemove]` | `<button>` |
93
+ | `[forComboboxGroup]` | `<div>` |
94
+ | `[forComboboxGroupLabel]` | `<div>` |
95
+ | `[forComboboxSeparator]` | `<div>` |
96
+
97
+ ### Context Menu
98
+
99
+ | Selector | Host |
100
+ | ------------------------------------------------------------ | ---------------- |
101
+ | `[forContextMenu]` | `<div>` |
102
+ | `[forContextMenuTrigger]` | any element |
103
+ | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ below |
104
+
105
+ ### Dialog
106
+
107
+ | Selector | Host |
108
+ | ------------------------ | ---------------------------------------------------------- |
109
+ | `[forDialog]` | `<div>` |
110
+ | `[forDialogTrigger]` | `<button>` |
111
+ | `[forDialogTitle]` | `<h2>` (any heading level works; pick by document outline) |
112
+ | `[forDialogDescription]` | `<p>` |
113
+ | `[forDialogClose]` | `<button>` |
114
+ | `[forDialogBackdrop]` | `<div>` |
115
+
116
+ ### Disclosure
117
+
118
+ | Selector | Host |
119
+ | ------------------------ | ------------------------ |
120
+ | `[forDisclosure]` | `<div>` |
121
+ | `[forDisclosureTrigger]` | `<button>` |
122
+ | `[forDisclosureContent]` | `<div>` (or `<section>`) |
123
+
124
+ ### Dropdown Menu
125
+
126
+ | Selector | Host |
127
+ | ------------------------------------------------------------ | ---------------- |
128
+ | `[forDropdownMenu]` | `<div>` |
129
+ | `[forDropdownMenuTrigger]` | `<button>` |
130
+ | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ below |
131
+
132
+ ### Hover Card
133
+
134
+ | Selector | Host |
135
+ | ----------------------- | ----------- |
136
+ | `[forHoverCard]` | `<div>` |
137
+ | `[forHoverCardTrigger]` | any element |
138
+ | `[forHoverCardContent]` | `<div>` |
139
+ | `[forHoverCardArrow]` | `<div>` |
140
+
141
+ ### Listbox
142
+
143
+ | Selector | Host |
144
+ | ----------------------------- | -------- |
145
+ | `[forListbox]` | `<div>` |
146
+ | `[forListboxOption]` | `<div>` |
147
+ | `[forListboxOptionIndicator]` | `<span>` |
148
+ | `[forListboxGroup]` | `<div>` |
149
+ | `[forListboxGroupLabel]` | `<div>` |
150
+
151
+ ### Menu
152
+
153
+ | Selector | Host |
154
+ | -------------------------- | -------- |
155
+ | `[forMenu]` | `<div>` |
156
+ | `[forMenuContent]` | `<div>` |
157
+ | `[forMenuItem]` | `<div>` |
158
+ | `[forMenuItemIndicator]` | `<span>` |
159
+ | `[forMenuCheckboxItem]` | `<div>` |
160
+ | `[forMenuRadioItem]` | `<div>` |
161
+ | `[forMenuRadioGroup]` | `<div>` |
162
+ | `[forMenuSeparator]` | `<div>` |
163
+ | `[forMenuGroup]` | `<div>` |
164
+ | `[forMenuGroupLabel]` | `<div>` |
165
+ | `[forMenuSub]` | `<div>` |
166
+ | `[forMenuSubTrigger]` | `<div>` |
167
+ | `[forMenuHorizontalArrow]` | `<span>` |
168
+
169
+ ### Menubar
170
+
171
+ | Selector | Host |
172
+ | ------------------------------------------------------------ | ---------------- |
173
+ | `[forMenubar]` | `<div>` |
174
+ | `[forMenubarTrigger]` | `<button>` |
175
+ | Menu surface pieces (`[forMenuContent]`, `[forMenuItem]`, …) | see _Menu_ above |
176
+
177
+ ### Meter
178
+
179
+ | Selector | Host |
180
+ | --------------------- | ------- |
181
+ | `[forMeter]` | `<div>` |
182
+ | `[forMeterIndicator]` | `<div>` |
183
+
184
+ ### Navigation Menu
185
+
186
+ | Selector | Host |
187
+ | ------------------------------ | ---------------------------------------- |
188
+ | `[forNavigationMenu]` | `<nav>` |
189
+ | `[forNavigationMenuList]` | `<ul>` (or `<div>`) |
190
+ | `[forNavigationMenuItem]` | `<li>` (or `<div>`, matching the list) |
191
+ | `[forNavigationMenuTrigger]` | `<button>` |
192
+ | `[forNavigationMenuLink]` | `<a>` (or `<button>` for in-app actions) |
193
+ | `[forNavigationMenuContent]` | `<div>` |
194
+ | `[forNavigationMenuViewport]` | `<div>` |
195
+ | `[forNavigationMenuIndicator]` | `<div>` |
196
+
197
+ ### Pane Resizer
198
+
199
+ | Selector | Host |
200
+ | ------------------ | ------- |
201
+ | `[forPaneResizer]` | `<div>` |
202
+
203
+ ### Popover
204
+
205
+ | Selector | Host |
206
+ | ------------------------- | ------------------------------------- |
207
+ | `[forPopover]` | `<div>` |
208
+ | `[forPopoverTrigger]` | `<button>` |
209
+ | `[forPopoverContent]` | `<div>` |
210
+ | `[forPopoverTitle]` | `<h2>` (any heading; pick by outline) |
211
+ | `[forPopoverDescription]` | `<p>` |
212
+ | `[forPopoverClose]` | `<button>` |
213
+ | `[forPopoverArrow]` | `<div>` |
214
+ | `[forPopoverAnchor]` | any element |
215
+
216
+ ### Progress
217
+
218
+ | Selector | Host |
219
+ | ------------------------ | ------- |
220
+ | `[forProgress]` | `<div>` |
221
+ | `[forProgressIndicator]` | `<div>` |
222
+
223
+ ### Radio Group
224
+
225
+ | Selector | Host |
226
+ | ----------------- | --------------------------------- |
227
+ | `[forRadioGroup]` | `<div>` |
228
+ | `[forRadio]` | `<button type="button">` (forced) |
229
+
230
+ ### Scroll Area
231
+
232
+ | Selector | Host |
233
+ | -------------------------- | ------- |
234
+ | `[forScrollArea]` | `<div>` |
235
+ | `[forScrollAreaViewport]` | `<div>` |
236
+ | `[forScrollAreaContent]` | `<div>` |
237
+ | `[forScrollAreaScrollbar]` | `<div>` |
238
+ | `[forScrollAreaThumb]` | `<div>` |
239
+ | `[forScrollAreaCorner]` | `<div>` |
240
+
241
+ ### Select
242
+
243
+ | Selector | Host |
244
+ | ----------------------- | ---------- |
245
+ | `[forSelect]` | `<div>` |
246
+ | `[forSelectTrigger]` | `<button>` |
247
+ | `[forSelectValue]` | `<span>` |
248
+ | `[forSelectContent]` | `<div>` |
249
+ | `[forSelectOption]` | `<div>` |
250
+ | `[forSelectIndicator]` | `<span>` |
251
+ | `[forSelectGroup]` | `<div>` |
252
+ | `[forSelectGroupLabel]` | `<div>` |
253
+ | `[forSelectSeparator]` | `<div>` |
254
+
255
+ ### Separator
256
+
257
+ | Selector | Host |
258
+ | ---------------- | --------------------------------------------------- |
259
+ | `[forSeparator]` | `<div>` (or `<hr>` for the static, decorative case) |
260
+
261
+ ### Slider
262
+
263
+ | Selector | Host |
264
+ | ------------------ | ------- |
265
+ | `[forSlider]` | `<div>` |
266
+ | `[forSliderTrack]` | `<div>` |
267
+ | `[forSliderRange]` | `<div>` |
268
+ | `[forSliderThumb]` | `<div>` |
269
+
270
+ ### Switch
271
+
272
+ | Selector | Host |
273
+ | ------------- | --------------------------------- |
274
+ | `[forSwitch]` | `<button type="button">` (forced) |
275
+
276
+ ### Tabs
277
+
278
+ | Selector | Host |
279
+ | ------------------ | ---------- |
280
+ | `[forTabs]` | `<div>` |
281
+ | `[forTabsList]` | `<div>` |
282
+ | `[forTabsTrigger]` | `<button>` |
283
+ | `[forTabsContent]` | `<div>` |
284
+
285
+ ### Toast
286
+
287
+ | Selector | Host |
288
+ | ------------------------------------------------------ | ---------- |
289
+ | `for-toast-viewport` (element) or `[forToastViewport]` | `<div>` |
290
+ | `[forToast]` | `<div>` |
291
+ | `[forToastTitle]` | `<div>` |
292
+ | `[forToastDescription]` | `<div>` |
293
+ | `[forToastAction]` | `<button>` |
294
+ | `[forToastClose]` | `<button>` |
295
+
296
+ ### Toggle
297
+
298
+ | Selector | Host |
299
+ | ---------------------- | --------------------------------- |
300
+ | `[forToggle]` | `<button type="button">` (forced) |
301
+ | `[forToggleGroup]` | `<div>` |
302
+ | `[forToggleGroupItem]` | `<button type="button">` (forced) |
303
+
304
+ ### Toolbar
305
+
306
+ | Selector | Host |
307
+ | ----------------------- | ---------- |
308
+ | `[forToolbar]` | `<div>` |
309
+ | `[forToolbarButton]` | `<button>` |
310
+ | `[forToolbarLink]` | `<a>` |
311
+ | `[forToolbarSeparator]` | `<div>` |
312
+
313
+ ### Tooltip
314
+
315
+ | Selector | Host |
316
+ | --------------------- | ----------- |
317
+ | `[forTooltip]` | `<div>` |
318
+ | `[forTooltipTrigger]` | any element |
319
+ | `[forTooltipContent]` | `<div>` |
320
+ | `[forTooltipArrow]` | `<div>` |
321
+
322
+ ## Building
323
+
324
+ ```bash
325
+ ng build forty-cdk
326
+ ```
327
+
328
+ Build artifacts land in `dist/forty-cdk` (consumed locally via the `forty-cdk` path alias in the root `tsconfig.json`).
329
+
330
+ ## Testing
331
+
332
+ Tests run on Vitest via the Angular CLI builder `@angular/build:unit-test`:
333
+
334
+ ```bash
335
+ pnpm test # all specs, single pass
336
+ pnpm exec ng test forty-cdk --watch # watch mode
337
+ pnpm exec ng test forty-cdk --include "projects/forty-cdk/src/lib/accordion/accordion.spec.ts" # single file
338
+ pnpm exec ng test forty-cdk --filter "Enter and Space select" # tests by name (regex)
339
+ ```
340
+
341
+ The `-- <path>` / `-- -t "<name>"` passthrough forms do **not** work on this setup (pnpm mangles the quoted `--`, so `ng` rejects it) — use the builder's own `--include` (repeatable) and `--filter` (regex) flags instead.
342
+
343
+ Every primitive's test suite includes a case running under `provideZonelessChangeDetection()` to keep reactivity working without Zone.js.