forty-cdk 0.2.0 → 0.3.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.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
package/field/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Field
|
|
2
|
+
|
|
3
|
+
Headless form-field wiring. It renders **nothing** and imposes no layout: it only associates a label, a description, and an error region with a single control (via `id` / `aria-labelledby` / `aria-describedby` / `aria-errormessage`) and reflects the control's validation state as `data-*` styling hooks.
|
|
4
|
+
|
|
5
|
+
There is no rendered chrome, no appearance variants, and **no control contract to implement**: every forty-cdk form primitive (`FormValueControl` / `FormCheckboxControl`) already exposes the state the field needs, so wrapping one in a `[forField]` auto-associates it with zero extra markup.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Role |
|
|
10
|
+
| --------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `ForField` | `[forField]` | Root. Owns the generated ids and reflects `data-invalid` / `data-disabled` / `data-required` / `data-touched`. |
|
|
12
|
+
| `ForLabel` | `[forLabel]` | Accessible label. Inside a field it wires `aria-labelledby` (and `for` on a `<label>`); usable standalone. |
|
|
13
|
+
| `ForFieldDescription` | `[forFieldDescription]` | Hint / description. Wires `aria-describedby`. |
|
|
14
|
+
| `ForFieldError` | `[forFieldError]` | Error region (`role="alert"`). Reads the control's Signal Forms errors automatically. |
|
|
15
|
+
| `ForFieldControl` | `[forFieldControl]` | Opt-in marker for a **native** `<input>` / `<textarea>` (forty-cdk controls don't need it). |
|
|
16
|
+
|
|
17
|
+
## How the control connects
|
|
18
|
+
|
|
19
|
+
- **forty-cdk controls** (`forSwitch`, `forCheckbox`, `forSlider`, `forSelect`, `forListbox`, `forCombobox`, `forRadioGroup`, `forToggle`, `forToggleGroup`) auto-wire — they inherit the association from the shared form base. No marker needed.
|
|
20
|
+
- **Native controls** add `[forFieldControl]` and drive validation state via its `invalid` / `required` / `disabled` / `touched` inputs.
|
|
21
|
+
|
|
22
|
+
`ForField` owns the control's `id` (it assigns one if the control has none, otherwise it adopts the existing id), so a `<label forLabel>`'s `for` always resolves to the control.
|
|
23
|
+
|
|
24
|
+
## `ForFieldError` — automatic Signal Forms errors
|
|
25
|
+
|
|
26
|
+
`ForFieldError` reads the control's `errors()` automatically and exposes them as signals:
|
|
27
|
+
|
|
28
|
+
- `errors()` — the raw `ValidationError[]`.
|
|
29
|
+
- `messages()` — `string[]` of human-readable messages.
|
|
30
|
+
- `hasErrors()` / `shown()` — `shown()` is `true` when the control is invalid and has errors.
|
|
31
|
+
|
|
32
|
+
You render them; the field handles the ARIA. The error id is wired into `aria-errormessage` (and folded into `aria-describedby`) only while the control is invalid.
|
|
33
|
+
|
|
34
|
+
## Example
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Component, signal } from '@angular/core';
|
|
38
|
+
import { form, FormField, required } from '@angular/forms/signals';
|
|
39
|
+
import { ForField, ForFieldDescription, ForFieldError, ForLabel } from 'forty-cdk/field';
|
|
40
|
+
import { ForSwitch } from 'forty-cdk/switch';
|
|
41
|
+
|
|
42
|
+
@Component({
|
|
43
|
+
selector: 'demo-field',
|
|
44
|
+
imports: [ForField, ForLabel, ForFieldDescription, ForFieldError, ForSwitch, FormField],
|
|
45
|
+
template: `
|
|
46
|
+
<div forField class="field">
|
|
47
|
+
<label forLabel class="field-label">Notifications</label>
|
|
48
|
+
<button forSwitch [formField]="settings.notify"></button>
|
|
49
|
+
<p forFieldDescription>We'll only email you about security.</p>
|
|
50
|
+
@if (err.shown()) {
|
|
51
|
+
<p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
|
|
52
|
+
}
|
|
53
|
+
</div>
|
|
54
|
+
`,
|
|
55
|
+
})
|
|
56
|
+
export class DemoField {
|
|
57
|
+
readonly model = signal({ notify: false });
|
|
58
|
+
readonly settings = form(this.model, (s) => {
|
|
59
|
+
required(s.notify, { message: 'Please choose a preference' });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Style off the reflected state:
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
.field[data-invalid] .field-label {
|
|
68
|
+
color: var(--color-danger);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Label-click activation
|
|
73
|
+
|
|
74
|
+
Clicking the label activates the control on both host shapes, not just focuses it. A native `<label forLabel>` emits `for` and the browser forwards the click; a non-`<label>` `[forLabel]` (e.g. `<span forLabel>`) has no native `for` forwarding, so the directive forwards the click itself. Either way, clicking the label toggles a `[forSwitch]` / checkbox-role control, activates a button-host control, or focuses a text input — matching native `<label for>` behavior consistently.
|
|
75
|
+
|
|
76
|
+
> Note: composite controls whose host is not the focusable element (`forListbox`, `forSelect`, `forCombobox`) still receive `aria-labelledby` correctly, and a label click is forwarded to the control's nominated focusable element (the Select trigger / Combobox input) rather than the wrapper host.
|
|
77
|
+
|
|
78
|
+
## Styling
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
### Data attributes
|
|
83
|
+
|
|
84
|
+
| Piece | Attribute | Values |
|
|
85
|
+
| ------------ | --------------- | ---------------- |
|
|
86
|
+
| `[forField]` | `data-invalid` | present / absent |
|
|
87
|
+
| `[forField]` | `data-disabled` | present / absent |
|
|
88
|
+
| `[forField]` | `data-required` | present / absent |
|
|
89
|
+
| `[forField]` | `data-touched` | present / absent |
|
|
90
|
+
|
|
91
|
+
The reflected state mirrors the registered control: `data-invalid` while it is invalid, `data-required` while it is required, `data-touched` once it has been touched, and `data-disabled` from the control's own disabled state OR a surrounding `[forFieldset]`'s `disabled`. (`[forFieldControl]` additionally reflects `aria-invalid` on its own host, but that is an ARIA hook, not a styling one.)
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
.field[data-invalid] .field-label {
|
|
95
|
+
color: var(--color-danger);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Fieldset
|
|
2
|
+
|
|
3
|
+
Headless grouping wiring — the styleless counterpart to a native `<fieldset>` + `<legend>`, and the grouping companion to [`Field`](../field/README.md). It gives a set of related fields (an address block, a preferences group) a **shared accessible name** and an optional shared `disabled` state. It renders nothing and imposes no layout.
|
|
4
|
+
|
|
5
|
+
Use it on a real `<fieldset>` to lean on native grouping, or on any other element to get `role="group"` + `aria-labelledby` wired automatically.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Role |
|
|
10
|
+
| ------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `ForFieldset` | `[forFieldset]` | Group container. Emits `role="group"` + `aria-labelledby` on a non-`<fieldset>`; reflects `data-disabled`. |
|
|
12
|
+
| `ForFieldsetLegend` | `[forFieldsetLegend]` | Group label. Adopts the fieldset's `legendId` so `aria-labelledby` resolves. Usable standalone (inert). |
|
|
13
|
+
|
|
14
|
+
## How grouping connects
|
|
15
|
+
|
|
16
|
+
`ForFieldset` detects its host tag, exactly like `ForLabel`'s `<label>` check:
|
|
17
|
+
|
|
18
|
+
- On a native **`<fieldset>`**, the browser groups its controls and labels them with the `<legend>` implicitly — so the directive emits **no** `role` and **no** `aria-labelledby`.
|
|
19
|
+
- On **any other element**, it emits `role="group"` and `aria-labelledby` pointing at the `[forFieldsetLegend]`'s generated id.
|
|
20
|
+
|
|
21
|
+
## Shared `disabled`
|
|
22
|
+
|
|
23
|
+
The `disabled` input:
|
|
24
|
+
|
|
25
|
+
- reflects `data-disabled` on the group host,
|
|
26
|
+
- emits the native `disabled` attribute on a `<fieldset>` (or `aria-disabled="true"` on any other element),
|
|
27
|
+
- and propagates to every descendant form control (`forSwitch`, `forCheckbox`, `forSelect`, `forSlider`, …) via context: each control ORs the group's disabled into its own effective disabled, so it becomes genuinely **inert** (interaction ignored, excluded from native form submission) and exposes `aria-disabled="true"` / `data-disabled`. This reaches custom-role controls that a native `<fieldset disabled>` cannot. A control outside any fieldset is unaffected.
|
|
28
|
+
|
|
29
|
+
Nesting composes like native fieldsets: a disabled outer `[forFieldset]` keeps every control inside disabled even under an inner, enabled `[forFieldset]` — the inner group cannot re-enable what the outer disabled.
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { Component, signal } from '@angular/core';
|
|
35
|
+
import { ForField, ForFieldControl, ForLabel } from 'forty-cdk/field';
|
|
36
|
+
import { ForFieldset, ForFieldsetLegend } from 'forty-cdk/fieldset';
|
|
37
|
+
|
|
38
|
+
@Component({
|
|
39
|
+
selector: 'demo-fieldset',
|
|
40
|
+
imports: [ForFieldset, ForFieldsetLegend, ForField, ForLabel, ForFieldControl],
|
|
41
|
+
template: `
|
|
42
|
+
<fieldset forFieldset class="fieldset" [disabled]="locked()">
|
|
43
|
+
<legend forFieldsetLegend>Shipping address</legend>
|
|
44
|
+
|
|
45
|
+
<div forField>
|
|
46
|
+
<label forLabel>Street</label>
|
|
47
|
+
<input forFieldControl />
|
|
48
|
+
</div>
|
|
49
|
+
<div forField>
|
|
50
|
+
<label forLabel>City</label>
|
|
51
|
+
<input forFieldControl />
|
|
52
|
+
</div>
|
|
53
|
+
</fieldset>
|
|
54
|
+
`,
|
|
55
|
+
})
|
|
56
|
+
export class DemoFieldset {
|
|
57
|
+
readonly locked = signal(false);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
On custom markup (no native `<fieldset>`), the same wiring yields `role="group"` + `aria-labelledby`:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<div forFieldset class="fieldset">
|
|
65
|
+
<span forFieldsetLegend>Shipping address</span>
|
|
66
|
+
<!-- … fields … -->
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Styling
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
### Data attributes
|
|
75
|
+
|
|
76
|
+
| Piece | Attribute | Values |
|
|
77
|
+
| --------------- | --------------- | ---------------- |
|
|
78
|
+
| `[forFieldset]` | `data-disabled` | present / absent |
|
|
79
|
+
|
|
80
|
+
`[forFieldsetLegend]` reflects no data-\* attributes (it only carries the generated `id` that the group's `aria-labelledby` resolves to).
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
.fieldset[data-disabled] {
|
|
84
|
+
opacity: 0.5;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# FileUpload
|
|
2
|
+
|
|
3
|
+
Headless drag-and-drop / dialog file-selection primitive. No ARIA role is imposed on the drop zone — it is a plain container. The `<input type="file">` remains the accessible form control; the trigger is a native `<button>`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<div forFileUpload accept="image/*,.pdf" (filesChange)="onFiles($event)">
|
|
9
|
+
<!-- Visually hidden input: keep it focusable for keyboard / AT users.
|
|
10
|
+
Apply your own sr-only / visually-hidden CSS utility. -->
|
|
11
|
+
<input forFileUploadInput aria-label="Upload files" class="sr-only" />
|
|
12
|
+
|
|
13
|
+
<!-- The trigger opens the native file dialog on click / Enter / Space. -->
|
|
14
|
+
<button forFileUploadTrigger type="button">Choose files</button>
|
|
15
|
+
|
|
16
|
+
<p>or drag and drop here</p>
|
|
17
|
+
</div>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Multiple files
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<div forFileUpload multiple (filesChange)="onFiles($event)">
|
|
24
|
+
<input forFileUploadInput aria-label="Upload files" class="sr-only" />
|
|
25
|
+
<button forFileUploadTrigger>Choose files</button>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Directory (folder) selection
|
|
30
|
+
|
|
31
|
+
Set `directory` to switch the native picker into folder-selection mode (mirrored onto the input as `webkitdirectory`). The emitted `FileList` then contains every file inside the chosen folder, each carrying a `webkitRelativePath` the consumer reads to reconstruct the tree.
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<div forFileUpload directory (filesChange)="onFolder($event)">
|
|
35
|
+
<input forFileUploadInput aria-label="Upload folder" class="sr-only" />
|
|
36
|
+
<button forFileUploadTrigger>Choose folder</button>
|
|
37
|
+
</div>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
onFolder(files: FileList): void {
|
|
42
|
+
for (const file of Array.from(files)) {
|
|
43
|
+
console.log(file.webkitRelativePath); // e.g. "photos/2024/img.jpg"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Despite the `webkit-` prefix the attribute is supported across modern Chromium, Firefox, and WebKit. Directory drag-and-drop (`DataTransferItem.webkitGetAsEntry`) is out of scope — drop continues to surface `DataTransfer.files` only.
|
|
49
|
+
|
|
50
|
+
### Disabled
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<div forFileUpload [disabled]="isDisabled()">
|
|
54
|
+
<input forFileUploadInput aria-label="Upload files" class="sr-only" />
|
|
55
|
+
<button forFileUploadTrigger>Choose files</button>
|
|
56
|
+
</div>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Inputs
|
|
60
|
+
|
|
61
|
+
| Input | Type | Default | Description |
|
|
62
|
+
| ----------- | ---------------- | ------- | ------------------------------------------------------------------------------ |
|
|
63
|
+
| `accept` | `string \| null` | `null` | MIME types or file extensions accepted by the chooser (e.g. `"image/*,.pdf"`). |
|
|
64
|
+
| `multiple` | `boolean` | `false` | Whether multiple files can be selected at once. |
|
|
65
|
+
| `directory` | `boolean` | `false` | Whether the picker selects a whole folder (mirrored as `webkitdirectory`). |
|
|
66
|
+
| `disabled` | `boolean` | `false` | Whether the zone and all its pieces are disabled. |
|
|
67
|
+
|
|
68
|
+
## Data attributes
|
|
69
|
+
|
|
70
|
+
| Attribute | When present |
|
|
71
|
+
| --------------- | ---------------------------------------------------- |
|
|
72
|
+
| `data-dragging` | Files are actively being dragged over the drop zone. |
|
|
73
|
+
| `data-disabled` | The zone (and all its pieces) is disabled. |
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# HoverCard
|
|
2
|
+
|
|
3
|
+
> New to overlays in forty-cdk? [Your first overlay](../../../../../docs/your-first-overlay.md) walks a Popover from empty markup to styled-and-animated and explains the `@if` / open-state model and the portal → global CSS rule.
|
|
4
|
+
|
|
5
|
+
Headless preview card that opens on hover or focus of a trigger. Use it for profile snapshots, link previews, definition cards — any **complementary** information that surfaces on dwell.
|
|
6
|
+
|
|
7
|
+
There is no APG pattern for HoverCard. Treat it as a presentational layer: the trigger must already convey full meaning (it's a link, a name, a tag), so keyboard-only users miss nothing if they never see the card. Card content can be interactive — that's its main difference from `[forTooltip]`, where APG bans it.
|
|
8
|
+
|
|
9
|
+
## Pieces
|
|
10
|
+
|
|
11
|
+
| Class | Selector | Role |
|
|
12
|
+
| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
13
|
+
| `ForHoverCard` | `[forHoverCard]` | Root. Owns open state and delays. |
|
|
14
|
+
| `ForHoverCardTrigger` | `[forHoverCardTrigger]` | Element that opens the card on hover / focus. |
|
|
15
|
+
| `ForHoverCardContent` | `[forHoverCardContent]` | The card surface. Portaled and floating-positioned. Pointer-enter cancels close, so the user can move the cursor inside. |
|
|
16
|
+
| `ForHoverCardArrow` | `[forHoverCardArrow]` | Optional arrow positioned by floating-ui. |
|
|
17
|
+
|
|
18
|
+
## Inputs / models
|
|
19
|
+
|
|
20
|
+
| API | Type | Description |
|
|
21
|
+
| ------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `open` | `model<boolean>` (two-way bindable as `[(open)]`) | `(openChange)` fires only on internal transitions (delay timers, escape, blur, and the force-close that runs when `disabled` flips to true). |
|
|
23
|
+
| `side` | `input<FloatingSide \| undefined>` | Anchor side (`'top'` / `'right'` / `'bottom'` / `'left'`). Falls back to `provideForHoverCardDefaults` (`'top'`). |
|
|
24
|
+
| `align` | `input<FloatingAlign \| undefined>` | Alignment along `side` (`'start'` / `'center'` / `'end'`). Falls back to `provideForHoverCardDefaults` (`'center'`). |
|
|
25
|
+
| `sideOffset` | `input<number \| undefined>` | Gap (px) between trigger and card along the main axis. Falls back to `provideForHoverCardDefaults` (`8`). |
|
|
26
|
+
| `alignOffset` | `input<number>` | Gap (px) along the cross axis. Default `0`. |
|
|
27
|
+
| `collisionPadding` | `input<number \| undefined>` | Padding (px) for the `flip` / `shift` / `size` collision middlewares. Falls back to `provideForHoverCardDefaults` (`8`). |
|
|
28
|
+
| `openDelay` | `input<number \| undefined>` | Per-card override for open delay. Falls back to `provideForHoverCardDefaults` (700ms). |
|
|
29
|
+
| `closeDelay` | `input<number \| undefined>` | Per-card override for close delay. Falls back to `provideForHoverCardDefaults` (300ms). |
|
|
30
|
+
| `disabled` | `input<boolean>` | When true, hover / focus interaction is ignored AND any open card is force-closed (with `(openChange)` firing so a `[(open)]` binding stays in sync). |
|
|
31
|
+
|
|
32
|
+
## Outputs
|
|
33
|
+
|
|
34
|
+
| Output | Payload | Description |
|
|
35
|
+
| --------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
36
|
+
| `escapeKeyDown` | `KeyboardEvent` | Fires when Escape is pressed while the card is open, regardless of where focus currently lives (trigger, portaled content, or an unrelated element). Call `preventDefault()` to keep open. |
|
|
37
|
+
|
|
38
|
+
## Defaults
|
|
39
|
+
|
|
40
|
+
`provideForHoverCardDefaults` configures defaults for an injector subtree — at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root). Each call also establishes a fresh skip-delay coordinator scope: peer cards inside the scope share a skip-delay window (the next open is instant within `skipDelayDuration` after a peer closed); cards in other scopes don't.
|
|
41
|
+
|
|
42
|
+
| Key | Library fallback | Meaning |
|
|
43
|
+
| ------------------- | ---------------- | ------------------------------------------------------------------------- |
|
|
44
|
+
| `openDelay` | `700` | ms before showing after hover/focus enters. |
|
|
45
|
+
| `closeDelay` | `300` | ms before hiding after hover/focus leaves. |
|
|
46
|
+
| `skipDelayDuration` | `300` | Window (ms) after a peer closes during which the next open is instant. |
|
|
47
|
+
| `side` | `'top'` | Anchor side for cards that don't set `side` themselves. |
|
|
48
|
+
| `align` | `'center'` | Alignment along `side` for cards that don't set `align` themselves. |
|
|
49
|
+
| `sideOffset` | `8` | Main-axis gap (px) for cards that don't set `sideOffset` themselves. |
|
|
50
|
+
| `collisionPadding` | `8` | Collision-middleware padding (px) for cards that don't set it themselves. |
|
|
51
|
+
|
|
52
|
+
Per-instance inputs always win over the scope defaults.
|
|
53
|
+
|
|
54
|
+
The HoverCard coordinator is **independent** from `TooltipCoordinator` — the two patterns have different cadences and shouldn't share their skip-delay windows.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { provideForHoverCardDefaults } from 'forty-cdk/hover-card';
|
|
58
|
+
|
|
59
|
+
// Right-aligned profile cards app-wide
|
|
60
|
+
bootstrapApplication(App, {
|
|
61
|
+
providers: [provideForHoverCardDefaults({ side: 'right', sideOffset: 4 })],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// component-level override layers on top, per key
|
|
65
|
+
@Component({
|
|
66
|
+
providers: [provideForHoverCardDefaults({ openDelay: 200 })],
|
|
67
|
+
...
|
|
68
|
+
})
|
|
69
|
+
class ProfileList {}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { Component, signal } from '@angular/core';
|
|
76
|
+
import {
|
|
77
|
+
ForHoverCard,
|
|
78
|
+
ForHoverCardArrow,
|
|
79
|
+
ForHoverCardContent,
|
|
80
|
+
ForHoverCardTrigger,
|
|
81
|
+
} from 'forty-cdk/hover-card';
|
|
82
|
+
|
|
83
|
+
@Component({
|
|
84
|
+
selector: 'demo-profile-link',
|
|
85
|
+
imports: [ForHoverCard, ForHoverCardTrigger, ForHoverCardContent, ForHoverCardArrow],
|
|
86
|
+
template: `
|
|
87
|
+
<span forHoverCard #card="forHoverCard">
|
|
88
|
+
<a forHoverCardTrigger href="/users/ada">Ada Lovelace</a>
|
|
89
|
+
@if (card.open()) {
|
|
90
|
+
<div forHoverCardContent animate.enter="card-in" animate.leave="card-out">
|
|
91
|
+
<img src="/api/avatar/ada" alt="" width="64" height="64" />
|
|
92
|
+
<h3>Ada Lovelace</h3>
|
|
93
|
+
<p>Mathematician — designed the first algorithm.</p>
|
|
94
|
+
<span forHoverCardArrow class="arrow"></span>
|
|
95
|
+
</div>
|
|
96
|
+
}
|
|
97
|
+
</span>
|
|
98
|
+
`,
|
|
99
|
+
})
|
|
100
|
+
export class DemoProfileLink {}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Triggers stamped from outside-declared templates
|
|
104
|
+
|
|
105
|
+
Angular resolves `ng-template` DI at the template's **declaration** site, not where it is stamped. A `[forHoverCardTrigger]` declared in a template outside the root throws the orphan error even when the template is rendered inside the root via `ngTemplateOutlet`. For that case the selector attribute accepts the root reference as a value, `routerLink`-style — grab it with `#root="forHoverCard"` and pass it through the outlet context. The bare valueless attribute keeps resolving via DI.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<span forHoverCard #root="forHoverCard">
|
|
109
|
+
<ng-container *ngTemplateOutlet="trig; context: { root }" />
|
|
110
|
+
@if (root.open()) {
|
|
111
|
+
<div forHoverCardContent>…</div>
|
|
112
|
+
}
|
|
113
|
+
</span>
|
|
114
|
+
|
|
115
|
+
<ng-template #trig let-root="root">
|
|
116
|
+
<a [forHoverCardTrigger]="root" href="/users/ada">Ada Lovelace</a>
|
|
117
|
+
</ng-template>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Behavior notes
|
|
121
|
+
|
|
122
|
+
- **Arrow offset**: `[forHoverCardArrow]` writes `position: absolute`, the floating-ui-resolved `left` / `top`, and `var(--for-arrow-offset, 0px)` on the side opposite the card. Set `--for-arrow-offset` on the arrow element (or any ancestor) to control how far the arrow pokes out — typically a negative `px` value such as `-4px`. The helper ships no default visual.
|
|
123
|
+
|
|
124
|
+
## Styling
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
### Data attributes
|
|
129
|
+
|
|
130
|
+
| Piece | Attribute | Values |
|
|
131
|
+
| ----------------------- | ----------------------- | ------------------ |
|
|
132
|
+
| `[forHoverCard]` | `data-state` | `open` \| `closed` |
|
|
133
|
+
| `[forHoverCard]` | `data-disabled` | present \| absent |
|
|
134
|
+
| `[forHoverCardTrigger]` | `data-state` | `open` \| `closed` |
|
|
135
|
+
| `[forHoverCardContent]` | `data-state` | `open` \| `closed` |
|
|
136
|
+
| `[forHoverCardArrow]` | `data-hover-card-arrow` | present (always) |
|
|
137
|
+
|
|
138
|
+
### CSS custom properties
|
|
139
|
+
|
|
140
|
+
See also: [Styling floating content](../../../../../docs/styling-floating-content.md) — animation rules, standalone `scale`/`opacity`, and the arrow recipe.
|
|
141
|
+
|
|
142
|
+
`[forHoverCardContent]` is portaled to `document.body` and gets its position resolved by floating-ui. It exposes that geometry as custom properties on the content host (cleared on close), and `[forHoverCardArrow]` reads the consumer-settable `--for-arrow-offset`:
|
|
143
|
+
|
|
144
|
+
| Element | Custom property | Type / range | Direction | Meaning |
|
|
145
|
+
| ----------------------- | -------------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
|
|
146
|
+
| `[forHoverCardContent]` | `--for-anchor-width` | px | out | Trigger (reference) width. |
|
|
147
|
+
| `[forHoverCardContent]` | `--for-anchor-height` | px | out | Trigger (reference) height. |
|
|
148
|
+
| `[forHoverCardContent]` | `--for-available-width` | px | out | Space available along the inline axis (floating-ui `size` middleware) — clamp with `max-width`. |
|
|
149
|
+
| `[forHoverCardContent]` | `--for-available-height` | px | out | Space available along the block axis — clamp with `max-height`. |
|
|
150
|
+
| `[forHoverCardContent]` | `--for-content-transform-origin` | `<origin>` keywords | out | `transform-origin` matching the resolved side / align, so a `scale` enter animation pivots from the trigger. |
|
|
151
|
+
| `[forHoverCardArrow]` | `--for-arrow-offset` | px (default `0px`) | in | Consumer-set. How far the arrow pokes out past the card edge — typically a negative `px` (e.g. `-4px`). |
|
|
152
|
+
|
|
153
|
+
> `[forHoverCardContent]` (and the projected `[forHoverCardArrow]`) is portaled to `document.body`, so it sits outside your component's view-encapsulated styles. Style it with **global CSS or a class** you pass on the content element — component-scoped styles won't reach it. The positioner also writes the shared geometry custom properties listed above (`--for-anchor-width` / `-height`, `--for-available-width` / `-height`, `--for-content-transform-origin`); see [Styling floating content](../../../../../docs/styling-floating-content.md) for the full list and the side/align animation recipe.
|
|
154
|
+
|
|
155
|
+
```css
|
|
156
|
+
.card[data-state='open'] {
|
|
157
|
+
animation: card-in 120ms ease-out;
|
|
158
|
+
}
|
|
159
|
+
.card {
|
|
160
|
+
max-width: var(--for-available-width);
|
|
161
|
+
transform-origin: var(--for-content-transform-origin);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Accessibility notes
|
|
166
|
+
|
|
167
|
+
- **Not for tooltips.** If your overlay is a non-interactive label / hint, use `[forTooltip]`. HoverCard does not set `aria-describedby`; the trigger keeps its own label.
|
|
168
|
+
- **Trigger must stand alone.** Keyboard users don't see hover-only previews. Make sure the trigger's text / `aria-label` already describes its destination or action.
|
|
169
|
+
- **Focus opens the card** so keyboard users get the preview when tabbing through a list. Blur closes it; Escape closes immediately, no matter where focus currently lives — on the trigger, on a link inside the content, or on an unrelated element (the common case for a card opened by hover). Escape is routed through a document-level dismissable layer that is active only while the card is open.
|
|
170
|
+
- **Pointer interaction inside the card.** Moving the cursor from the trigger to the content cancels the close timer, so users can copy text or follow nested links. This holds even when the content overlaps its trigger (a negative `sideOffset`) and `closeDelay` is `0`: the trigger recognises a pointer leaving _into_ the card (via `relatedTarget`) and keeps the card open instead of flickering open/closed.
|
|
171
|
+
- **Use `provideForHoverCardDefaults` per scope** when you need a different cadence for, e.g., a list of profile cards (faster) vs. a sidebar of glossary entries (slower).
|
package/input/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Input / Textarea
|
|
2
|
+
|
|
3
|
+
Headless text form controls that implement Angular's `FormValueControl<string>` from `@angular/forms/signals`, so they auto-wire with `[formField]` and auto-associate inside a [`[forField]`](../field/README.md) — label, description, and error wiring — with zero extra markup.
|
|
4
|
+
|
|
5
|
+
These are thin wrappers, not re-implementations: the native `<input>` / `<textarea>` keeps its own `type`, caret, IME composition, and native form submission. The directive only bridges the value to a signal and reflects validation state.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Element | Role |
|
|
10
|
+
| ------------- | --------------- | ------------ | ------------------------- |
|
|
11
|
+
| `ForInput` | `[forInput]` | `<input>` | Single-line text control. |
|
|
12
|
+
| `ForTextarea` | `[forTextarea]` | `<textarea>` | Multi-line text control. |
|
|
13
|
+
|
|
14
|
+
Both expose the identical API below; `[forTextarea]` adds the optional `autosize` input.
|
|
15
|
+
|
|
16
|
+
## Inputs / models
|
|
17
|
+
|
|
18
|
+
| API | Type | Description |
|
|
19
|
+
| ---------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `value` | `model<string>` | Two-way bindable text value. Defaults to `''`; reflected as `data-empty` while empty. |
|
|
21
|
+
| `disabled` | `input<boolean>` | Reflects native `disabled` + `aria-disabled="true"` + `data-disabled`. |
|
|
22
|
+
| `readonly` | `input<boolean>` | Reflects native `readonly` + `aria-readonly="true"` + `data-readonly`. |
|
|
23
|
+
| `required` | `input<boolean>` | Reflects `aria-required="true"`. |
|
|
24
|
+
| `invalid` | `input<boolean>` | Reflects `aria-invalid="true"` + `data-invalid`. |
|
|
25
|
+
| `pending` | `input<boolean>` | Reflects `aria-busy="true"` + `data-pending` while async validation is in flight. |
|
|
26
|
+
| `dirty` | `input<boolean>` | Reflects `data-dirty`. |
|
|
27
|
+
| `name` | `input<string>` | Reflected on the native `name` attribute for form submission. |
|
|
28
|
+
| `errors` | `input<readonly ValidationError.WithOptionalFieldTree[]>` | Validation errors fed by `[formField]`. The directive does not render them — that is consumer territory. |
|
|
29
|
+
| `touched` | `model<boolean>` | Set to `true` on blur. Two-way so the field can read it back. |
|
|
30
|
+
| `autosize` | `input<boolean>` | `[forTextarea]` only. Grows/shrinks the height to fit content; reflects `data-autosize`. Defaults to `false`. |
|
|
31
|
+
|
|
32
|
+
The host gets `data-empty` (while the value is `''`), `data-disabled`, and `data-readonly` for CSS hooks, plus `data-touched` / `data-dirty` / `data-pending` / `data-invalid` from the shared form-control reflection.
|
|
33
|
+
|
|
34
|
+
## Auto-resizing textarea
|
|
35
|
+
|
|
36
|
+
`[forTextarea]` accepts an optional `autosize` input. When set, the textarea's height tracks its content — it grows as the value gets taller and shrinks back as it gets shorter, recomputed on every edit, on programmatic `value` writes, and on width reflow. The directive only sets the element's `height`; pair it with `resize: none; overflow: hidden;` (key off the reflected `data-autosize`) so the native resize grip and scrollbar don't fight the measured height.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Component, signal } from '@angular/core';
|
|
40
|
+
import { ForTextarea } from 'forty-cdk/input';
|
|
41
|
+
|
|
42
|
+
@Component({
|
|
43
|
+
selector: 'demo-comment',
|
|
44
|
+
imports: [ForTextarea],
|
|
45
|
+
template: `<textarea forTextarea autosize class="textarea" [(value)]="comment"></textarea>`,
|
|
46
|
+
})
|
|
47
|
+
export class DemoComment {
|
|
48
|
+
readonly comment = signal('');
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```css
|
|
53
|
+
.textarea[data-autosize] {
|
|
54
|
+
resize: none;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Auto-resize is a browser-only DOM side effect, so it is inert under server-side rendering and hydrates without a layout jump.
|
|
60
|
+
|
|
61
|
+
## Stand-alone usage
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Component, signal } from '@angular/core';
|
|
65
|
+
import { ForInput, ForTextarea } from 'forty-cdk/input';
|
|
66
|
+
|
|
67
|
+
@Component({
|
|
68
|
+
selector: 'demo-profile',
|
|
69
|
+
imports: [ForInput, ForTextarea],
|
|
70
|
+
template: `
|
|
71
|
+
<input forInput class="input" [(value)]="email" type="email" placeholder="you@example.com" />
|
|
72
|
+
<textarea forTextarea class="textarea" [(value)]="bio" placeholder="About you"></textarea>
|
|
73
|
+
<p>{{ email() }} — {{ bio().length }} chars</p>
|
|
74
|
+
`,
|
|
75
|
+
})
|
|
76
|
+
export class DemoProfile {
|
|
77
|
+
readonly email = signal('');
|
|
78
|
+
readonly bio = signal('');
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Field composition
|
|
83
|
+
|
|
84
|
+
Drop the control inside a `[forField]` and it auto-associates with the label, description, and error region — no `id` / `aria-*` wiring by hand.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { Component, signal } from '@angular/core';
|
|
88
|
+
import { form, required } from '@angular/forms/signals';
|
|
89
|
+
import { FormField } from '@angular/forms/signals';
|
|
90
|
+
import { ForField, ForFieldError, ForLabel } from 'forty-cdk/field';
|
|
91
|
+
import { ForInput } from 'forty-cdk/input';
|
|
92
|
+
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'demo-signup',
|
|
95
|
+
imports: [ForField, ForLabel, ForFieldError, ForInput, FormField],
|
|
96
|
+
template: `
|
|
97
|
+
<form>
|
|
98
|
+
<div forField>
|
|
99
|
+
<label forLabel>Full name</label>
|
|
100
|
+
<input forInput class="input" [formField]="profile.name" />
|
|
101
|
+
@if (err.shown()) {
|
|
102
|
+
<p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
|
|
103
|
+
}
|
|
104
|
+
</div>
|
|
105
|
+
</form>
|
|
106
|
+
`,
|
|
107
|
+
})
|
|
108
|
+
export class DemoSignup {
|
|
109
|
+
readonly model = signal({ name: '' });
|
|
110
|
+
readonly profile = form(this.model, (p) => {
|
|
111
|
+
required(p.name, { message: 'Name is required' });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`[formField]` detects the `FormValueControl<string>` interface and wires everything — value, disabled, required, invalid, errors, touched — without any glue.
|
|
117
|
+
|
|
118
|
+
## Styling
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
`[forInput]` and `[forTextarea]` reflect the identical set of attributes on their native host element.
|
|
123
|
+
|
|
124
|
+
### Data attributes
|
|
125
|
+
|
|
126
|
+
| Piece | Attribute | Values |
|
|
127
|
+
| ----------------------------- | --------------- | -------------------------------- |
|
|
128
|
+
| `[forInput]`, `[forTextarea]` | `data-empty` | present (value is `''`) / absent |
|
|
129
|
+
| `[forInput]`, `[forTextarea]` | `data-disabled` | present / absent |
|
|
130
|
+
| `[forInput]`, `[forTextarea]` | `data-readonly` | present / absent |
|
|
131
|
+
| `[forInput]`, `[forTextarea]` | `data-touched` | present / absent |
|
|
132
|
+
| `[forInput]`, `[forTextarea]` | `data-dirty` | present / absent |
|
|
133
|
+
| `[forInput]`, `[forTextarea]` | `data-pending` | present / absent |
|
|
134
|
+
| `[forInput]`, `[forTextarea]` | `data-invalid` | present / absent |
|
|
135
|
+
| `[forTextarea]` | `data-autosize` | present (`autosize` on) / absent |
|
|
136
|
+
|
|
137
|
+
```css
|
|
138
|
+
.input[data-invalid] {
|
|
139
|
+
border-color: red;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.input[data-empty]::placeholder {
|
|
143
|
+
opacity: 0.5;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Accessibility notes
|
|
148
|
+
|
|
149
|
+
- **The native element is the control.** It stays the focusable, submittable form field, so screen readers, mobile keyboards (`type`, `inputmode`), autofill, and native validation all behave exactly as they would on a bare `<input>` / `<textarea>`.
|
|
150
|
+
- **No hidden input.** Because the visible element carries `name` and its `.value` _is_ the form value, the browser serializes it natively — unlike `ForSwitch` (a `<button>`) or `ForNumberInput` (formatted display), which mount a hidden input. A disabled control is skipped by native serialization automatically.
|
|
151
|
+
- **Falsy state styling selects on absence.** `aria-disabled` / `aria-readonly` / `aria-required` / `aria-invalid` / `aria-busy` are emitted only when truthy — style the off state with `:not([aria-invalid])`, never `[aria-invalid="false"]`.
|
|
152
|
+
- **`@angular/forms` is an optional peer.** If you're not using Signal Forms, don't install it — the directive runs fine on a plain `[(value)]` binding (the only `@angular/forms/signals` reference is a type import, erased at build).
|
|
153
|
+
|
|
154
|
+
## Wrapping in a design system
|
|
155
|
+
|
|
156
|
+
Both supported wrapper patterns — `hostDirectives` with the exported `FOR_INPUT_HOST_DIRECTIVE_INPUTS` / `FOR_INPUT_HOST_DIRECTIVE_OUTPUTS` and `FOR_TEXTAREA_HOST_DIRECTIVE_INPUTS` / `FOR_TEXTAREA_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing — are documented in [Wrapping form primitives](../../../../../docs/wrapping-form-primitives.md).
|