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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
Headless pagination control implementing a [WAI-ARIA `navigation` landmark](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/navigation.html) with [`aria-current="page"`](https://www.w3.org/TR/wai-aria-1.2/#aria-current) on the active page. Derives the visible page list (with ellipsis gaps) from `page`, `count`, `siblingCount`, and `boundaryCount`. Ships no styles — apply your own.
|
|
4
|
+
|
|
5
|
+
## Pieces
|
|
6
|
+
|
|
7
|
+
| Class | Selector | Role |
|
|
8
|
+
| ----------------------- | ------------------------- | ----------------------------------------------------------------------- |
|
|
9
|
+
| `ForPagination` | `[forPagination]` | Root. `role="navigation"`. Owns the page model and computed items list. |
|
|
10
|
+
| `ForPaginationItem` | `[forPaginationItem]` | Page number button. `aria-current="page"` when current. |
|
|
11
|
+
| `ForPaginationPrevious` | `[forPaginationPrevious]` | Previous-page button. Native `disabled` at the first page. |
|
|
12
|
+
| `ForPaginationNext` | `[forPaginationNext]` | Next-page button. Native `disabled` at the last page. |
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { Component, signal } from '@angular/core';
|
|
18
|
+
import {
|
|
19
|
+
ForPagination,
|
|
20
|
+
ForPaginationItem,
|
|
21
|
+
ForPaginationNext,
|
|
22
|
+
ForPaginationPrevious,
|
|
23
|
+
} from 'forty-cdk/pagination';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'demo-pagination',
|
|
27
|
+
imports: [ForPagination, ForPaginationItem, ForPaginationPrevious, ForPaginationNext],
|
|
28
|
+
template: `
|
|
29
|
+
<nav forPagination [(page)]="page" [count]="20" ariaLabel="Pagination" #pg="forPagination">
|
|
30
|
+
<button forPaginationPrevious ariaLabel="Previous page">‹</button>
|
|
31
|
+
@for (item of pg.items(); track $index) {
|
|
32
|
+
@if (item.type === 'page') {
|
|
33
|
+
<button forPaginationItem [page]="item.value!">{{ item.value }}</button>
|
|
34
|
+
} @else {
|
|
35
|
+
<span aria-hidden="true">…</span>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
<button forPaginationNext ariaLabel="Next page">›</button>
|
|
39
|
+
</nav>
|
|
40
|
+
`,
|
|
41
|
+
})
|
|
42
|
+
export class DemoPagination {
|
|
43
|
+
readonly page = signal(1);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Styling
|
|
48
|
+
|
|
49
|
+
forty-cdk ships no styles. Style the current page via `[aria-current="page"]` and disabled prev/next via `:disabled`.
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
[forPaginationItem][aria-current='page'] {
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[forPaginationPrevious]:disabled,
|
|
57
|
+
[forPaginationNext]:disabled {
|
|
58
|
+
opacity: 0.4;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Pane Resizer
|
|
2
|
+
|
|
3
|
+
Headless implementation of the [WAI-ARIA Window Splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/): the focusable divider between two resizable panes. It carries `role="separator"` plus live `aria-value*`, is tabbable, handles arrow / Page / Home / End keys, and drives a pointer-drag resize with `setPointerCapture`.
|
|
4
|
+
|
|
5
|
+
It is essentially a 1-D slider wearing a separator role. The static visual divider lives in the separate [`ForSeparator`](../separator/README.md) primitive so a plain `<hr forSeparator>` never pulls the drag / keyboard-resize code in.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Role |
|
|
10
|
+
| ---------------- | ------------------ | ------------------------------------------------------------------------------------- |
|
|
11
|
+
| `ForPaneResizer` | `[forPaneResizer]` | Single attribute directive. Focusable resizer: tabbable, exposes `aria-value*`, drag. |
|
|
12
|
+
|
|
13
|
+
## Inputs
|
|
14
|
+
|
|
15
|
+
| API | Type | Description |
|
|
16
|
+
| ------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `orientation` | `input<'horizontal' \| 'vertical'>` | Axis the divider line runs along. Defaults to `'horizontal'`. The resize axis runs perpendicular. |
|
|
18
|
+
| `disabled` | `input<boolean>` | Drops the resizer out of tab order; reflects `aria-disabled` / `data-disabled`; blocks keyboard / pointer. |
|
|
19
|
+
| `value` | `model<number>` | Two-way bindable value along the resize axis. Units are consumer-defined (px, %, fr…). |
|
|
20
|
+
| `min` | `input<number>` | Lower bound. Default `0`. |
|
|
21
|
+
| `max` | `input<number>` | Upper bound. Default `100`. |
|
|
22
|
+
| `step` | `input<number>` | Step applied by ArrowKeys. Default `1`. |
|
|
23
|
+
| `largeStep` | `input<number>` | Step applied by `Page Up` / `Page Down`. Default `10`. |
|
|
24
|
+
| `valueText` | `input<string \| null>` | Optional `aria-valuetext` string for human-readable values. |
|
|
25
|
+
| `controls` | `input<string \| null>` | Space-separated list of pane ids surfaced as `aria-controls`. |
|
|
26
|
+
| `collapsible` | `input<boolean>` | Opt-in `Enter` / `Space` toggle: collapses to `min`, restores to the previous size on the next press. |
|
|
27
|
+
| `dir` | `input<'ltr' \| 'rtl'>` | Reading direction. RTL inverts ArrowLeft / ArrowRight and the horizontal axis of pointer drag. |
|
|
28
|
+
|
|
29
|
+
## Outputs
|
|
30
|
+
|
|
31
|
+
| API | Payload | Fires |
|
|
32
|
+
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `valueChange` | `number` | Implicit emitter from `model()`. Fires on internal updates only — silent on consumer writes via `[(value)]`. |
|
|
34
|
+
| `resize` | `number` | Verb-named alias for `valueChange`. Useful when wiring one-way without `[(value)]`. |
|
|
35
|
+
| `resizeCommit` | `number` | Fires once at the end of a resize burst (key release, pointerup, or `pointercancel`). Persist final size here. |
|
|
36
|
+
|
|
37
|
+
The host gets `data-orientation="horizontal" \| "vertical"` for CSS hooks. When `disabled`, the host also gets `data-disabled=""`.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { Component, signal } from '@angular/core';
|
|
43
|
+
import { ForPaneResizer } from 'forty-cdk/pane-resizer';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
selector: 'demo-split-pane',
|
|
47
|
+
imports: [ForPaneResizer],
|
|
48
|
+
template: `
|
|
49
|
+
<div class="split" [style.--start.px]="size()">
|
|
50
|
+
<section id="pane-a" class="pane-a">…</section>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
class="resizer"
|
|
54
|
+
forPaneResizer
|
|
55
|
+
orientation="vertical"
|
|
56
|
+
[(value)]="size"
|
|
57
|
+
[min]="120"
|
|
58
|
+
[max]="640"
|
|
59
|
+
[step]="8"
|
|
60
|
+
[largeStep]="80"
|
|
61
|
+
[valueText]="size() + ' pixels'"
|
|
62
|
+
aria-controls="pane-a pane-b"
|
|
63
|
+
(resizeCommit)="persist($event)"
|
|
64
|
+
></div>
|
|
65
|
+
|
|
66
|
+
<section id="pane-b" class="pane-b">…</section>
|
|
67
|
+
</div>
|
|
68
|
+
`,
|
|
69
|
+
styles: `
|
|
70
|
+
.split {
|
|
71
|
+
display: grid;
|
|
72
|
+
grid-template-columns: var(--start) 4px 1fr;
|
|
73
|
+
block-size: 100%;
|
|
74
|
+
}
|
|
75
|
+
.resizer {
|
|
76
|
+
cursor: col-resize;
|
|
77
|
+
background: var(--border);
|
|
78
|
+
}
|
|
79
|
+
.resizer:focus-visible {
|
|
80
|
+
outline: 2px solid currentColor;
|
|
81
|
+
outline-offset: 2px;
|
|
82
|
+
}
|
|
83
|
+
.resizer[data-disabled] {
|
|
84
|
+
cursor: default;
|
|
85
|
+
opacity: 0.5;
|
|
86
|
+
}
|
|
87
|
+
`,
|
|
88
|
+
})
|
|
89
|
+
export class DemoSplitPane {
|
|
90
|
+
readonly size = signal(280);
|
|
91
|
+
persist(px: number) {
|
|
92
|
+
localStorage.setItem('pane-a-size', String(px));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Pointer drag
|
|
98
|
+
|
|
99
|
+
`pointerdown` captures the pointer, records the starting value, and on each `pointermove` adds the **raw px delta** along the resize axis to `value`, clamped to `[min, max]`. Use this directly for px-unit layouts; for percentage / fractional layouts, listen to `(resizing)` and translate yourself, or skip pointer drag and stick to keyboard.
|
|
100
|
+
|
|
101
|
+
## Styling
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
105
|
+
### Data attributes
|
|
106
|
+
|
|
107
|
+
| Piece | Attribute | Values |
|
|
108
|
+
| ------------------ | ------------------ | -------------------------- |
|
|
109
|
+
| `[forPaneResizer]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
110
|
+
| `[forPaneResizer]` | `data-disabled` | present \| absent |
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
.resizer[data-orientation='vertical'] {
|
|
114
|
+
cursor: col-resize;
|
|
115
|
+
}
|
|
116
|
+
.resizer[data-orientation='horizontal'] {
|
|
117
|
+
cursor: row-resize;
|
|
118
|
+
}
|
|
119
|
+
.resizer[data-disabled] {
|
|
120
|
+
cursor: default;
|
|
121
|
+
opacity: 0.5;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Accessibility notes
|
|
126
|
+
|
|
127
|
+
- **Follows the Window Splitter pattern verbatim.**
|
|
128
|
+
- `aria-orientation` is reflected **explicitly** (both `'horizontal'` and `'vertical'`) so AT can announce the resize axis.
|
|
129
|
+
- **Arrow keys** move along the resize axis: `Arrow←` / `Arrow→` for a vertical separator (horizontal pane stack), `Arrow↑` / `Arrow↓` for a horizontal separator (vertical pane stack). RTL inverts the horizontal pair.
|
|
130
|
+
- **`Page Up` / `Page Down`** apply `largeStep` (canonical APG large-step keys, not `Shift+Arrow`).
|
|
131
|
+
- **`Home` / `End`** snap to `min` / `max`.
|
|
132
|
+
- **`Enter` / `Space`** toggle to / from `min` when `collapsible` is enabled. Off by default — opt-in because it changes the meaning of `Enter`.
|
|
133
|
+
- `aria-controls` is recommended: point it at the panes the resizer splits so AT can relate them.
|
|
134
|
+
- **`aria-valuetext`** when the bare number is not meaningful (e.g. `"30 percent of viewport"`).
|
|
135
|
+
- **`data-disabled`** is reflected when `disabled` is true so consumers can flip styling and pointer affordances in CSS.
|
|
136
|
+
- **Accessible name.** Provide a name via native `aria-label` / `aria-labelledby` on the host so AT announces what the resizer adjusts.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Popover
|
|
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 implementation of the [WAI-ARIA Modeless Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), positioned against an internal trigger via [`@floating-ui/dom`](https://floating-ui.com/).
|
|
6
|
+
|
|
7
|
+
A popover is a non-modal dialog: focus moves into the surface on open and returns to the trigger on close, but Tab is allowed to leave (no focus trap). For a modal version, use `[forDialog]`. For a non-interactive label that follows the cursor / focus, use `[forTooltip]`.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Component } from '@angular/core';
|
|
13
|
+
import {
|
|
14
|
+
ForPopover,
|
|
15
|
+
ForPopoverArrow,
|
|
16
|
+
ForPopoverClose,
|
|
17
|
+
ForPopoverContent,
|
|
18
|
+
ForPopoverDescription,
|
|
19
|
+
ForPopoverTitle,
|
|
20
|
+
ForPopoverTrigger,
|
|
21
|
+
} from 'forty-cdk/popover';
|
|
22
|
+
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'demo-popover',
|
|
25
|
+
imports: [
|
|
26
|
+
ForPopover,
|
|
27
|
+
ForPopoverTrigger,
|
|
28
|
+
ForPopoverContent,
|
|
29
|
+
ForPopoverTitle,
|
|
30
|
+
ForPopoverDescription,
|
|
31
|
+
ForPopoverClose,
|
|
32
|
+
ForPopoverArrow,
|
|
33
|
+
],
|
|
34
|
+
template: `
|
|
35
|
+
<div forPopover #popover="forPopover" side="bottom" align="start">
|
|
36
|
+
<button forPopoverTrigger>Settings</button>
|
|
37
|
+
|
|
38
|
+
@if (popover.open()) {
|
|
39
|
+
<div forPopoverContent class="popover" animate.leave="fade-out">
|
|
40
|
+
<h2 forPopoverTitle>Display</h2>
|
|
41
|
+
<p forPopoverDescription>Adjust theme and density.</p>
|
|
42
|
+
<!-- your content -->
|
|
43
|
+
<button forPopoverClose>Close</button>
|
|
44
|
+
<span forPopoverArrow class="arrow"></span>
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
47
|
+
</div>
|
|
48
|
+
`,
|
|
49
|
+
})
|
|
50
|
+
export class DemoPopover {}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`[forPopoverContent]` portals to `document.body` and is positioned with floating-ui — it must be wrapped with `@if` so mount and unmount drive `animate.enter` / `animate.leave`.
|
|
54
|
+
|
|
55
|
+
### `#popover="forPopover"` vs. `[(open)]`
|
|
56
|
+
|
|
57
|
+
The minimal "click trigger → show content" case needs **neither** a separate `open` signal **nor** a two-way binding. `[forPopover]` is `exportAs: 'forPopover'`, so expose the directive instance with a template reference variable — `#popover="forPopover"` — and drive the `@if` straight off its own `open()` signal, as above. The trigger toggles it; Escape and outside dismissal flip it back.
|
|
58
|
+
|
|
59
|
+
Reach for the explicit `[(open)]="mySignal"` model binding only when the component class needs to read or drive open state — open it programmatically, persist it, or react to it elsewhere:
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<div forPopover [(open)]="open">
|
|
63
|
+
<button forPopoverTrigger>Settings</button>
|
|
64
|
+
@if (open()) {
|
|
65
|
+
<div forPopoverContent>…</div>
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Triggers stamped from outside-declared templates
|
|
71
|
+
|
|
72
|
+
Angular resolves `ng-template` DI at the template's **declaration** site, not where it is stamped. A `[forPopoverTrigger]` 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="forPopover"` and pass it through the outlet context. The bare valueless attribute keeps resolving via DI.
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<div forPopover #root="forPopover">
|
|
76
|
+
<ng-container *ngTemplateOutlet="trig; context: { root }" />
|
|
77
|
+
@if (root.open()) {
|
|
78
|
+
<div forPopoverContent>…</div>
|
|
79
|
+
}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<ng-template #trig let-root="root">
|
|
83
|
+
<button [forPopoverTrigger]="root">Settings</button>
|
|
84
|
+
</ng-template>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Pieces
|
|
88
|
+
|
|
89
|
+
| Class | Selector | Role |
|
|
90
|
+
| ----------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `ForPopover` | `[forPopover]` | Root. Owns `open`, side / align positioning, dismissible, returnFocus, initialFocus. |
|
|
92
|
+
| `ForPopoverTrigger` | `[forPopoverTrigger]` | Toggles `open` on click. Wires `aria-haspopup` / `aria-expanded` / `aria-controls`. Used as the floating-ui anchor unless a `[forPopoverAnchor]` is registered. |
|
|
93
|
+
| `ForPopoverAnchor` | `[forPopoverAnchor]` | Optional. When present, the popover is positioned against this element instead of the trigger — useful when "what opens it" and "where it appears" differ (cursor follower, contextual help anchored to a row, popover anchored to a text-selection range). |
|
|
94
|
+
| `ForPopoverContent` | `[forPopoverContent]` | The popover surface. `role="dialog"`, portaled to body, positioned, dismissable. |
|
|
95
|
+
| `ForPopoverTitle` | `[forPopoverTitle]` | Generates an id and registers it as `aria-labelledby`. |
|
|
96
|
+
| `ForPopoverDescription` | `[forPopoverDescription]` | Same, for `aria-describedby`. |
|
|
97
|
+
| `ForPopoverClose` | `[forPopoverClose]` | Button that sets `open` to `false`. Bypasses `dismissible`. |
|
|
98
|
+
| `ForPopoverArrow` | `[forPopoverArrow]` | Optional decorative arrow positioned by floating-ui. |
|
|
99
|
+
|
|
100
|
+
## Inputs (`ForPopover`)
|
|
101
|
+
|
|
102
|
+
| API | Default | Description |
|
|
103
|
+
| ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `open` | `false` | Two-way bindable visibility. |
|
|
105
|
+
| `side` | `'bottom'` | Anchor side (`'top'` / `'right'` / `'bottom'` / `'left'`). Falls back to `provideForPopoverDefaults`. |
|
|
106
|
+
| `align` | `'center'` | Alignment along the chosen side (`'start'` / `'center'` / `'end'`). Falls back to `provideForPopoverDefaults`. |
|
|
107
|
+
| `sideOffset` | `8` | Gap (px) between trigger and content along the main axis. Falls back to `provideForPopoverDefaults`. |
|
|
108
|
+
| `alignOffset` | `0` | Gap (px) along the cross axis (parallel to `side`). |
|
|
109
|
+
| `collisionPadding` | `8` | Padding (px) for the `flip` / `shift` / `size` collision middlewares. Falls back to `provideForPopoverDefaults`. |
|
|
110
|
+
| `disabled` | `false` | When `true`, trigger does not toggle. |
|
|
111
|
+
| `dismissible` | `true` | When `false`, Escape / outside-pointer / outside-focus do not close. |
|
|
112
|
+
| `returnFocus` | `true` | Focus returns to the trigger on close. |
|
|
113
|
+
| `initialFocus` | `'first'` | `'first'` (first focusable inside content) or `'container'` (the content host). |
|
|
114
|
+
| `ariaLabel` | `null` | Manual `aria-label` on the content when no `[forPopoverTitle]` is rendered. |
|
|
115
|
+
|
|
116
|
+
## Inputs (`ForPopoverTrigger`)
|
|
117
|
+
|
|
118
|
+
| API | Default | Description |
|
|
119
|
+
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `disabled` | `false` | Disables this trigger only — merged OR with the root's `disabled`. The effective state drives `disabled` / `aria-disabled` / `data-disabled` and the click guard. |
|
|
121
|
+
|
|
122
|
+
## Scoped defaults
|
|
123
|
+
|
|
124
|
+
`provideForPopoverDefaults` configures positioning 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).
|
|
125
|
+
|
|
126
|
+
| Key | Library fallback | Meaning |
|
|
127
|
+
| ------------------ | ---------------- | ---------------------------------------------------------------------------- |
|
|
128
|
+
| `side` | `'bottom'` | Anchor side for popovers that don't set `side` themselves. |
|
|
129
|
+
| `align` | `'center'` | Alignment along `side` for popovers that don't set `align` themselves. |
|
|
130
|
+
| `sideOffset` | `8` | Main-axis gap (px) for popovers that don't set `sideOffset` themselves. |
|
|
131
|
+
| `collisionPadding` | `8` | Collision-middleware padding (px) for popovers that don't set it themselves. |
|
|
132
|
+
|
|
133
|
+
Per-instance inputs always win over the scope defaults.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { provideForPopoverDefaults } from 'forty-cdk/popover';
|
|
137
|
+
|
|
138
|
+
// Top-anchored popovers app-wide
|
|
139
|
+
bootstrapApplication(App, {
|
|
140
|
+
providers: [provideForPopoverDefaults({ side: 'top', sideOffset: 4 })],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// component-level override layers on top, per key
|
|
144
|
+
@Component({
|
|
145
|
+
providers: [provideForPopoverDefaults({ align: 'start' })],
|
|
146
|
+
...
|
|
147
|
+
})
|
|
148
|
+
class Toolbar {}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Outputs (`ForPopover`)
|
|
152
|
+
|
|
153
|
+
The dismiss outputs and the auto-focus pair are vetoable: each receives a `VetoableEvent` (or `VetoableNativeEvent<E>` when there is a native DOM event to surface). Call `preventDefault()` on the emitted veto to suppress the automatic close / focus move; the original DOM event, when present, is on `.event`.
|
|
154
|
+
|
|
155
|
+
| Output | Payload | Fires on |
|
|
156
|
+
| -------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `escapeKeyDown` | `VetoableNativeEvent<KeyboardEvent>` | Escape while this popover is the topmost dismissable layer. |
|
|
158
|
+
| `pointerDownOutside` | `VetoableNativeEvent<PointerEvent>` | Pointer-down outside the content (and outside the trigger). |
|
|
159
|
+
| `focusOutside` | `VetoableNativeEvent<FocusEvent>` | Focus moves outside the content (and outside the trigger). |
|
|
160
|
+
| `interactOutside` | `VetoableNativeEvent<PointerEvent \| FocusEvent>` | Composite: fires alongside both of the above (and shares their veto state). |
|
|
161
|
+
| `autoFocusOnOpen` | `VetoableEvent` | Just before focus moves into the popover on mount. `preventDefault()` skips the move. |
|
|
162
|
+
| `autoFocusOnClose` | `VetoableEvent` | Just before focus returns to the trigger on unmount. `preventDefault()` skips the return-focus. |
|
|
163
|
+
| `openChange` | `boolean` | Implicit from `model()`. Emits only on internal transitions, not on consumer writes via `[(open)]`. |
|
|
164
|
+
|
|
165
|
+
`(autoFocusOnOpen)` / `(autoFocusOnClose)` are output-shape because Popover always routes close transitions through `[(open)]` (via the implicit `openChange` emitter). See [CLAUDE.md › Auto-focus hook shape](../../../../../CLAUDE.md#auto-focus-hook-shape) for why Dialog uses callback-shape inputs instead.
|
|
166
|
+
|
|
167
|
+
### Open without stealing focus
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<div forPopover [(open)]="open">
|
|
171
|
+
<input forPopoverAnchor #q type="search" (input)="open.set(true)" placeholder="Search…" />
|
|
172
|
+
<button forPopoverTrigger hidden></button>
|
|
173
|
+
|
|
174
|
+
@if (open()) {
|
|
175
|
+
<div
|
|
176
|
+
forPopoverContent
|
|
177
|
+
(autoFocusOnOpen)="$event.preventDefault()"
|
|
178
|
+
(autoFocusOnClose)="$event.preventDefault()"
|
|
179
|
+
>
|
|
180
|
+
…
|
|
181
|
+
</div>
|
|
182
|
+
}
|
|
183
|
+
</div>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The popover opens / closes alongside the input but never steals focus from it — handy for live-search panels where every keystroke matters.
|
|
187
|
+
|
|
188
|
+
## Styling
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
### Data attributes
|
|
193
|
+
|
|
194
|
+
| Piece | Attribute | Values |
|
|
195
|
+
| --------------------- | --------------------- | ------------------ |
|
|
196
|
+
| `[forPopover]` | `data-state` | `open` \| `closed` |
|
|
197
|
+
| `[forPopover]` | `data-disabled` | present \| absent |
|
|
198
|
+
| `[forPopover]` | `data-reduced-motion` | present \| absent |
|
|
199
|
+
| `[forPopoverTrigger]` | `data-state` | `open` \| `closed` |
|
|
200
|
+
| `[forPopoverTrigger]` | `data-disabled` | present \| absent |
|
|
201
|
+
| `[forPopoverContent]` | `data-state` | `open` \| `closed` |
|
|
202
|
+
| `[forPopoverContent]` | `data-reduced-motion` | present \| absent |
|
|
203
|
+
| `[forPopoverArrow]` | `data-popover-arrow` | present |
|
|
204
|
+
|
|
205
|
+
### CSS custom properties
|
|
206
|
+
|
|
207
|
+
See also: [Styling floating content](../../../../../docs/styling-floating-content.md) — animation rules, standalone `scale`/`opacity`, and the arrow recipe.
|
|
208
|
+
|
|
209
|
+
`[forPopoverContent]` 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 `[forPopoverArrow]` reads the consumer-settable `--for-arrow-offset`:
|
|
210
|
+
|
|
211
|
+
| Element | Custom property | Type / range | Direction | Meaning |
|
|
212
|
+
| --------------------- | -------------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
|
|
213
|
+
| `[forPopoverContent]` | `--for-anchor-width` | px | out | Trigger (reference) width — match it with `width: var(--for-anchor-width)`. |
|
|
214
|
+
| `[forPopoverContent]` | `--for-anchor-height` | px | out | Trigger (reference) height. |
|
|
215
|
+
| `[forPopoverContent]` | `--for-available-width` | px | out | Space available along the inline axis (floating-ui `size` middleware) — clamp with `max-width`. |
|
|
216
|
+
| `[forPopoverContent]` | `--for-available-height` | px | out | Space available along the block axis — clamp with `max-height`. |
|
|
217
|
+
| `[forPopoverContent]` | `--for-content-transform-origin` | `<origin>` keywords | out | `transform-origin` matching the resolved side / align, so a `scale` enter animation pivots from the trigger. |
|
|
218
|
+
| `[forPopoverArrow]` | `--for-arrow-offset` | px (default `0px`) | in | Consumer-set. How far the arrow pokes out past the popover edge — typically a negative `px` (e.g. `-4px`). |
|
|
219
|
+
|
|
220
|
+
> `[forPopoverContent]` portals to `document.body`, so ancestor-scoped CSS can't reach it. Style it with global CSS or a class — see [Styling floating content](../../../../../docs/styling-floating-content.md) for the full positioner-property list and the floating-content rules.
|
|
221
|
+
|
|
222
|
+
```css
|
|
223
|
+
.popover-trigger .chevron {
|
|
224
|
+
transition: transform 150ms;
|
|
225
|
+
}
|
|
226
|
+
.popover-trigger[data-state='open'] .chevron {
|
|
227
|
+
transform: rotate(180deg);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Reduced motion
|
|
232
|
+
|
|
233
|
+
`[forPopover]` and `[forPopoverContent]` reflect `data-reduced-motion` (present / absent) whenever the OS `prefers-reduced-motion: reduce` media query matches, so you can opt your own `animate.enter` / `animate.leave` and CSS transitions out without re-deriving the query. The attribute flips reactively if the preference changes mid-session. The popover toggles open / closed synchronously on click, so there is no JS-coordinated timing to skip — only the visual transitions (which are yours) opt out.
|
|
234
|
+
|
|
235
|
+
```css
|
|
236
|
+
.popover-content[data-reduced-motion] {
|
|
237
|
+
transition: none;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Keyboard
|
|
242
|
+
|
|
243
|
+
- **Tab / Shift+Tab** moves focus through the popover and beyond (no trap). When focus leaves, `focusOutside` fires and the popover closes unless prevented.
|
|
244
|
+
- **Escape** closes when `dismissible`. Use `(escapeKeyDown)="$event.preventDefault()"` to ask "are you sure?" first.
|
|
245
|
+
- **Enter / Space** on the trigger toggles (native button behavior).
|
|
246
|
+
|
|
247
|
+
## Behavior notes
|
|
248
|
+
|
|
249
|
+
- **Portal**: the content is moved to `document.body` on first render. CSS scoped to ancestors won't reach it — use global styles or classes.
|
|
250
|
+
- **Trigger exemption**: clicks on the trigger never fire `pointerDownOutside` or `interactOutside`. Their only effect is the trigger's own toggle.
|
|
251
|
+
- **Anchor vs. trigger**: `[forPopoverAnchor]` only changes the floating-ui reference. The trigger keeps `aria-controls` / `aria-expanded`, the click toggle, and focus return on close. The anchor is _not_ exempt from outside dismissal — clicking it is treated as outside.
|
|
252
|
+
- **Non-modal**: no focus trap, no body scroll lock, no `aria-modal`. If you need modal semantics, use `[forDialog]` instead.
|
|
253
|
+
- **No backdrop**: popovers don't render an overlay. Outside dismissal is event-driven.
|
|
254
|
+
- **Focus return**: on unmount, focus is sent back to the registered trigger element (unless `returnFocus="false"`). The return happens before the portal helper removes the node, so the trigger receives `focusin` against a stable layout.
|
|
255
|
+
- **Arrow offset**: `[forPopoverArrow]` writes `position: absolute`, the floating-ui-resolved `left` / `top`, and `var(--for-arrow-offset, 0px)` on the side opposite the popover (so the arrow points back at the trigger). 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`. Defaults to `0px` (flush with the popover edge); the helper ships no default visual.
|
|
256
|
+
|
|
257
|
+
## Accessibility notes
|
|
258
|
+
|
|
259
|
+
- Always provide an accessible name: render a `[forPopoverTitle]` or pass `ariaLabel`.
|
|
260
|
+
- `[forPopoverDescription]` is optional — use it for explanatory copy beyond the title.
|
|
261
|
+
- `aria-haspopup="dialog"` advertises the popover as a dialog (matches `role="dialog"` on the content). For menus or listboxes, build a different primitive.
|
|
262
|
+
- The popover is not modal: assistive tech users can still navigate around it. That's intentional — modeless surfaces should not interrupt.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Progress
|
|
2
|
+
|
|
3
|
+
Headless [WAI-ARIA `progressbar`](https://www.w3.org/TR/wai-aria-1.2/#progressbar) for tasks whose completion you can communicate visually.
|
|
4
|
+
|
|
5
|
+
Pass a numeric `value` for a determinate bar, or `null` for indeterminate ("loading…"). The directive owns ARIA + state; the visual fill is yours via `[forProgressIndicator]`.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Role |
|
|
10
|
+
| ---------------------- | ------------------------ | ------------------------------------------------------------------- |
|
|
11
|
+
| `ForProgress` | `[forProgress]` | Root. Owns `value` / `max`, reflects `role="progressbar"` and ARIA. |
|
|
12
|
+
| `ForProgressIndicator` | `[forProgressIndicator]` | Visual fill. Reflects `data-state` and `data-percentage`. |
|
|
13
|
+
|
|
14
|
+
## Inputs / models
|
|
15
|
+
|
|
16
|
+
| API | Type | Description |
|
|
17
|
+
| -------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `value` | `model<number \| null>` | Two-way bindable. Current progress in `[0, max]`. `null` = indeterminate. |
|
|
19
|
+
| `max` | `input<number>` | Upper bound. Defaults to `100`. A non-positive `max` is clamped to `1` for ARIA so `aria-valuemax` always exceeds `aria-valuemin` (`0`). |
|
|
20
|
+
| `getValueLabel` | `input<((value, max) => string) \| null>` | Override for `aria-valuetext` (e.g. "Step 3 of 5"). |
|
|
21
|
+
| `announceCompletion` | `input<boolean>` | Announce `Complete` (or the label) once via `aria-live` on the loading→complete transition. |
|
|
22
|
+
|
|
23
|
+
The host carries `data-state="indeterminate" \| "loading" \| "complete"`, `data-value`, `data-min`, `data-max`, and `data-percentage` (absent while indeterminate), matching the meter root so the root can be styled from `data-percentage` directly. The indicator reflects the same `data-percentage` plus the CSS custom property `--for-progress-percentage` (e.g. `25%`) that you can use directly in `transform` / `width`.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Component, signal } from '@angular/core';
|
|
29
|
+
import { ForProgress, ForProgressIndicator } from 'forty-cdk/progress';
|
|
30
|
+
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'demo-upload',
|
|
33
|
+
imports: [ForProgress, ForProgressIndicator],
|
|
34
|
+
template: `
|
|
35
|
+
<div forProgress class="progress" [value]="uploaded()" [max]="total()" announceCompletion>
|
|
36
|
+
<div forProgressIndicator class="progress-indicator"></div>
|
|
37
|
+
</div>
|
|
38
|
+
`,
|
|
39
|
+
styles: [
|
|
40
|
+
`
|
|
41
|
+
.progress {
|
|
42
|
+
position: relative;
|
|
43
|
+
height: 8px;
|
|
44
|
+
width: 240px;
|
|
45
|
+
background: #eee;
|
|
46
|
+
border-radius: 4px;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
.progress-indicator {
|
|
50
|
+
position: absolute;
|
|
51
|
+
inset: 0;
|
|
52
|
+
background: #4f46e5;
|
|
53
|
+
transform-origin: left center;
|
|
54
|
+
transition: transform 120ms;
|
|
55
|
+
}
|
|
56
|
+
.progress-indicator[data-state='loading'] {
|
|
57
|
+
transform: scaleX(calc(var(--for-progress-percentage) / 100));
|
|
58
|
+
}
|
|
59
|
+
.progress-indicator[data-state='indeterminate'] {
|
|
60
|
+
transform: scaleX(0.4);
|
|
61
|
+
animation: slide 1.2s infinite ease-in-out;
|
|
62
|
+
}
|
|
63
|
+
@keyframes slide {
|
|
64
|
+
from {
|
|
65
|
+
transform: translateX(-100%) scaleX(0.4);
|
|
66
|
+
}
|
|
67
|
+
to {
|
|
68
|
+
transform: translateX(250%) scaleX(0.4);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`,
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
export class DemoUpload {
|
|
75
|
+
readonly uploaded = signal(0);
|
|
76
|
+
readonly total = signal(200);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Styling
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
### Data attributes
|
|
85
|
+
|
|
86
|
+
| Piece | Attribute | Values |
|
|
87
|
+
| ------------------------ | ----------------- | ------------------------------------------ |
|
|
88
|
+
| `[forProgress]` | `data-state` | `indeterminate` \| `loading` \| `complete` |
|
|
89
|
+
| `[forProgress]` | `data-value` | clamped value (absent while indeterminate) |
|
|
90
|
+
| `[forProgress]` | `data-min` | `0` |
|
|
91
|
+
| `[forProgress]` | `data-max` | the `max` value |
|
|
92
|
+
| `[forProgress]` | `data-percentage` | `0`–`100` (absent while indeterminate) |
|
|
93
|
+
| `[forProgressIndicator]` | `data-state` | `indeterminate` \| `loading` \| `complete` |
|
|
94
|
+
| `[forProgressIndicator]` | `data-value` | clamped value (absent while indeterminate) |
|
|
95
|
+
| `[forProgressIndicator]` | `data-max` | the `max` value |
|
|
96
|
+
| `[forProgressIndicator]` | `data-percentage` | `0`–`100` (absent while indeterminate) |
|
|
97
|
+
|
|
98
|
+
### CSS custom properties
|
|
99
|
+
|
|
100
|
+
| Property | Meaning |
|
|
101
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `--for-progress-percentage` | Completion as a CSS percentage (e.g. `25%`), set on `[forProgressIndicator]`. Absent while indeterminate (`value === null`). |
|
|
103
|
+
|
|
104
|
+
```css
|
|
105
|
+
.progress-indicator[data-state='loading'] {
|
|
106
|
+
transform: scaleX(calc(var(--for-progress-percentage) / 100));
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Accessibility notes
|
|
111
|
+
|
|
112
|
+
- **`role="progressbar"`** is announced as "progressbar" with the current value as a percentage (or your `aria-valuetext` if `getValueLabel` is set).
|
|
113
|
+
- **Indeterminate omits `aria-valuenow`.** Per spec, the absence of `aria-valuenow` is what tells AT the bar is indeterminate. The directive enforces this; `data-state="indeterminate"` is the CSS hook.
|
|
114
|
+
- **Announce sparingly.** `announceCompletion` is opt-in; only enable it on flows where the user explicitly waits for completion (uploads, submissions). For background activity, the silent state change is enough.
|
|
115
|
+
- **Keep the bar focusable only if it has actions.** A vanilla `[forProgress]` is non-interactive and should not be in the tab order.
|