forty-cdk 0.25.2 → 0.27.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 +86 -38
- package/aspect-ratio/README.md +17 -34
- package/avatar/README.md +35 -37
- package/breadcrumbs/README.md +54 -13
- package/breakpoints/README.md +80 -24
- package/button/README.md +42 -15
- package/calendar/README.md +171 -49
- package/carousel/README.md +200 -56
- package/checkbox/README.md +73 -36
- package/combobox/README.md +245 -107
- package/context-menu/README.md +105 -99
- package/date-field/README.md +57 -29
- package/date-picker/README.md +118 -68
- package/dialog/README.md +247 -198
- package/disclosure/README.md +37 -32
- package/drag-drop/README.md +157 -59
- package/drawer/README.md +183 -63
- package/dropdown-menu/README.md +113 -115
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +29 -6
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +86 -46
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +86 -10
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +7 -5
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-dialog.mjs +7 -1
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +14 -6
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +61 -25
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-fieldset.mjs +6 -1
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +23 -12
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs +4 -2
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-menubar.mjs +18 -4
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +5 -3
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +14 -15
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +3 -3
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-tooltip.mjs +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +208 -59
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/field/README.md +53 -43
- package/fieldset/README.md +39 -34
- package/file-upload/README.md +80 -32
- package/hover-card/README.md +79 -54
- package/input/README.md +93 -85
- package/internationalized-date/README.md +5 -3
- package/listbox/README.md +112 -57
- package/menu/README.md +234 -62
- package/menubar/README.md +84 -64
- package/meter/README.md +37 -39
- package/navigation-menu/README.md +138 -45
- package/number-input/README.md +32 -31
- package/otp-input/README.md +74 -70
- package/package.json +1 -1
- package/pagination/README.md +75 -11
- package/pane-resizer/README.md +46 -56
- package/popover/README.md +90 -63
- package/progress/README.md +28 -44
- package/radio-group/README.md +77 -30
- package/scroll-area/README.md +92 -117
- package/search/README.md +113 -46
- package/select/README.md +258 -104
- package/separator/README.md +25 -26
- package/shared/README.md +30 -24
- package/slider/README.md +64 -38
- package/stepper/README.md +191 -112
- package/switch/README.md +36 -30
- package/table/README.md +381 -186
- package/table-virtualization/README.md +29 -27
- package/tabs/README.md +79 -40
- package/time-field/README.md +53 -20
- package/time-picker/README.md +104 -43
- package/toast/README.md +174 -98
- package/toggle/README.md +79 -70
- package/toolbar/README.md +85 -26
- package/tooltip/README.md +90 -70
- package/tree/README.md +244 -131
- package/types/forty-cdk-checkbox.d.ts +1 -1
- package/types/forty-cdk-combobox.d.ts +11 -3
- package/types/forty-cdk-core-overlay.d.ts +35 -13
- package/types/forty-cdk-core.d.ts +89 -18
- package/types/forty-cdk-dialog.d.ts +2 -1
- package/types/forty-cdk-drag-drop.d.ts +2 -2
- package/types/forty-cdk-field.d.ts +26 -9
- package/types/forty-cdk-listbox.d.ts +8 -1
- package/types/forty-cdk-menu.d.ts +12 -10
- package/types/forty-cdk-menubar.d.ts +21 -3
- package/types/forty-cdk-radio-group.d.ts +1 -1
- package/types/forty-cdk-select.d.ts +1 -1
- package/types/forty-cdk-table.d.ts +1 -1
- package/types/forty-cdk-tooltip.d.ts +6 -5
- package/types/forty-cdk-tree.d.ts +66 -10
- package/virtual-reorder/README.md +25 -23
- package/virtualization/README.md +132 -43
- package/visually-hidden/README.md +69 -32
package/accordion/README.md
CHANGED
|
@@ -9,6 +9,12 @@ apgUrl: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
|
|
|
9
9
|
|
|
10
10
|
A stack of collapsible sections, optionally allowing multiple panels open at once.
|
|
11
11
|
|
|
12
|
+
## When to choose
|
|
13
|
+
|
|
14
|
+
- **Accordion**: a group of collapsible items under one root. `[(value)]` holds which are open, `multiple` decides whether more than one may be, and ArrowUp / ArrowDown / Home / End move focus across the triggers.
|
|
15
|
+
- **[Disclosure](../disclosure/README.md)**: a single trigger and its region, with no shared state and no arrow-key navigation. Stacking several of them is not an accordion, and that is the right shape when the panels are unrelated.
|
|
16
|
+
- **[Tabs](../tabs/README.md)**: when exactly one panel is ever visible and the panels are alternatives rather than sections the reader may open together.
|
|
17
|
+
|
|
12
18
|
## Anatomy
|
|
13
19
|
|
|
14
20
|
```html
|
|
@@ -25,8 +31,10 @@ A stack of collapsible sections, optionally allowing multiple panels open at onc
|
|
|
25
31
|
|
|
26
32
|
## Examples
|
|
27
33
|
|
|
34
|
+
Open a panel with the pointer or `Enter`, move between headers with the arrow keys, and watch `data-state` flip on the item, its trigger and its content together.
|
|
35
|
+
|
|
28
36
|
```ts
|
|
29
|
-
import { Component, signal } from '@angular/core';
|
|
37
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
30
38
|
import {
|
|
31
39
|
ForAccordion,
|
|
32
40
|
ForAccordionContent,
|
|
@@ -34,43 +42,83 @@ import {
|
|
|
34
42
|
ForAccordionTrigger,
|
|
35
43
|
} from 'forty-cdk/accordion';
|
|
36
44
|
|
|
45
|
+
interface AccordionEntry {
|
|
46
|
+
readonly value: string;
|
|
47
|
+
readonly title: string;
|
|
48
|
+
readonly body: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
@Component({
|
|
38
|
-
selector: '
|
|
52
|
+
selector: 'app-accordion-default-example',
|
|
53
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
39
54
|
imports: [ForAccordion, ForAccordionItem, ForAccordionTrigger, ForAccordionContent],
|
|
40
55
|
template: `
|
|
41
|
-
<div forAccordion [(value)]="
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
<div forAccordion class="acc-root" [(value)]="value" collapsible>
|
|
57
|
+
@for (item of items; track item.value) {
|
|
58
|
+
<div forAccordionItem class="acc-item" [value]="item.value">
|
|
59
|
+
<h3 class="acc-heading">
|
|
60
|
+
<button type="button" forAccordionTrigger class="acc-trigger">
|
|
61
|
+
<span>{{ item.title }}</span>
|
|
62
|
+
<span class="chevron" aria-hidden="true"></span>
|
|
63
|
+
</button>
|
|
64
|
+
</h3>
|
|
65
|
+
<section forAccordionContent class="acc-content">
|
|
66
|
+
<div class="acc-inner">
|
|
67
|
+
<p>{{ item.body }}</p>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
54
72
|
</div>
|
|
55
73
|
`,
|
|
56
74
|
})
|
|
57
|
-
export class
|
|
58
|
-
readonly
|
|
75
|
+
export class AccordionDefaultExample {
|
|
76
|
+
protected readonly items: readonly AccordionEntry[] = [
|
|
77
|
+
{
|
|
78
|
+
value: 'a',
|
|
79
|
+
title: 'What is forty-cdk?',
|
|
80
|
+
body: 'A library of headless UI primitives with built-in WAI-ARIA accessibility.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
value: 'b',
|
|
84
|
+
title: 'Does it ship styles?',
|
|
85
|
+
body: 'No. It exposes state, behavior, focus and ARIA; you apply the styles yourself.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
value: 'c',
|
|
89
|
+
title: 'Does it work without Zone.js?',
|
|
90
|
+
body: 'Yes, it is designed to run under provideZonelessChangeDetection().',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
protected readonly value = signal<readonly string[]>(['a']);
|
|
59
95
|
}
|
|
60
96
|
```
|
|
61
97
|
|
|
98
|
+
### Multiple
|
|
99
|
+
|
|
100
|
+
`multiple` lets several sections stay open at once, so `value` holds an array of every open item.
|
|
101
|
+
|
|
102
|
+
### Horizontal
|
|
103
|
+
|
|
104
|
+
`orientation='horizontal'` lays the sections out in a row and switches roving navigation to `ArrowLeft` / `ArrowRight`. It is reflected as `data-orientation` for styling.
|
|
105
|
+
|
|
106
|
+
### Disabled item
|
|
107
|
+
|
|
108
|
+
A disabled item cannot be toggled and is skipped by the arrow keys, while staying in the DOM for screen readers.
|
|
109
|
+
|
|
62
110
|
## API
|
|
63
111
|
|
|
64
112
|
### `ForAccordion`
|
|
65
113
|
|
|
66
|
-
| Property | Type | Description
|
|
67
|
-
| ------------- | ----------------------------------- |
|
|
68
|
-
| `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** —
|
|
69
|
-
| `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously.<br>**Default:** `false`
|
|
70
|
-
| `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Otherwise once any item is open, exactly one stays open.<br>**Default:** `false`
|
|
71
|
-
| `disabled` | `input<boolean>` | When true, disables every item
|
|
72
|
-
| `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction of the trigger list. In horizontal mode ArrowLeft/Right replace ArrowUp/Down.<br>**Default:** `'vertical'`
|
|
73
|
-
| `dir` | `input<'ltr' \| 'rtl'>` | Writing direction. Only relevant in horizontal mode
|
|
114
|
+
| Property | Type | Description |
|
|
115
|
+
| ------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** — |
|
|
117
|
+
| `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously.<br>**Default:** `false` |
|
|
118
|
+
| `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Otherwise once any item is open, exactly one stays open.<br>**Default:** `false` |
|
|
119
|
+
| `disabled` | `input<boolean>` | When true, disables every item: each trigger reflects the native `disabled` attribute and cannot toggle. Composes with a per-item `[disabled]`.<br>**Default:** `false` |
|
|
120
|
+
| `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction of the trigger list. In horizontal mode ArrowLeft/Right replace ArrowUp/Down.<br>**Default:** `'vertical'` |
|
|
121
|
+
| `dir` | `input<'ltr' \| 'rtl'>` | Writing direction. Only relevant in horizontal mode, where it swaps the meaning of Left/Right arrows.<br>**Default:** — |
|
|
74
122
|
|
|
75
123
|
| Data attribute | Values |
|
|
76
124
|
| ------------------ | -------------------------- |
|
|
@@ -106,35 +154,35 @@ export class DemoFaq {
|
|
|
106
154
|
|
|
107
155
|
## Keyboard
|
|
108
156
|
|
|
109
|
-
| Key | Action
|
|
110
|
-
| -------------------------------------------- |
|
|
111
|
-
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Toggle the focused trigger (native button).
|
|
112
|
-
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Move focus between triggers (vertical, default). Wrap-around, skips disabled.
|
|
113
|
-
| <kbd>ArrowLeft</kbd> / <kbd>ArrowRight</kbd> | Move focus between triggers (horizontal
|
|
114
|
-
| <kbd>Home</kbd> | Jump to the first trigger.
|
|
115
|
-
| <kbd>End</kbd> | Jump to the last trigger.
|
|
157
|
+
| Key | Action |
|
|
158
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Toggle the focused trigger (native button). |
|
|
160
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Move focus between triggers (vertical, default). Wrap-around, skips disabled. |
|
|
161
|
+
| <kbd>ArrowLeft</kbd> / <kbd>ArrowRight</kbd> | Move focus between triggers (horizontal, flipped under `dir='rtl'`). Wrap-around, skips disabled. |
|
|
162
|
+
| <kbd>Home</kbd> | Jump to the first trigger. |
|
|
163
|
+
| <kbd>End</kbd> | Jump to the last trigger. |
|
|
116
164
|
|
|
117
165
|
## Accessibility
|
|
118
166
|
|
|
119
|
-
- **Heading wrapper is your job.** The library does not render a heading around the trigger
|
|
167
|
+
- **Heading wrapper is your job.** The library does not render a heading around the trigger, so wrap it in the heading level (`<h2>`–`<h6>`) appropriate to your document outline. Without it, screen-reader landmark navigation is broken.
|
|
120
168
|
- **Use a real `<button type="button">` for the trigger.** Native Enter / Space activation and focus come for free; the directive does not synthesize them.
|
|
121
169
|
- **`role="region"`** is added to every panel automatically. APG recommends suppressing it on accordions with 6+ panels to avoid landmark proliferation; there is currently no opt-out.
|
|
122
170
|
- **Closed panels leave the accessibility tree.** While closed, `ForAccordionContent` sets `aria-hidden="true"` and `inert` on the panel, removing it from both the accessibility tree and the focus order. The directive does **not** apply `[hidden]`, so pick how to hide it visually:
|
|
123
|
-
- **Mount / unmount with `@if (item.expanded())
|
|
124
|
-
- **Leave it mounted
|
|
171
|
+
- **Mount / unmount with `@if (item.expanded())`**: the panel is absent from the DOM while closed; the cleanest path for `animate.enter` / `animate.leave`. The trigger emits `aria-controls` only while expanded, so the reference never dangles at an unmounted panel.
|
|
172
|
+
- **Leave it mounted**: preserve internal state or run CSS-only transitions off `data-state`. Add `display: none` (or your own collapse animation) keyed on `[data-state="closed"]` to also hide it visually.
|
|
125
173
|
- **`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.
|
|
126
174
|
- **A truly disabled item (`[disabled]` on `[forAccordionItem]`) uses the native `disabled` attribute on the trigger, by design.** 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.
|
|
127
175
|
|
|
128
176
|
## Styling
|
|
129
177
|
|
|
130
|
-
forty-cdk ships no styles. Add your own class to each piece
|
|
178
|
+
forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed per piece in the [API](#api) section.
|
|
131
179
|
|
|
132
180
|
```css
|
|
133
|
-
.
|
|
181
|
+
.chevron {
|
|
134
182
|
transition: transform 150ms ease;
|
|
135
183
|
}
|
|
136
184
|
|
|
137
|
-
.
|
|
185
|
+
.acc-trigger[data-state='open'] .chevron {
|
|
138
186
|
transform: rotate(180deg);
|
|
139
187
|
}
|
|
140
188
|
```
|
package/aspect-ratio/README.md
CHANGED
|
@@ -8,21 +8,21 @@ archetype: [composable-ui]
|
|
|
8
8
|
|
|
9
9
|
A container that keeps its content at a fixed width-to-height ratio.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
It is a pure visual utility that locks an element's box via the native CSS `aspect-ratio` property, with no ARIA semantics. Reach for it to reserve space for media before it loads (preventing layout shift), keep cards on a grid uniform, or wrap responsive iframes.
|
|
12
12
|
|
|
13
13
|
## Why this exists
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
You don't need this primitive for a fixed, never-changing ratio, which is one line of CSS:
|
|
16
16
|
|
|
17
17
|
```css
|
|
18
|
-
.
|
|
18
|
+
.box {
|
|
19
19
|
aspect-ratio: 16 / 9;
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
`[forAspectRatio]` earns its place when the ratio is **dynamic or must be validated**. It is more than the static declaration:
|
|
24
24
|
|
|
25
|
-
- **Reactive `ratio` input.** Bind `[ratio]="ratio()"` and the host style recomputes as the value changes
|
|
25
|
+
- **Reactive `ratio` input.** Bind `[ratio]="ratio()"` and the host style recomputes as the value changes, with no manual style writes.
|
|
26
26
|
- **Invalid-value guarding.** `0`, negative, and non-finite ratios fall back to `1`, so a bad computed value never emits invalid CSS.
|
|
27
27
|
- **SSR-safe.** The `aspect-ratio` style is bound declaratively (never touched imperatively), so it renders identically on the server and hydrates cleanly.
|
|
28
28
|
- **Consistent headless API.** Same shape as the other primitives, so it composes the same way.
|
|
@@ -39,46 +39,29 @@ If your ratio is a literal constant, prefer the CSS property directly and keep t
|
|
|
39
39
|
|
|
40
40
|
## Examples
|
|
41
41
|
|
|
42
|
+
Resize the preview and watch the frame hold its 16 / 9 ratio. The primitive writes the ratio and nothing else, so every border, colour and inset below is your own CSS.
|
|
43
|
+
|
|
42
44
|
```ts
|
|
43
|
-
import { Component } from '@angular/core';
|
|
45
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
44
46
|
import { ForAspectRatio } from 'forty-cdk/aspect-ratio';
|
|
45
47
|
|
|
46
48
|
@Component({
|
|
47
|
-
selector: '
|
|
49
|
+
selector: 'app-aspect-ratio-default-example',
|
|
50
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
48
51
|
imports: [ForAspectRatio],
|
|
49
52
|
template: `
|
|
50
|
-
<div forAspectRatio [ratio]="16 / 9"
|
|
51
|
-
<
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div forAspectRatio ratio="1" class="avatar">
|
|
55
|
-
<img src="me.jpg" alt="Me" />
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<div forAspectRatio [ratio]="21 / 9" class="hero">
|
|
59
|
-
<video src="hero.mp4" autoplay loop muted></video>
|
|
53
|
+
<div forAspectRatio class="box" [ratio]="16 / 9">
|
|
54
|
+
<span class="label">16 / 9</span>
|
|
60
55
|
</div>
|
|
61
56
|
`,
|
|
62
|
-
styles: [
|
|
63
|
-
`
|
|
64
|
-
.card-cover,
|
|
65
|
-
.avatar,
|
|
66
|
-
.hero {
|
|
67
|
-
width: 100%;
|
|
68
|
-
}
|
|
69
|
-
.card-cover img,
|
|
70
|
-
.avatar img,
|
|
71
|
-
.hero video {
|
|
72
|
-
width: 100%;
|
|
73
|
-
height: 100%;
|
|
74
|
-
object-fit: cover;
|
|
75
|
-
}
|
|
76
|
-
`,
|
|
77
|
-
],
|
|
78
57
|
})
|
|
79
|
-
export class
|
|
58
|
+
export class AspectRatioDefaultExample {}
|
|
80
59
|
```
|
|
81
60
|
|
|
61
|
+
### Square (1 / 1)
|
|
62
|
+
|
|
63
|
+
Set `ratio` to `1` to keep a box perfectly square at any width. That is handy for avatars, thumbnails, or uniform grid cards.
|
|
64
|
+
|
|
82
65
|
## API
|
|
83
66
|
|
|
84
67
|
### `ForAspectRatio`
|
|
@@ -93,7 +76,7 @@ export class DemoAspectRatio {}
|
|
|
93
76
|
|
|
94
77
|
## Styling
|
|
95
78
|
|
|
96
|
-
forty-cdk ships no styles. Add your own class to each piece
|
|
79
|
+
forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). This primitive is purely structural: its only host effect is the native `aspect-ratio` style, so it reflects no `data-*` attributes and writes no CSS custom properties. Style the host through your own class on `[forAspectRatio]`.
|
|
97
80
|
|
|
98
81
|
## Behavior notes
|
|
99
82
|
|
package/avatar/README.md
CHANGED
|
@@ -8,7 +8,7 @@ archetype: [composable-ui]
|
|
|
8
8
|
|
|
9
9
|
A user image with a graceful fallback across its loading lifecycle.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
It is headless and presentational: it tracks the load lifecycle of an `<img>` and lets you choose what to show while loading or after an error. There is no WAI-ARIA pattern for avatars, so the directive imposes no `role` of its own.
|
|
12
12
|
|
|
13
13
|
## Anatomy
|
|
14
14
|
|
|
@@ -22,52 +22,50 @@ Headless and presentational — it tracks the load lifecycle of an `<img>` and l
|
|
|
22
22
|
|
|
23
23
|
## Examples
|
|
24
24
|
|
|
25
|
+
Let the image load, then break its URL: `data-status` moves between `loading`, `loaded` and `error`, and the fallback only appears once the delay has passed without an image.
|
|
26
|
+
|
|
25
27
|
```ts
|
|
26
|
-
import {
|
|
28
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
27
29
|
import { ForAvatar, ForAvatarFallback, ForAvatarImage } from 'forty-cdk/avatar';
|
|
28
30
|
|
|
31
|
+
const AVATAR_SRC =
|
|
32
|
+
'data:image/svg+xml;utf8,' +
|
|
33
|
+
encodeURIComponent(
|
|
34
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 72 72">
|
|
35
|
+
<defs>
|
|
36
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
37
|
+
<stop offset="0" stop-color="#6366f1" />
|
|
38
|
+
<stop offset="1" stop-color="#ec4899" />
|
|
39
|
+
</linearGradient>
|
|
40
|
+
</defs>
|
|
41
|
+
<rect width="72" height="72" fill="url(#g)" />
|
|
42
|
+
<circle cx="36" cy="28" r="14" fill="#fff" opacity="0.92" />
|
|
43
|
+
<path d="M14 64c0-12 9.8-20 22-20s22 8 22 20Z" fill="#fff" opacity="0.92" />
|
|
44
|
+
</svg>`,
|
|
45
|
+
);
|
|
46
|
+
|
|
29
47
|
@Component({
|
|
30
|
-
selector: '
|
|
48
|
+
selector: 'app-avatar-default-example',
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
31
50
|
imports: [ForAvatar, ForAvatarImage, ForAvatarFallback],
|
|
32
51
|
template: `
|
|
33
|
-
<span forAvatar #
|
|
34
|
-
<img forAvatarImage class="avatar-image" [src]="
|
|
35
|
-
@if (
|
|
36
|
-
<span forAvatarFallback class="avatar-fallback">
|
|
52
|
+
<span forAvatar #avatar="forAvatar" class="avatar" [fallbackDelayMs]="500">
|
|
53
|
+
<img forAvatarImage class="avatar-image" [src]="src" alt="Ada Lovelace" />
|
|
54
|
+
@if (avatar.shouldShowFallback()) {
|
|
55
|
+
<span forAvatarFallback class="avatar-fallback">AL</span>
|
|
37
56
|
}
|
|
38
57
|
</span>
|
|
39
58
|
`,
|
|
40
|
-
styles: [
|
|
41
|
-
`
|
|
42
|
-
.avatar {
|
|
43
|
-
display: inline-flex;
|
|
44
|
-
width: 40px;
|
|
45
|
-
height: 40px;
|
|
46
|
-
border-radius: 999px;
|
|
47
|
-
overflow: hidden;
|
|
48
|
-
background: #eee;
|
|
49
|
-
font: 600 14px/40px system-ui;
|
|
50
|
-
align-items: center;
|
|
51
|
-
justify-content: center;
|
|
52
|
-
}
|
|
53
|
-
.avatar-image {
|
|
54
|
-
width: 100%;
|
|
55
|
-
height: 100%;
|
|
56
|
-
object-fit: cover;
|
|
57
|
-
}
|
|
58
|
-
.avatar-image[data-status='loading'],
|
|
59
|
-
.avatar-image[data-status='error'] {
|
|
60
|
-
display: none;
|
|
61
|
-
}
|
|
62
|
-
`,
|
|
63
|
-
],
|
|
64
59
|
})
|
|
65
|
-
export class
|
|
66
|
-
readonly
|
|
67
|
-
readonly initials = signal('AL');
|
|
60
|
+
export class AvatarDefaultExample {
|
|
61
|
+
protected readonly src = AVATAR_SRC;
|
|
68
62
|
}
|
|
69
63
|
```
|
|
70
64
|
|
|
65
|
+
### Failed load
|
|
66
|
+
|
|
67
|
+
When the image errors, the directive flips `shouldShowFallback()` and the initials render in its place. An error shows the fallback at once, skipping the `fallbackDelayMs` wait.
|
|
68
|
+
|
|
71
69
|
## API
|
|
72
70
|
|
|
73
71
|
### `ForAvatar`
|
|
@@ -104,7 +102,7 @@ The directive does not impose a `role`. Pair the avatar with visible name text o
|
|
|
104
102
|
|
|
105
103
|
## Styling
|
|
106
104
|
|
|
107
|
-
forty-cdk ships no styles. Add your own class to each piece
|
|
105
|
+
forty-cdk ships no styles. Add your own class to each piece. The `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed per piece in the [API](#api) section.
|
|
108
106
|
|
|
109
107
|
```css
|
|
110
108
|
.avatar-image:not([data-status='loaded']) {
|
|
@@ -117,9 +115,9 @@ forty-cdk ships no styles. Add your own class to each piece — the `for*` selec
|
|
|
117
115
|
|
|
118
116
|
## Behavior notes
|
|
119
117
|
|
|
120
|
-
- **Cached images are detected on first render.** If the browser already has the image cached, `load`/`error` may not fire
|
|
118
|
+
- **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`.
|
|
121
119
|
- **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.
|
|
122
|
-
- **`alt` is consumer territory.** Because `<img>` is the host element, the consumer keeps full control of `alt
|
|
120
|
+
- **`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.
|
|
123
121
|
- **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.
|
|
124
122
|
|
|
125
123
|
## Wrapping in a design system
|
package/breadcrumbs/README.md
CHANGED
|
@@ -23,34 +23,75 @@ A labelled navigation landmark for a breadcrumb trail: links with aria-current='
|
|
|
23
23
|
|
|
24
24
|
## Examples
|
|
25
25
|
|
|
26
|
+
Walk the trail with `Tab`: the last crumb is the page you are on, so it carries `aria-current="page"` and is not a link back to itself.
|
|
27
|
+
|
|
26
28
|
```ts
|
|
27
|
-
import { Component } from '@angular/core';
|
|
29
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
28
30
|
import { ForBreadcrumbItem, ForBreadcrumbSeparator, ForBreadcrumbs } from 'forty-cdk/breadcrumbs';
|
|
29
31
|
|
|
32
|
+
interface Crumb {
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly href: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
@Component({
|
|
31
|
-
selector: '
|
|
38
|
+
selector: 'app-breadcrumbs-default-example',
|
|
39
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
32
40
|
imports: [ForBreadcrumbs, ForBreadcrumbItem, ForBreadcrumbSeparator],
|
|
33
41
|
template: `
|
|
34
|
-
<nav forBreadcrumbs>
|
|
35
|
-
<ol>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
<nav forBreadcrumbs class="bc">
|
|
43
|
+
<ol class="bc-list">
|
|
44
|
+
@for (crumb of crumbs(); track crumb.href; let last = $last) {
|
|
45
|
+
<li class="bc-li">
|
|
46
|
+
<a
|
|
47
|
+
forBreadcrumbItem
|
|
48
|
+
class="bc-link"
|
|
49
|
+
[href]="crumb.href"
|
|
50
|
+
[current]="last"
|
|
51
|
+
(click)="$event.preventDefault()"
|
|
52
|
+
>
|
|
53
|
+
{{ crumb.label }}
|
|
54
|
+
</a>
|
|
55
|
+
</li>
|
|
56
|
+
@if (!last) {
|
|
57
|
+
<li forBreadcrumbSeparator class="bc-sep">
|
|
58
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
59
|
+
<path
|
|
60
|
+
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
stroke-width="1.75"
|
|
64
|
+
stroke-linecap="round"
|
|
65
|
+
stroke-linejoin="round"
|
|
66
|
+
/>
|
|
67
|
+
</svg>
|
|
68
|
+
</li>
|
|
69
|
+
}
|
|
70
|
+
}
|
|
41
71
|
</ol>
|
|
42
72
|
</nav>
|
|
43
73
|
`,
|
|
44
74
|
})
|
|
45
|
-
export class
|
|
75
|
+
export class BreadcrumbsDefaultExample {
|
|
76
|
+
protected readonly crumbs = signal<readonly Crumb[]>([
|
|
77
|
+
{ label: 'Home', href: '/' },
|
|
78
|
+
{ label: 'Components', href: '/components' },
|
|
79
|
+
{ label: 'Navigation', href: '/components/navigation' },
|
|
80
|
+
{ label: 'Breadcrumbs', href: '/components/navigation/breadcrumbs' },
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
46
83
|
```
|
|
47
84
|
|
|
48
|
-
|
|
85
|
+
### Collapsing a long trail
|
|
86
|
+
|
|
87
|
+
The primitive renders whatever items you give it, so collapsing a deep path is a consumer decision. Here the middle is folded into an expandable ellipsis button that reveals the hidden crumbs. The trail stays a single accessible navigation landmark either way.
|
|
49
88
|
|
|
50
|
-
|
|
89
|
+
## Localizing the label
|
|
51
90
|
|
|
52
91
|
`Breadcrumb` is verbalized by screen readers, so translate it per injector scope with `provideForBreadcrumbsDefaults`. Configure it at the application root, or in any component's `providers` to scope the translation to a subtree. A per-instance `[ariaLabel]` still wins over the scope default.
|
|
53
92
|
|
|
93
|
+
<!-- snippet: fragment -->
|
|
94
|
+
|
|
54
95
|
```ts
|
|
55
96
|
import { provideForBreadcrumbsDefaults } from 'forty-cdk/breadcrumbs';
|
|
56
97
|
|
|
@@ -77,7 +118,7 @@ bootstrapApplication(App, {
|
|
|
77
118
|
|
|
78
119
|
Implements the [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/).
|
|
79
120
|
|
|
80
|
-
- **Navigation landmark.** `[forBreadcrumbs]` applies `role="navigation"` and labels it `aria-label="Breadcrumb"` by default, creating a named landmark that screen-reader users can jump to directly.
|
|
121
|
+
- **Navigation landmark.** `[forBreadcrumbs]` applies `role="navigation"` and labels it `aria-label="Breadcrumb"` by default, creating a named landmark that screen-reader users can jump to directly. When a page hosts more than one trail, name each with `[ariaLabel]`, or point a native `aria-labelledby` at a visible heading.
|
|
81
122
|
- **Current page.** Set `current` on `[forBreadcrumbItem]` for the active page; the directive reflects `aria-current="page"` so assistive technology announces the user's location in the trail.
|
|
82
123
|
- **Decorative separators.** `[forBreadcrumbSeparator]` reflects `aria-hidden="true"` so the visual divider (e.g. `/`) is skipped by screen readers.
|
|
83
124
|
|