forty-cdk 0.37.0 → 0.39.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/README.md +4 -4
- package/avatar/README.md +3 -1
- package/breadcrumbs/README.md +2 -2
- package/button/README.md +13 -7
- package/calendar/README.md +10 -4
- package/carousel/README.md +1 -1
- package/checkbox/README.md +3 -3
- package/combobox/README.md +23 -21
- package/date-field/README.md +9 -9
- package/date-picker/README.md +7 -8
- package/drag-drop/README.md +56 -8
- package/drawer/README.md +25 -20
- package/dropdown-menu/README.md +3 -1
- package/fesm2022/forty-cdk-button.mjs +5 -4
- package/fesm2022/forty-cdk-button.mjs.map +1 -1
- package/fesm2022/forty-cdk-carousel.mjs +2 -2
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +232 -141
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +23 -6
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +114 -50
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +5 -5
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-defaults.mjs.map +1 -1
- package/fesm2022/forty-cdk-dialog.mjs +2 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +293 -71
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +129 -52
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +3 -3
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-fieldset.mjs +2 -2
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
- package/fesm2022/forty-cdk-file-upload.mjs +6 -3
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +199 -135
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-navigation-menu.mjs +37 -45
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-number-input.mjs +18 -11
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-pagination.mjs +8 -4
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
- package/fesm2022/forty-cdk-pane-resizer.mjs +2 -2
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +243 -159
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-slider.mjs +2 -2
- package/fesm2022/forty-cdk-slider.mjs.map +1 -1
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
- package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +8 -8
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +17 -3
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-toast.mjs +3 -3
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +174 -111
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtual-reorder.mjs +2 -1
- package/fesm2022/forty-cdk-virtual-reorder.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +74 -13
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/field/README.md +9 -9
- package/fieldset/README.md +1 -1
- package/file-upload/README.md +1 -1
- package/input/README.md +2 -2
- package/listbox/README.md +86 -98
- package/menubar/README.md +1 -1
- package/navigation-menu/README.md +39 -36
- package/number-input/README.md +20 -8
- package/otp-input/README.md +2 -2
- package/package.json +1 -1
- package/pagination/README.md +2 -1
- package/pane-resizer/README.md +2 -2
- package/progress/README.md +22 -5
- package/radio-group/README.md +6 -6
- package/select/README.md +18 -12
- package/stepper/README.md +9 -9
- package/switch/README.md +30 -17
- package/table/README.md +43 -37
- package/table-virtualization/README.md +1 -1
- package/time-field/README.md +8 -8
- package/time-picker/README.md +9 -3
- package/toggle/README.md +12 -3
- package/tree/README.md +25 -14
- package/types/forty-cdk-button.d.ts +7 -1
- package/types/forty-cdk-carousel.d.ts +2 -1
- package/types/forty-cdk-combobox.d.ts +176 -63
- package/types/forty-cdk-context-menu.d.ts +1 -1
- package/types/forty-cdk-core-overlay.d.ts +12 -1
- package/types/forty-cdk-core.d.ts +105 -70
- package/types/forty-cdk-date-field.d.ts +6 -2
- package/types/forty-cdk-defaults.d.ts +6 -5
- package/types/forty-cdk-drag-drop.d.ts +52 -8
- package/types/forty-cdk-drawer.d.ts +50 -29
- package/types/forty-cdk-dropdown-menu.d.ts +1 -1
- package/types/forty-cdk-field.d.ts +3 -4
- package/types/forty-cdk-file-upload.d.ts +4 -0
- package/types/forty-cdk-listbox.d.ts +180 -66
- package/types/forty-cdk-menu.d.ts +1 -1
- package/types/forty-cdk-navigation-menu.d.ts +39 -30
- package/types/forty-cdk-number-input.d.ts +15 -15
- package/types/forty-cdk-pagination.d.ts +8 -1
- package/types/forty-cdk-pane-resizer.d.ts +4 -3
- package/types/forty-cdk-select.d.ts +215 -91
- package/types/forty-cdk-stepper.d.ts +11 -7
- package/types/forty-cdk-table.d.ts +5 -4
- package/types/forty-cdk-time-field.d.ts +5 -4
- package/types/forty-cdk-time-picker.d.ts +21 -8
- package/types/forty-cdk-toggle.d.ts +4 -3
- package/types/forty-cdk-toolbar.d.ts +2 -2
- package/types/forty-cdk-tree.d.ts +303 -61
- package/types/forty-cdk-virtual-reorder.d.ts +5 -3
- package/types/forty-cdk-virtualization.d.ts +52 -17
- package/virtual-reorder/README.md +8 -2
- package/virtualization/README.md +190 -31
package/README.md
CHANGED
|
@@ -26,10 +26,10 @@ Required:
|
|
|
26
26
|
|
|
27
27
|
Optional — install only if you use the matching entry point / primitives:
|
|
28
28
|
|
|
29
|
-
| Peer | Needed by
|
|
30
|
-
| ---------------------------------- |
|
|
31
|
-
| `@angular/forms` `^22.1.0` |
|
|
32
|
-
| `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves depend only on the abstract `DateAdapter` contract from `forty-cdk/date-adapter` — install this peer only when you import that entry point.
|
|
29
|
+
| Peer | Needed by |
|
|
30
|
+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `@angular/forms` `^22.1.0` | The form-control entry points: `forty-cdk/checkbox`, `combobox`, `date-field`, `date-picker`, `input`, `listbox`, `number-input`, `otp-input`, `radio-group`, `search`, `select`, `slider`, `switch`, `time-field`, `time-picker` and `toggle`. Their controls implement `FormValueControl` / `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring. The contract is type-only, so no published bundle references the package, but those entry points' type declarations do: a project that type-checks its dependencies (`skipLibCheck: false`) needs the peer as soon as it imports one of them. Every other entry point, `forty-cdk/field` and `forty-cdk/stepper` included, compiles without it. |
|
|
32
|
+
| `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves depend only on the abstract `DateAdapter` contract from `forty-cdk/date-adapter` — install this peer only when you import that entry point. |
|
|
33
33
|
|
|
34
34
|
### Regular dependencies
|
|
35
35
|
|
package/avatar/README.md
CHANGED
|
@@ -22,7 +22,7 @@ It is headless and presentational: it tracks the load lifecycle of an `<img>` an
|
|
|
22
22
|
|
|
23
23
|
## Examples
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The image loads, so `data-status` settles on `loaded` and the initials never render: `fallbackDelayMs` holds the fallback back for 500 ms, so a fast load never flashes it. Failed load below breaks the URL to show the `error` path.
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
@@ -100,6 +100,8 @@ When the image errors, the directive flips `shouldShowFallback()` and the initia
|
|
|
100
100
|
|
|
101
101
|
The directive does not impose a `role`. Pair the avatar with visible name text or `aria-label` on the surrounding element when identity matters. Set `alt=""` on the `<img>` for purely decorative avatars next to a name, or provide a meaningful `alt` description if the avatar stands alone.
|
|
102
102
|
|
|
103
|
+
A meaningful `alt` names the avatar only while the image is shown: once it fails, the image is hidden and the fallback initials are all that is left to announce. When an avatar stands alone, name the root instead (`role="img"` with an `aria-label`) and give the `<img>` `alt=""`, so the fallback carries the same name as the loaded image.
|
|
104
|
+
|
|
103
105
|
## Styling
|
|
104
106
|
|
|
105
107
|
forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [API](#api), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
|
package/breadcrumbs/README.md
CHANGED
|
@@ -126,10 +126,10 @@ Implements the [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/pat
|
|
|
126
126
|
|
|
127
127
|
## Styling
|
|
128
128
|
|
|
129
|
-
forty-cdk ships no styles
|
|
129
|
+
forty-cdk ships no styles: put your own class on each piece and key your CSS off the `aria-current="page"` the current item reflects, not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
|
|
130
130
|
|
|
131
131
|
```css
|
|
132
|
-
[
|
|
132
|
+
.bc-link[aria-current='page'] {
|
|
133
133
|
font-weight: bold;
|
|
134
134
|
color: inherit;
|
|
135
135
|
text-decoration: none;
|
package/button/README.md
CHANGED
|
@@ -5,7 +5,7 @@ archetype: [composable-ui]
|
|
|
5
5
|
apgUrl: https://www.w3.org/WAI/ARIA/apg/patterns/button/
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Button
|
|
9
9
|
|
|
10
10
|
Turns any element (a native `<button>` or a custom host like `<div>` / `<span>`) into an accessible button with keyboard activation. Disabled stays focusable (aria-disabled, never the native attribute) and pressed / hovered / focus-visible are reflected as data-\* hooks.
|
|
11
11
|
|
|
@@ -58,20 +58,26 @@ Disabled buttons stay focusable so assistive technology can announce them. The n
|
|
|
58
58
|
A surrounding disabled `[forFieldset]` disables the button too: its `disabled` input is OR'd with the group's, so `aria-disabled` / `data-disabled` are reflected and activation is suppressed. This matters most on a non-native host (`<div forButton>`), which a native `<fieldset disabled>` cannot reach.
|
|
59
59
|
|
|
60
60
|
```html
|
|
61
|
-
<
|
|
62
|
-
<
|
|
61
|
+
<div forFieldset [disabled]="locked()">
|
|
62
|
+
<span forFieldsetLegend>Account</span>
|
|
63
63
|
<button forButton (activate)="save()">Save</button>
|
|
64
|
-
</
|
|
64
|
+
</div>
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
On a native `<fieldset>` host, `[forFieldset]` sets the native `disabled` attribute, and HTML then disables every native `<button>` inside it: the button leaves the tab order and cannot be focused, whatever `forButton` does. The guarantee that a disabled button stays focusable holds for a non-native group host like the `<div>` above, and for a non-native button host (`<div forButton>`) in either group.
|
|
68
|
+
|
|
67
69
|
## Preserve consumer `type`
|
|
68
70
|
|
|
69
|
-
A native `<button>` without an explicit `type` attribute defaults to `type="button"`. A consumer-set `type
|
|
71
|
+
A native `<button>` without an explicit `type` attribute defaults to `type="button"`. A consumer-set `type` is preserved, whether it is a static attribute or a binding, and later writes to a binding are followed:
|
|
70
72
|
|
|
71
73
|
```html
|
|
72
74
|
<button type="submit" forButton>Submit form</button>
|
|
75
|
+
|
|
76
|
+
<button [attr.type]="kind()" forButton>Save</button>
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
A binding that resolves to `null` removes the attribute, so the button falls back to the HTML default, which is `submit` inside a form.
|
|
80
|
+
|
|
75
81
|
## API
|
|
76
82
|
|
|
77
83
|
### `ForButton`
|
|
@@ -118,12 +124,12 @@ Implements the [WAI-ARIA Button pattern](https://www.w3.org/WAI/ARIA/apg/pattern
|
|
|
118
124
|
forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [API](#api), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
|
|
119
125
|
|
|
120
126
|
```css
|
|
121
|
-
[
|
|
127
|
+
.btn[data-disabled] {
|
|
122
128
|
opacity: 0.4;
|
|
123
129
|
pointer-events: none;
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
[
|
|
132
|
+
.btn[data-pressed] {
|
|
127
133
|
transform: scale(0.97);
|
|
128
134
|
}
|
|
129
135
|
```
|
package/calendar/README.md
CHANGED
|
@@ -341,12 +341,18 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
|
|
|
341
341
|
/>
|
|
342
342
|
</svg>
|
|
343
343
|
</button>
|
|
344
|
-
<select forCalendarMonthSelect #m="forCalendarMonthSelect">
|
|
344
|
+
<select forCalendarMonthSelect #m="forCalendarMonthSelect" aria-label="Month">
|
|
345
345
|
@for (opt of m.options(); track opt.value) {
|
|
346
346
|
<option [value]="opt.value" [disabled]="opt.disabled">{{ opt.label }}</option>
|
|
347
347
|
}
|
|
348
348
|
</select>
|
|
349
|
-
<select
|
|
349
|
+
<select
|
|
350
|
+
forCalendarYearSelect
|
|
351
|
+
#y="forCalendarYearSelect"
|
|
352
|
+
aria-label="Year"
|
|
353
|
+
[minYear]="1900"
|
|
354
|
+
[maxYear]="2100"
|
|
355
|
+
>
|
|
350
356
|
@for (opt of y.years(); track opt.value) {
|
|
351
357
|
<option [value]="opt.value" [disabled]="opt.disabled">{{ opt.value }}</option>
|
|
352
358
|
}
|
|
@@ -575,7 +581,7 @@ Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/
|
|
|
575
581
|
- **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
|
|
576
582
|
- **`aria-multiselectable="true"`** is set on the day grid in `selectionMode="range"`, where a committed range selects several cells, and is absent in single mode.
|
|
577
583
|
- **Roving tabindex**: exactly one cell (the focused date) is tabbable. `Tab` enters and leaves the grid in one stop.
|
|
578
|
-
- **Boolean `data-*`** on the cell, present when true and absent when false: `data-selected`, `data-today`, `data-highlighted` (the
|
|
584
|
+
- **Boolean `data-*`** on the cell, present when true and absent when false: `data-selected`, `data-today`, `data-highlighted` (the roving tab-stop cell, kept after focus leaves the grid), `data-disabled` and `data-outside-month`.
|
|
579
585
|
|
|
580
586
|
## Styling
|
|
581
587
|
|
|
@@ -612,7 +618,7 @@ import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date';
|
|
|
612
618
|
@Component({
|
|
613
619
|
selector: 'app-date',
|
|
614
620
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
615
|
-
|
|
621
|
+
template: `<p>Today is {{ date()?.toString() }}</p>`,
|
|
616
622
|
})
|
|
617
623
|
export class DatePage {
|
|
618
624
|
readonly date = signal<CalendarDate | null>(null);
|
package/carousel/README.md
CHANGED
|
@@ -5,7 +5,7 @@ archetype: [composable-ui]
|
|
|
5
5
|
apgUrl: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Carousel
|
|
9
9
|
|
|
10
10
|
A slideshow of content panels with previous / next controls, an indicator group, optional looping and multi-slide views, and an accessible autoplay mode with a pause control.
|
|
11
11
|
|
package/checkbox/README.md
CHANGED
|
@@ -134,12 +134,12 @@ One class and one directive, three states. `disabled` and `readonly` both keep t
|
|
|
134
134
|
|
|
135
135
|
```ts
|
|
136
136
|
import { Component, signal } from '@angular/core';
|
|
137
|
-
import { form, required } from '@angular/forms/signals';
|
|
137
|
+
import { form, FormField, required } from '@angular/forms/signals';
|
|
138
138
|
import { ForCheckbox } from 'forty-cdk/checkbox';
|
|
139
139
|
|
|
140
140
|
@Component({
|
|
141
141
|
selector: 'demo-checkout',
|
|
142
|
-
imports: [ForCheckbox
|
|
142
|
+
imports: [ForCheckbox, FormField],
|
|
143
143
|
template: `
|
|
144
144
|
<button forCheckbox class="cb-row" [formField]="checkout.acceptTerms">
|
|
145
145
|
<span class="cb">
|
|
@@ -166,7 +166,7 @@ export class DemoCheckout {
|
|
|
166
166
|
| `disabled` / `readonly` / `required` / `invalid` / `pending` | `input<boolean>` | Reflected as the matching `aria-*` / `data-*` attributes. A disabled checkbox stays focusable (per APG), with `aria-disabled="true"` + `data-disabled` and no native `disabled`; click is a no-op.<br>**Default:** — |
|
|
167
167
|
| `name` | `input<string>` | Reflected on `name` (empty string omits the attribute). A native reset of the owning `<form>` restores `checked` and `indeterminate` to their first-render values.<br>**Default:** `''` |
|
|
168
168
|
| `errors` | `input<readonly ValidationError.WithOptionalFieldTree[]>` | Validation errors fed by `[formField]`.<br>**Default:** — |
|
|
169
|
-
| `touched` | `model<boolean>` | Set to `true` on blur.<br>**Default:**
|
|
169
|
+
| `touched` | `model<boolean>` | Set to `true` on blur.<br>**Default:** `false` |
|
|
170
170
|
|
|
171
171
|
| Data attribute | Values |
|
|
172
172
|
| --------------- | ------------------------------------------- |
|
package/combobox/README.md
CHANGED
|
@@ -115,17 +115,11 @@ const COUNTRIES = [
|
|
|
115
115
|
ForComboboxClear,
|
|
116
116
|
],
|
|
117
117
|
template: `
|
|
118
|
-
<div
|
|
119
|
-
forCombobox
|
|
120
|
-
#combobox="forCombobox"
|
|
121
|
-
class="combobox"
|
|
122
|
-
[(query)]="query"
|
|
123
|
-
[(value)]="value"
|
|
124
|
-
ariaLabel="Country search"
|
|
125
|
-
>
|
|
118
|
+
<div forCombobox #combobox="forCombobox" class="combobox" [(query)]="query" [(value)]="value">
|
|
126
119
|
<div class="combobox-single">
|
|
127
120
|
<input
|
|
128
121
|
forComboboxInput
|
|
122
|
+
aria-label="Country search"
|
|
129
123
|
class="combobox-input combobox-input--boxed"
|
|
130
124
|
placeholder="Search countries…"
|
|
131
125
|
/>
|
|
@@ -192,7 +186,7 @@ Click the `[forComboboxTrigger]` button, type into the search field inside the p
|
|
|
192
186
|
|
|
193
187
|
### Virtualized (1,000 options)
|
|
194
188
|
|
|
195
|
-
The primitive never owns the scroll container, so it virtualizes with any windowing strategy. The demo uses a dependency-free one. The consumer renders only the visible window and wires `[totalCount]
|
|
189
|
+
The primitive never owns the scroll container, so it virtualizes with any windowing strategy. The demo uses a dependency-free one. The consumer renders only the visible window, adds `forComboboxVirtualized` to the combobox, and wires its `[totalCount]` and `[visibleRange]` plus `[forComboboxOption][posInSet]`; `(scrollToIndex)` fires when navigation targets a row outside the window.
|
|
196
190
|
|
|
197
191
|
## API
|
|
198
192
|
|
|
@@ -225,6 +219,9 @@ The content anchors to the field box when a `[forComboboxAnchor]` is present; se
|
|
|
225
219
|
| `[forCombobox]` | `data-dirty` | present / absent |
|
|
226
220
|
| `[forCombobox]` | `data-pending` | present / absent |
|
|
227
221
|
| `[forCombobox]` | `data-invalid` | present / absent |
|
|
222
|
+
| `[forComboboxTrigger]` | `data-state` | `open` \| `closed` |
|
|
223
|
+
| `[forComboboxTrigger]` | `data-disabled` | present / absent |
|
|
224
|
+
| `[forComboboxTrigger]` | `data-readonly` | present / absent |
|
|
228
225
|
| `[forComboboxInput]` | `data-state` | `open` \| `closed` |
|
|
229
226
|
| `[forComboboxInput]` | `data-disabled` | present / absent |
|
|
230
227
|
| `[forComboboxToggle]` | `data-state` | `open` \| `closed` |
|
|
@@ -299,7 +296,7 @@ The default (editable) anatomy is a text field that filters in place. A **picker
|
|
|
299
296
|
|
|
300
297
|
Add two parts:
|
|
301
298
|
|
|
302
|
-
- **`[forComboboxTrigger]`**: a real `<button>` outside the `@if (open())`. It opens the panel, becomes the default positioning anchor, and is where focus returns on close.
|
|
299
|
+
- **`[forComboboxTrigger]`**: a real `<button>` outside the `@if (open())`. It opens the panel, becomes the default positioning anchor, and is where focus returns on close. It carries `role="combobox"` and announces the form state (`aria-required`, `aria-invalid`, `aria-busy`, `aria-readonly`), because while the panel is closed it is the only focusable part of the field. Give it a label: a combobox is not named by its text.
|
|
303
300
|
- **`[forComboboxList]`**: the `role="listbox"` element nested inside `[forComboboxContent]`, next to the input. It owns the options and the labelled role; `[forComboboxContent]` becomes the `role="dialog"` the trigger opens, named by the trigger.
|
|
304
301
|
|
|
305
302
|
```html
|
|
@@ -669,14 +666,17 @@ The same three inputs cover multi mode + chips: bind `<span forComboboxChip [val
|
|
|
669
666
|
|
|
670
667
|
## Virtualization
|
|
671
668
|
|
|
672
|
-
For very large option sets (1k+) the consumer can render only the visible window and let the directive coordinate navigation across the full source.
|
|
669
|
+
For very large option sets (1k+) the consumer can render only the visible window and let the directive coordinate navigation across the full source. The wiring lives on `ForComboboxVirtualized` (`[forComboboxVirtualized]`, `exportAs: 'forComboboxVirtualized'`), placed on the same element as `[forCombobox]` and imported from `forty-cdk/combobox` next to `ForCombobox`. Non-virtualized usage is unchanged, and a combobox without the directive never bundles the virtualized navigation engine.
|
|
673
670
|
|
|
674
|
-
|
|
|
675
|
-
| ------------------------------------------- |
|
|
676
|
-
| `[totalCount]: number \| undefined` | Length of the **filtered** source array. Drives `aria-setsize` and lets navigation walk past the rendered window. Leave `undefined` (default) for non-virtualized lists.
|
|
677
|
-
| `[visibleRange]: [start, end) \| undefined` | Inclusive-exclusive index range currently rendered in the DOM. Pulled from your virtualizer's `getVirtualItems()`.
|
|
678
|
-
| `[
|
|
679
|
-
| `(scrollToIndex)` | Emitted when arrow keys (or Home / End) need to land on an option whose absolute index falls outside `visibleRange()`. Wire to the virtualizer's `scrollToIndex(idx)`.
|
|
671
|
+
| `ForComboboxVirtualized` member | Purpose |
|
|
672
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
673
|
+
| `[totalCount]: number \| undefined` | Length of the **filtered** source array. Drives `aria-setsize` and lets navigation walk past the rendered window. Leave `undefined` (default) for non-virtualized lists. |
|
|
674
|
+
| `[visibleRange]: [start, end) \| undefined` | Inclusive-exclusive index range currently rendered in the DOM. Pulled from your virtualizer's `getVirtualItems()`. |
|
|
675
|
+
| `[dataVersion]: unknown` | Bind any value that changes when the source is re-sorted or refreshed without its length changing, so navigation never lands on a stale off-window entry. |
|
|
676
|
+
| `(scrollToIndex)` | Emitted when arrow keys (or Home / End) need to land on an option whose absolute index falls outside `visibleRange()`. Wire to the virtualizer's `scrollToIndex(idx)`. |
|
|
677
|
+
| `invalidateSnapshot()` | The imperative twin of `[dataVersion]`, for a same-length refresh you cannot express through the input. No-op while `totalCount` is unset. |
|
|
678
|
+
|
|
679
|
+
Each rendered option also binds `[forComboboxOption][posInSet]`, its absolute index in the source array. It is required when virtualizing: the directive keeps option data keyed by this index so it survives unmount.
|
|
680
680
|
|
|
681
681
|
How navigation flows when virtualizing:
|
|
682
682
|
|
|
@@ -692,6 +692,7 @@ Inline autocomplete matches against the most recently rendered window overlaid w
|
|
|
692
692
|
```html
|
|
693
693
|
<div
|
|
694
694
|
forCombobox
|
|
695
|
+
forComboboxVirtualized
|
|
695
696
|
[(query)]="query"
|
|
696
697
|
[(value)]="value"
|
|
697
698
|
[(open)]="open"
|
|
@@ -742,11 +743,11 @@ This uses the library's own [`injectVirtualizer`](../virtualization/README.md) c
|
|
|
742
743
|
|
|
743
744
|
`[totalCount]` is also the result count the message pieces read: `[forComboboxStatus]`'s `count()` reports it rather than the size of the rendered window, so `{{ status.count() }} results.` announces the full count once and stays put while the window scrolls, and `[forComboboxEmpty]` stays hidden while a window with results has not rendered yet.
|
|
744
745
|
|
|
745
|
-
When `[totalCount]` is omitted, the
|
|
746
|
+
When `[forComboboxVirtualized]` is absent or its `[totalCount]` is omitted, the combobox falls back to `options().length` and behaves exactly as before: `aria-setsize` is left to the platform default and navigation never emits `(scrollToIndex)`.
|
|
746
747
|
|
|
747
748
|
> **Disabled options off-screen.** The directive learns an option's `disabled` only when it's been rendered at least once. While the consumer can pre-mark disabled rows with their own filter (most apps do), arrow nav cannot skip an off-screen disabled option it has never seen: it will land on it, the option will mount, and the next arrow press skips. Mark disabled rows in the source array if this matters.
|
|
748
749
|
|
|
749
|
-
> **Listbox virtualization** ships the same contract: `[forListbox]` defaults to roving tabindex (DOM focus on the actual option element) and switches to the `aria-activedescendant` model when you
|
|
750
|
+
> **Listbox virtualization** ships the same contract: `[forListbox]` defaults to roving tabindex (DOM focus on the actual option element) and switches to the `aria-activedescendant` model when you add `[forListboxVirtualized]` with `[totalCount]`. See the [Listbox README "Virtualization"](../listbox/README.md#virtualization) section.
|
|
750
751
|
|
|
751
752
|
## Writing direction
|
|
752
753
|
|
|
@@ -838,10 +839,11 @@ Hovering an option also makes it the activedescendant, so mouse and keyboard int
|
|
|
838
839
|
|
|
839
840
|
Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).
|
|
840
841
|
|
|
842
|
+
- **Name the input.** `role="combobox"` takes its name from the author, and a placeholder is only a fallback that disappears once the user types. The root's `ariaLabel` names the listbox, not the input. Give `[forComboboxInput]` a `<label for>`, an `aria-label` or an `aria-labelledby`, or wrap the combobox in `[forField]` with a `[forLabel]`. In the picker anatomy, label `[forComboboxTrigger]` the same way, for example `aria-labelledby` pointing at a visible label and at the element showing the value.
|
|
841
843
|
- Apply the input directive to an actual `<input>`: the browser's caret and selection semantics are what make inline autocomplete work, and screen readers expect a real text field for `role="combobox"`.
|
|
842
844
|
- `role="listbox"` lives on `[forComboboxContent]` in the editable anatomy and on `[forComboboxList]` in the picker anatomy; the input's `aria-controls` targets whichever carries it. In the picker anatomy the popup surface (`[forComboboxContent]`) is a `role="dialog"` named by the trigger (`aria-labelledby`; a static `aria-label` or `aria-labelledby` on it wins), so it can hold the input next to the list without an `aria-required-owned-elements` violation. The "editable + list" shape, with no trigger, leaves it role-less.
|
|
843
845
|
- `aria-multiselectable="true"` (multi mode) and the labelled role (`aria-label` / `aria-labelledby`, pointing at the input) sit on whichever element carries `role="listbox"`: content in the editable anatomy, the list in the picker anatomy.
|
|
844
|
-
- `[forComboboxTrigger]` (picker anatomy) is a real `<button>` reflecting `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls` (the dialog surface, while open), an `id` (a static one is kept), and native `disabled` from the combobox's effective disabled. The popup is a dialog rather than a listbox because focus moves into the search input inside it. It is exempt from the popup's outside-pointer dismissal layer, like the input.
|
|
846
|
+
- `[forComboboxTrigger]` (picker anatomy) is a real `<button>` with `role="combobox"`, reflecting `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls` (the dialog surface, while open), an `id` (a static one is kept), and native `disabled` from the combobox's effective disabled. It also reflects `aria-required`, `aria-invalid`, `aria-busy` and `aria-readonly` (truthy-only) from the root, so a closed picker announces a required, failing or validating field: the input carrying the same state only exists while the panel is open. It keeps the role while the dialog is open, next to the `role="combobox"` search input inside it. The popup is a dialog rather than a listbox because focus moves into the search input inside it. It is exempt from the popup's outside-pointer dismissal layer, like the input.
|
|
845
847
|
- `[forComboboxContent]` and `[forComboboxList]` cancel a `mousedown` that would otherwise move focus onto the surface or blur the input, so a press between options leaves focus in the input. A press on a focusable descendant is let through.
|
|
846
848
|
- `[forComboboxToggle]` (editable anatomy) is a real `<button>` with `tabindex="-1"`, a localizable `aria-label`, `aria-expanded`, `aria-controls` (the listbox, while open) and native `disabled`. It cancels `mousedown` so focus stays in the input, and it is exempt from the outside-pointer dismissal layer.
|
|
847
849
|
- `[forComboboxClear]` has `tabindex="-1"` and a localizable `aria-label`. It cancels `mousedown` too, and is exempt from the outside-pointer dismissal layer, so clearing an open listbox leaves it open with focus in the input and the combobox untouched.
|
|
@@ -885,4 +887,4 @@ forty-cdk ships no styles: put your own class on each piece and key your CSS off
|
|
|
885
887
|
|
|
886
888
|
## Wrapping in a design system
|
|
887
889
|
|
|
888
|
-
[Wrapping form primitives](../../../docs/wrapping-form-primitives.md) documents both supported wrapper patterns: `hostDirectives` with the exported `FOR_COMBOBOX_HOST_DIRECTIVE_INPUTS` / `FOR_COMBOBOX_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing.
|
|
890
|
+
[Wrapping form primitives](../../../docs/wrapping-form-primitives.md) documents both supported wrapper patterns: `hostDirectives` with the exported `FOR_COMBOBOX_HOST_DIRECTIVE_INPUTS` / `FOR_COMBOBOX_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing. A virtualized wrapper adds `ForComboboxVirtualized` as a second `hostDirectives` entry and re-exposes its surface with `FOR_COMBOBOX_VIRTUALIZED_HOST_DIRECTIVE_INPUTS` / `FOR_COMBOBOX_VIRTUALIZED_HOST_DIRECTIVE_OUTPUTS`.
|
package/date-field/README.md
CHANGED
|
@@ -99,14 +99,14 @@ With a time-capable adapter, a `granularity` finer than `'day'` appends hour / m
|
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
101
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
102
|
-
import { form } from '@angular/forms/signals';
|
|
102
|
+
import { form, FormField } from '@angular/forms/signals';
|
|
103
103
|
import { CalendarDate } from '@internationalized/date';
|
|
104
104
|
import { ForDateField, ForDateFieldLiteral, ForDateFieldSegment } from 'forty-cdk/date-field';
|
|
105
105
|
|
|
106
106
|
@Component({
|
|
107
107
|
selector: 'app-dob-form',
|
|
108
108
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
109
|
-
imports: [ForDateField, ForDateFieldSegment, ForDateFieldLiteral],
|
|
109
|
+
imports: [ForDateField, ForDateFieldSegment, ForDateFieldLiteral, FormField],
|
|
110
110
|
template: `
|
|
111
111
|
<div
|
|
112
112
|
forDateField
|
|
@@ -135,7 +135,7 @@ export class DobFormField {
|
|
|
135
135
|
|
|
136
136
|
### Date range
|
|
137
137
|
|
|
138
|
-
`ForDateRangeField` is the range variant, shipped from this same entry point. Two labelled endpoint groups share locale, granularity and bounds; each is its own tab stop, so `Tab` steps start → end while the arrows move between segments inside one endpoint.
|
|
138
|
+
`ForDateRangeField` is the range variant, shipped from this same entry point. Two labelled endpoint groups share locale, granularity and bounds; each is its own tab stop, so `Tab` steps start → end while the arrows move between segments inside one endpoint. Type an end date before the start and a message under the field says so, read from `disordered()`, since the red border alone is a colour cue only.
|
|
139
139
|
|
|
140
140
|
### Date & time range
|
|
141
141
|
|
|
@@ -143,7 +143,7 @@ With a time-capable adapter, a `granularity` finer than `'day'` appends time seg
|
|
|
143
143
|
|
|
144
144
|
### Range in Signal Forms
|
|
145
145
|
|
|
146
|
-
`ForDateRangeField` implements `FormValueControl<DateRange | null>`, so `[formField]` binds the committed range into the form and pulls validation back out. A half-entered or out-of-order range keeps `value()` null, so a `required` field stays invalid until both endpoints are filled and ordered.
|
|
146
|
+
`ForDateRangeField` implements `FormValueControl<DateRange | null>`, so `[formField]` binds the committed range into the form and pulls validation back out. A half-entered or out-of-order range keeps `value()` null, so a `required` field stays invalid until both endpoints are filled and ordered. The error message tells the two cases apart through `disordered()`.
|
|
147
147
|
|
|
148
148
|
## API
|
|
149
149
|
|
|
@@ -191,7 +191,7 @@ A native reset of the owning `<form>` returns `value` to what it held when the f
|
|
|
191
191
|
| `[forDateFieldSegment]` | `data-disabled` | present \| absent |
|
|
192
192
|
| `[forDateFieldSegment]` | `data-readonly` | present \| absent |
|
|
193
193
|
|
|
194
|
-
`data-empty` marks the field only while **every** editable segment is empty (nothing has been entered); a partially-typed field is **not** empty. `data-placeholder` marks each individual segment that is still empty. `data-highlighted`
|
|
194
|
+
`data-empty` marks the field only while **every** editable segment is empty (nothing has been entered); a partially-typed field is **not** empty. `data-placeholder` marks each individual segment that is still empty. `data-highlighted` marks the roving tab-stop segment, the vocabulary shared with the other roving primitives. It stays on that segment after focus leaves the field, so a style that reads as focus is scoped to the field's `:focus-within`, and to each endpoint group's in a range field, where each endpoint keeps its own. `[forDateFieldLiteral]` carries no data-\* attributes (it is `aria-hidden` and out of the tab order).
|
|
195
195
|
|
|
196
196
|
## Date-time field
|
|
197
197
|
|
|
@@ -306,7 +306,7 @@ The endpoint groups each accept an `ariaLabel` input for their own group label,
|
|
|
306
306
|
|
|
307
307
|
### Ordering
|
|
308
308
|
|
|
309
|
-
The two endpoints are typed independently, so order is not guaranteed by construction the way the picker's two-click flow guarantees it. The field preserves the `DateRange` `end >= start` invariant by **never emitting an out-of-order range**: when both endpoints are complete but `start > end`, the typed segments are kept (not silently rewritten), `value` stays `null`, and the root reflects `aria-invalid="true"` + `data-range-error` so the disorder is perceivable and stylable. Editing either endpoint back into order emits the range.
|
|
309
|
+
The two endpoints are typed independently, so order is not guaranteed by construction the way the picker's two-click flow guarantees it. The field preserves the `DateRange` `end >= start` invariant by **never emitting an out-of-order range**: when both endpoints are complete but `start > end`, the typed segments are kept (not silently rewritten), `value` stays `null`, and the root reflects `aria-invalid="true"` + `data-range-error` so the disorder is perceivable and stylable. Editing either endpoint back into order emits the range. The border colour alone is not an error message, so read the same state from `disordered()` (through `#range="forDateRangeField"`) and say it in words, as the Date range example does.
|
|
310
310
|
|
|
311
311
|
### Range keyboard and accessibility
|
|
312
312
|
|
|
@@ -339,13 +339,13 @@ Composes the [WAI-ARIA Spinbutton pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
339
339
|
- **`role="spinbutton"`** per segment, with `aria-valuemin` / `aria-valuemax` / `aria-valuenow` reflected; the month segment also exposes a localized `aria-valuetext` ("March"), so screen readers read the name rather than the number.
|
|
340
340
|
- **Roving tabindex**: exactly one segment is tabbable, so `Tab` enters and leaves the whole field in one stop; arrows move between segments.
|
|
341
341
|
- **Literals are `aria-hidden`** and never focusable, so assistive tech reads only the spinbutton segments.
|
|
342
|
-
- **Boolean `data-*`** on each segment, present when true and absent when false: `data-highlighted` (
|
|
342
|
+
- **Boolean `data-*`** on each segment, present when true and absent when false: `data-highlighted` (the roving tab stop, kept after blur), `data-placeholder` (empty), `data-disabled`, `data-readonly`.
|
|
343
343
|
- **`aria-readonly` belongs on the segments, not the group.** WAI-ARIA supports it on `role="spinbutton"` but not on `role="group"`, so each segment carries `aria-readonly="true"` while the group reflects the `data-readonly` styling hook only.
|
|
344
344
|
- **On-screen keyboards work.** A segment of a field that is neither disabled nor read-only is a `contenteditable="plaintext-only"` editing host with `inputmode="numeric"`, so tapping it on a touch device opens the numeric keypad (the AM/PM segment opens the text keyboard). What the keyboard or an input method types goes through the [Keyboard](#keyboard) map above and is never inserted into the segment, which keeps rendering `{{ seg.text }}`.
|
|
345
345
|
|
|
346
346
|
## Styling
|
|
347
347
|
|
|
348
|
-
The library is styleless, so style the boolean `data-*` hooks yourself: `[data-highlighted]` (the
|
|
348
|
+
The library is styleless, so style the boolean `data-*` hooks yourself: `[data-highlighted]` (the roving tab-stop segment, kept after focus leaves the field), `[data-placeholder]` (empty), `[data-disabled]` and `[data-readonly]` on the segments, and `[data-empty]` / `[data-disabled]` / `[data-readonly]` / `[data-required]` plus the form-state hooks `[data-touched]` / `[data-dirty]` / `[data-pending]` / `[data-invalid]` on the root group.
|
|
349
349
|
|
|
350
350
|
forty-cdk ships no styles: put your own class on each piece and key your CSS off the `data-*` attributes listed under [Data attributes](#data-attributes), not off the `for*` selectors ([Styling forty-cdk](../../../docs/styling.md) explains why).
|
|
351
351
|
|
|
@@ -355,7 +355,7 @@ A segment is an editing host, so the browser paints a text caret in the focused
|
|
|
355
355
|
.date-field-segment[data-placeholder] {
|
|
356
356
|
color: GrayText;
|
|
357
357
|
}
|
|
358
|
-
.date-field-segment[data-highlighted] {
|
|
358
|
+
.date-field:focus-within .date-field-segment[data-highlighted] {
|
|
359
359
|
caret-color: transparent;
|
|
360
360
|
background: Highlight;
|
|
361
361
|
color: HighlightText;
|
package/date-picker/README.md
CHANGED
|
@@ -94,14 +94,13 @@ import { provideInternationalizedDateAdapter } from 'forty-cdk/internationalized
|
|
|
94
94
|
],
|
|
95
95
|
providers: [...provideInternationalizedDateAdapter()],
|
|
96
96
|
template: `
|
|
97
|
-
<div
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<button forDatePickerTrigger type="button" class="date-picker-trigger">
|
|
97
|
+
<div forDatePicker #picker="forDatePicker" [(value)]="date" placeholder="Pick a date">
|
|
98
|
+
<button
|
|
99
|
+
forDatePickerTrigger
|
|
100
|
+
aria-label="Choose a date"
|
|
101
|
+
type="button"
|
|
102
|
+
class="date-picker-trigger"
|
|
103
|
+
>
|
|
105
104
|
<span forDatePickerValue class="date-picker-value"></span>
|
|
106
105
|
<svg class="date-picker-chevron" viewBox="0 0 24 24" aria-hidden="true">
|
|
107
106
|
<path
|
package/drag-drop/README.md
CHANGED
|
@@ -157,7 +157,10 @@ whole gesture (and past the drop, while a settle transition runs) it answers the
|
|
|
157
157
|
selector (`[forDraggable]`, or a composed one such as `[forTableRow]`) and repeats its `data-index`.
|
|
158
158
|
`id` and `data-testid` are stripped from the clone and its whole subtree, so a hook that identifies
|
|
159
159
|
a single element stays unambiguous; anything that **enumerates** items by attribute selector during
|
|
160
|
-
a drag must filter the preview out with `:not([data-for-drag-preview])`.
|
|
160
|
+
a drag must filter the preview out with `:not([data-for-drag-preview])`. The clone is also made
|
|
161
|
+
inert before it joins the document: `name`, `form`, `autofocus` and `autoplay` are stripped, and
|
|
162
|
+
every `iframe`, `object` and `embed` becomes an empty box of the same size, so a cloned radio never
|
|
163
|
+
unchecks the item's own and no cloned media plays or frame reloads.
|
|
161
164
|
|
|
162
165
|
## Keyboard
|
|
163
166
|
|
|
@@ -263,6 +266,19 @@ unaffected.
|
|
|
263
266
|
</li>
|
|
264
267
|
```
|
|
265
268
|
|
|
269
|
+
A handle carries `touch-action: none`, so a touch drag on it is not taken by the page's
|
|
270
|
+
scrolling, and the item around it leaves touch scrolling alone. Both follow the item: a handle
|
|
271
|
+
mounted or removed after the first render moves the `touch-action: none` between the handle and
|
|
272
|
+
the item, and a disabled item or list drops it from both.
|
|
273
|
+
|
|
274
|
+
### Native drag
|
|
275
|
+
|
|
276
|
+
Images, links and selected text are draggable by the browser itself, and its own drag starts
|
|
277
|
+
before a pointer drag arms. A `[forDraggable]` or `[forFreeDrag]` therefore cancels the native
|
|
278
|
+
`dragstart` of every press it would turn into a pointer drag, so pressing a thumbnail inside an
|
|
279
|
+
item reorders the item instead of dragging a copy of the image. Content outside the item's
|
|
280
|
+
handles, and every press while the item or its list is disabled, keeps its native drag.
|
|
281
|
+
|
|
266
282
|
### Nested controls
|
|
267
283
|
|
|
268
284
|
A control inside a draggable item keeps its press from starting a drag by calling
|
|
@@ -286,6 +302,28 @@ the press stays with the control; lifting with the keyboard is unaffected.
|
|
|
286
302
|
- A canceled `pointerdown` suppresses the compatibility mouse events of that press (`mousedown`,
|
|
287
303
|
`mouseup`), so the control must not rely on them. `click` still fires.
|
|
288
304
|
|
|
305
|
+
A `[forDraggable]` or `[forFreeDrag]` nested inside another one needs no `preventDefault()`: every
|
|
306
|
+
press inside it is its own, and the item around it never starts a drag from there, even while the
|
|
307
|
+
nested one is disabled. On a board whose columns are the items of one list and hold a list of
|
|
308
|
+
cards each, pressing a card drags the card, and pressing a column outside its cards drags the
|
|
309
|
+
column. Each item lifts with its own `[forDragPreview]` / `[forDragPlaceholder]`, never with one
|
|
310
|
+
declared inside a nested item.
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
<div forDropList orientation="horizontal" (dragDrop)="moveColumn($event)">
|
|
314
|
+
@for (column of columns(); track column.id) {
|
|
315
|
+
<section forDraggable [dragData]="column">
|
|
316
|
+
<h2>{{ column.title }}</h2>
|
|
317
|
+
<ul forDropList (dragDrop)="moveCard(column, $event)">
|
|
318
|
+
@for (card of column.cards; track card.id) {
|
|
319
|
+
<li forDraggable [dragData]="card">{{ card.title }}</li>
|
|
320
|
+
}
|
|
321
|
+
</ul>
|
|
322
|
+
</section>
|
|
323
|
+
}
|
|
324
|
+
</div>
|
|
325
|
+
```
|
|
326
|
+
|
|
289
327
|
### Custom preview & placeholder
|
|
290
328
|
|
|
291
329
|
Place `<ng-template forDragPreview>` and/or `<ng-template forDragPlaceholder>` as direct
|
|
@@ -398,7 +436,9 @@ always provided by the consumer via CSS; the library imposes none.
|
|
|
398
436
|
```
|
|
399
437
|
|
|
400
438
|
With no such CSS, `animateReorder` is a graceful no-op: transforms clear instantly and the
|
|
401
|
-
preview is destroyed promptly.
|
|
439
|
+
preview is destroyed promptly. Otherwise the preview is destroyed when its `transform` transition
|
|
440
|
+
ends, and never before that transition's `transition-delay` plus duration have elapsed, wherever
|
|
441
|
+
`transform` sits in a multi-property `transition` list.
|
|
402
442
|
|
|
403
443
|
## Orientation
|
|
404
444
|
|
|
@@ -453,10 +493,14 @@ the CSS `translate` property. It is the standalone counterpart to a sortable lis
|
|
|
453
493
|
pointer-driven way to move an element freely, with optional root-element retargeting, a confinement
|
|
454
494
|
boundary, axis locking, and a controllable position (see the inputs below).
|
|
455
495
|
|
|
456
|
-
The offset composes with
|
|
457
|
-
`transform: translate(-50%, -50%)`
|
|
458
|
-
`translate
|
|
459
|
-
|
|
496
|
+
The offset composes with the `transform` and the `translate` the moved element already has, inline
|
|
497
|
+
or from a stylesheet, so a dialog centred with `transform: translate(-50%, -50%)` or
|
|
498
|
+
`translate: -50% -50%` stays centred and moves from there. The `translate` it composes with is read
|
|
499
|
+
when the element first leaves its rest position, and the element's own `translate` comes back once
|
|
500
|
+
`position` returns to `{ x: 0, y: 0 }`.
|
|
501
|
+
|
|
502
|
+
Scrolling the page or a scroll container during a drag keeps the element under the pointer, and the
|
|
503
|
+
`boundary` clamp follows the scrolled geometry.
|
|
460
504
|
|
|
461
505
|
```html
|
|
462
506
|
<!-- move the whole dialog by its header -->
|
|
@@ -483,7 +527,7 @@ properties rather than `translate`.
|
|
|
483
527
|
| Output | Payload | Fires |
|
|
484
528
|
| ----------- | ---------- | ---------------------------------------------------------------------------------------------- |
|
|
485
529
|
| `moveStart` | `{ x; y }` | When a pointer drag starts (the lift-time position). |
|
|
486
|
-
| `moveMove` | `{ x; y }` | On every armed move, with the live position.
|
|
530
|
+
| `moveMove` | `{ x; y }` | On every armed move, and when a scroll moves the element, with the live position. |
|
|
487
531
|
| `moveEnd` | `{ x; y }` | When the drag ends (commit keeps the position; cancel/Escape restores the lift-time snapshot). |
|
|
488
532
|
|
|
489
533
|
`[forDragHandle]` works inside `[forFreeDrag]` exactly as it does inside `[forDraggable]`: once any
|
|
@@ -533,8 +577,12 @@ other list, because their reorder output describes a move within one dataset.
|
|
|
533
577
|
[virtualCount]="rows().length"
|
|
534
578
|
forVirtualReorder
|
|
535
579
|
(itemReorder)="onReorder($event)"
|
|
580
|
+
role="list"
|
|
581
|
+
aria-label="Rows"
|
|
536
582
|
>
|
|
537
|
-
<div *forVirtualFor="let row of rows()" forDraggable [dragData]="row.id">
|
|
583
|
+
<div *forVirtualFor="let row of rows()" role="listitem" forDraggable [dragData]="row.id">
|
|
584
|
+
{{ row.label }}
|
|
585
|
+
</div>
|
|
538
586
|
</div>
|
|
539
587
|
```
|
|
540
588
|
|