forty-cdk 0.12.0 → 0.13.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.
Files changed (74) hide show
  1. package/combobox/README.md +33 -6
  2. package/date-field/README.md +9 -8
  3. package/date-range-field/README.md +9 -8
  4. package/drawer/README.md +14 -0
  5. package/fesm2022/forty-cdk-calendar.mjs +2 -11
  6. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  7. package/fesm2022/forty-cdk-carousel.mjs +6 -16
  8. package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
  9. package/fesm2022/forty-cdk-combobox.mjs +150 -54
  10. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  11. package/fesm2022/forty-cdk-core.mjs +1477 -733
  12. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  13. package/fesm2022/forty-cdk-date-field.mjs +18 -58
  14. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  15. package/fesm2022/forty-cdk-date-picker.mjs +1 -1
  16. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  17. package/fesm2022/forty-cdk-date-range-field.mjs +26 -13
  18. package/fesm2022/forty-cdk-date-range-field.mjs.map +1 -1
  19. package/fesm2022/forty-cdk-dialog.mjs +51 -237
  20. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  21. package/fesm2022/forty-cdk-drawer.mjs +133 -229
  22. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  23. package/fesm2022/forty-cdk-hover-card.mjs +116 -105
  24. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
  25. package/fesm2022/forty-cdk-internationalized-date.mjs +3 -21
  26. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  27. package/fesm2022/forty-cdk-listbox.mjs +45 -161
  28. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  29. package/fesm2022/forty-cdk-popover.mjs +7 -99
  30. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  31. package/fesm2022/forty-cdk-select.mjs +114 -180
  32. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  33. package/fesm2022/forty-cdk-stepper.mjs +30 -19
  34. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  35. package/fesm2022/forty-cdk-table.mjs +2 -2
  36. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  37. package/fesm2022/forty-cdk-time-field.mjs +19 -60
  38. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  39. package/fesm2022/forty-cdk-time-picker.mjs +13 -15
  40. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  41. package/fesm2022/forty-cdk-time-range-field.mjs +39 -14
  42. package/fesm2022/forty-cdk-time-range-field.mjs.map +1 -1
  43. package/fesm2022/forty-cdk-toast.mjs +43 -21
  44. package/fesm2022/forty-cdk-toast.mjs.map +1 -1
  45. package/fesm2022/forty-cdk-tooltip.mjs +13 -96
  46. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
  47. package/fesm2022/forty-cdk-virtualization.mjs +12 -3
  48. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  49. package/hover-card/README.md +56 -8
  50. package/package.json +1 -1
  51. package/select/README.md +9 -7
  52. package/stepper/README.md +27 -1
  53. package/table/README.md +3 -3
  54. package/time-field/README.md +9 -8
  55. package/time-range-field/README.md +12 -11
  56. package/toast/README.md +4 -1
  57. package/tooltip/README.md +15 -15
  58. package/types/forty-cdk-combobox.d.ts +62 -15
  59. package/types/forty-cdk-core.d.ts +926 -231
  60. package/types/forty-cdk-date-field.d.ts +16 -24
  61. package/types/forty-cdk-date-picker.d.ts +1 -1
  62. package/types/forty-cdk-date-range-field.d.ts +18 -11
  63. package/types/forty-cdk-dialog.d.ts +31 -122
  64. package/types/forty-cdk-drawer.d.ts +34 -77
  65. package/types/forty-cdk-hover-card.d.ts +55 -72
  66. package/types/forty-cdk-popover.d.ts +6 -90
  67. package/types/forty-cdk-select.d.ts +29 -7
  68. package/types/forty-cdk-stepper.d.ts +34 -13
  69. package/types/forty-cdk-time-field.d.ts +17 -26
  70. package/types/forty-cdk-time-picker.d.ts +12 -5
  71. package/types/forty-cdk-time-range-field.d.ts +31 -12
  72. package/types/forty-cdk-toast.d.ts +8 -1
  73. package/types/forty-cdk-tooltip.d.ts +17 -86
  74. package/types/forty-cdk-virtualization.d.ts +9 -2
@@ -36,6 +36,21 @@ The editable (default) anatomy — an `<input>` that filters a portaled listbox
36
36
  </div>
37
37
  ```
38
38
 
39
+ **Editable + list (no trigger).** Wrapping the options in a `[forComboboxList]` without adding a `[forComboboxTrigger]` is a supported shape, and the a11y-clean way to add non-option pieces (`[forComboboxEmpty]`, `[forComboboxStatus]`, `[forComboboxAction]`) to the editable anatomy. Because content carries `role="listbox"` (which may only own `option` / `group` children), moving the options into `[forComboboxList]` makes those pieces siblings of the listbox instead of invalid listbox children — content becomes role-less and the list owns the listbox role. The role split keys off `hasList`, the focus model off `trigger()`, so focus still stays on the input the whole time:
40
+
41
+ ```html
42
+ <div forComboboxContent>
43
+ <div forComboboxList>
44
+ <div forComboboxOption [value]="item.id" [label]="item.label">{{ item.label }}</div>
45
+ </div>
46
+ <div forComboboxEmpty>No matches.</div>
47
+ </div>
48
+ ```
49
+
50
+ A `[forComboboxAction]` **requires** this shape — see [Action items](#action-items).
51
+
52
+ > **Editable-anatomy caveat.** For the common case of options plus only a `[forComboboxEmpty]` / `[forComboboxStatus]` message (the bare anatomy above, no `[forComboboxList]`), the message sits directly inside `[forComboboxContent]`. This is still supported and does not throw, but it leaves the `role="status"` message as an owned child of `role="listbox"`, a minor `aria-required-owned` compromise; wrap the options in a `[forComboboxList]` (the "editable + list" shape) when you want the strictly-clean tree.
53
+
39
54
  The picker anatomy adds a `[forComboboxTrigger]` `<button>` showing the committed selection, with the search input and a `[forComboboxList]` (`role="listbox"`) nested inside the popup — see [Picker anatomy](#picker-anatomy). Multi mode wraps the chips + input in `[forComboboxChips]` — see [Multi mode](#multi-mode). Optional `[forComboboxAnchor]`, `[forComboboxStatus]`, `[forComboboxGroup]` / `[forComboboxGroupLabel]`, and `[forComboboxSeparator]` pieces are covered in their own sections below.
40
55
 
41
56
  ## Examples
@@ -204,15 +219,24 @@ A combobox popup often needs an entry that is an **action**, not a value —
204
219
  `role="button"` actions, not `role="option"` selections, so `[forComboboxAction]`
205
220
  renders one that stays out of the option/value collection entirely.
206
221
 
222
+ `[forComboboxAction]` **requires a `[forComboboxList]`**: content carries
223
+ `role="listbox"` in the editable anatomy, so a `role="button"` placed directly
224
+ inside it would be an invalid listbox child (`aria-required-owned`). Wrap the
225
+ options in a `[forComboboxList]` so the action becomes a sibling of the listbox.
226
+ An action rendered without a `[forComboboxList]` throws `[forty-cdk/combobox]` at
227
+ runtime. This is the "editable + list" shape (no `[forComboboxTrigger]` needed).
228
+
207
229
  ```html
208
230
  <div forCombobox #combobox="forCombobox" [(query)]="query" [(value)]="value">
209
231
  <input forComboboxInput placeholder="Search…" />
210
232
  @if (combobox.open()) {
211
233
  <div forComboboxContent>
212
234
  <button forComboboxAction (action)="createNew(query())">Create "{{ query() }}"</button>
213
- @for (it of filtered; track it.id) {
214
- <div forComboboxOption [value]="it.id" [label]="it.label">{{ it.label }}</div>
215
- }
235
+ <div forComboboxList>
236
+ @for (it of filtered; track it.id) {
237
+ <div forComboboxOption [value]="it.id" [label]="it.label">{{ it.label }}</div>
238
+ }
239
+ </div>
216
240
  </div>
217
241
  }
218
242
  </div>
@@ -249,9 +273,9 @@ returns focus to the input (editable anatomy) or the `[forComboboxTrigger]`
249
273
  only. With no action registered, Tab keeps its default "close and let Tab flow on"
250
274
  behaviour, so existing comboboxes are unchanged.
251
275
 
252
- Actions live inside `[forComboboxContent]` (beside `[forComboboxList]` in the
253
- picker anatomy), so they are naturally "inside" the outside-pointer / outside-focus
254
- dismissal checks, exactly like the input.
276
+ Actions live inside `[forComboboxContent]` and beside `[forComboboxList]` (never
277
+ inside it, in either anatomy), so they are naturally "inside" the outside-pointer /
278
+ outside-focus dismissal checks, exactly like the input.
255
279
 
256
280
  ### API
257
281
 
@@ -382,6 +406,8 @@ The `autocompleteMode` input mirrors the WAI-ARIA `aria-autocomplete` property:
382
406
 
383
407
  Inline completion preserves the user's typed prefix as unselected and selects the appended remainder, so the next keystroke replaces the selection (matching native browser autofill behavior). Backspace deletes the selection without re-completing, so the user can always shorten the query.
384
408
 
409
+ > **Pure `'inline'` needs a warm cache.** `'inline'` never opens the popup (per APG — `aria-autocomplete="inline"` has no listbox), so in the default `@if (open())` anatomy no `[forComboboxOption]` ever renders and the label cache starts cold. A first keystroke into a combobox that has never been opened completes against nothing; inline completion only works once the options have rendered at least once (the user opened the popup via ArrowDown or `[openOnFocus]`, warming the cache). If completion must work from the very first keystroke, use `'both'` (which opens the popup) or keep the options mounted rather than gating them behind `@if (open())`.
410
+
385
411
  ## Dismiss events
386
412
 
387
413
  Each dismiss reason emits a vetoable event from `[forCombobox]` — call `preventDefault()` on the event to keep the listbox open.
@@ -585,6 +611,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
585
611
  - `[forComboboxSeparator]` is decorative and never registers with the listbox's option collection — keyboard navigation skips it automatically.
586
612
  - `[forComboboxGroup]` is purely advisory grouping — options inside still register flatly with the root, so navigation flows through groups without interruption.
587
613
  - `[forComboboxEmpty]` carries `role="status"` + `aria-live="polite"` so the empty-state message is announced when filtering removes all matches.
614
+ - Non-option pieces (`[forComboboxAction]`, and ideally `[forComboboxEmpty]` / `[forComboboxStatus]`) belong inside `[forComboboxContent]` but **outside** `[forComboboxList]` — `role="listbox"` may only own `option` / `group` children (`aria-required-owned-elements`). Wrapping the options in a `[forComboboxList]` (the "editable + list" shape) makes those pieces siblings of the listbox. `[forComboboxAction]` **requires** a `[forComboboxList]` and throws `[forty-cdk/combobox]` without one; `[forComboboxEmpty]` / `[forComboboxStatus]` stay lenient in the bare editable anatomy (documented compromise) — see the [editable-anatomy caveat](#anatomy).
588
615
  - The input element is exempt from the listbox's outside-pointer dismissal layer, so a click on the input while the listbox is open routes through `(click)` (toggle / focus open) instead of double-firing as an outside dismissal.
589
616
 
590
617
  ## Styling
@@ -146,7 +146,7 @@ Plus the shared `FormUiControl` members from `@angular/forms/signals`: `disabled
146
146
  | `[forDateFieldSegment]` | `data-disabled` | present \| absent |
147
147
  | `[forDateFieldSegment]` | `data-readonly` | present \| absent |
148
148
 
149
- `data-empty` marks the whole field while any segment is still unfilled (the value is `null`); `data-placeholder` marks each individual segment that is still empty. `data-highlighted` is the current roving-tabindex segment — the only focus hook the consumer gets, shared with the other roving primitives. `[forDateFieldLiteral]` carries no data-\* attributes (it is `aria-hidden` and out of the tab order).
149
+ `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` is the current roving-tabindex segment — the only focus hook the consumer gets, shared with the other roving primitives. `[forDateFieldLiteral]` carries no data-\* attributes (it is `aria-hidden` and out of the tab order).
150
150
 
151
151
  ## Date-time field
152
152
 
@@ -192,13 +192,14 @@ providers: [
192
192
 
193
193
  Key behavior applies per segment. Horizontal arrows mirror under `dir="rtl"`.
194
194
 
195
- | Key | Behavior |
196
- | -------------------------- | ------------------------------------------------------------------------ |
197
- | **0–9** | Type the value; auto-advances to the next segment when full. |
198
- | **ArrowUp / ArrowDown** | Step the value. Day and month wrap; year clamps. Empty seeds from today. |
199
- | **ArrowLeft / ArrowRight** | Move to the previous / next segment (no wrap). |
200
- | **Home / End** | Jump to the segment minimum / maximum. |
201
- | **Backspace / Delete** | Clear the segment (the value becomes `null` until refilled). |
195
+ | Key | Behavior |
196
+ | -------------------------- | --------------------------------------------------------------------------------------- |
197
+ | **0–9** | Type the value; auto-advances to the next segment when full. |
198
+ | **ArrowUp / ArrowDown** | Step the value. Day and month wrap; year clamps. Empty seeds from today. |
199
+ | **ArrowLeft / ArrowRight** | Move to the previous / next segment (no wrap). |
200
+ | **Home / End** | Jump to the segment minimum / maximum. |
201
+ | **Backspace** | Delete the last entered digit; the value becomes `null` when the last digit is removed. |
202
+ | **Delete** | Clear the whole segment (the value becomes `null` until refilled). |
202
203
 
203
204
  The day clamps to the current month's length (e.g. 31 → 28 in February), and a composed value is clamped into `[minDate, maxDate]`.
204
205
 
@@ -189,7 +189,7 @@ Plus the shared `FormUiControl` members from `@angular/forms/signals`: `disabled
189
189
  | `[forDateRangeFieldSegment]` | `data-disabled` | present \| absent |
190
190
  | `[forDateRangeFieldSegment]` | `data-readonly` | present \| absent |
191
191
 
192
- `data-empty` marks the whole field while the range is `null` (either endpoint unfilled, or the two out of order); `data-range-error` marks the specific case of two complete but out-of-order endpoints; `data-placeholder` marks each individual segment that is still empty. `data-highlighted` is the current roving-tabindex segment — the only focus hook the consumer gets, shared with the other roving primitives.
192
+ `data-empty` marks the field only while **both** endpoints are entirely empty; a partially-filled or complete-but-disordered range is **not** empty (`data-range-error` covers the disorder). `data-range-error` marks the specific case of two complete but out-of-order endpoints; `data-placeholder` marks each individual segment that is still empty. `data-highlighted` is the current roving-tabindex segment — the only focus hook the consumer gets, shared with the other roving primitives.
193
193
 
194
194
  ## Ordering
195
195
 
@@ -222,13 +222,14 @@ providers: [
222
222
 
223
223
  Key behavior applies per segment. Each endpoint is its own tab stop, so `Tab` moves from the start group to the end group to the next control; arrows move between segments **within** an endpoint. Horizontal arrows mirror under `dir="rtl"`.
224
224
 
225
- | Key | Behavior |
226
- | -------------------------- | ------------------------------------------------------------------------ |
227
- | **0–9** | Type the value; auto-advances to the next segment when full. |
228
- | **ArrowUp / ArrowDown** | Step the value. Day and month wrap; year clamps. Empty seeds from today. |
229
- | **ArrowLeft / ArrowRight** | Move to the previous / next segment in the same endpoint (no wrap). |
230
- | **Home / End** | Jump to the segment minimum / maximum. |
231
- | **Backspace / Delete** | Clear the segment (the range becomes `null` until refilled). |
225
+ | Key | Behavior |
226
+ | -------------------------- | --------------------------------------------------------------------------------------- |
227
+ | **0–9** | Type the value; auto-advances to the next segment when full. |
228
+ | **ArrowUp / ArrowDown** | Step the value. Day and month wrap; year clamps. Empty seeds from today. |
229
+ | **ArrowLeft / ArrowRight** | Move to the previous / next segment in the same endpoint (no wrap). |
230
+ | **Home / End** | Jump to the segment minimum / maximum. |
231
+ | **Backspace** | Delete the last entered digit; the range becomes `null` when the last digit is removed. |
232
+ | **Delete** | Clear the whole segment (the range becomes `null` until refilled). |
232
233
 
233
234
  The day clamps to the current month's length (e.g. 31 → 28 in February), and each composed endpoint is clamped into `[minDate, maxDate]`.
234
235
 
package/drawer/README.md CHANGED
@@ -205,6 +205,20 @@ this.#drawers.open(ConfirmDrawer, {
205
205
 
206
206
  `activeSnapPointChange` fires with the landed snap on the mount-time default and every drag release — the read-back the declarative API exposes through `[(activeSnapPoint)]`. All three subscriptions are released automatically when the drawer closes.
207
207
 
208
+ **Driving the active snap point.** `ForDrawerRef.setActiveSnapPoint(snap)` moves a snap-point drawer to a new snap after open — the programmatic equivalent of _writing_ `[(activeSnapPoint)]` on the declarative `[forDrawer]`. `ref.activeSnapPoint()` is the matching reactive read (it also reflects the drawer's own internal transitions — the mount-time default and every drag release):
209
+
210
+ ```ts
211
+ const ref = this.#drawers.open(ConfirmDrawer, {
212
+ data,
213
+ snapPoints: ['148px', '50%', 1],
214
+ defaultSnapPoint: '148px',
215
+ });
216
+ ref.setActiveSnapPoint('50%'); // slide to the mid snap
217
+ ref.activeSnapPoint(); // => '50%'
218
+ ```
219
+
220
+ Like the declarative model it does not validate the argument against `snapPoints` (the drag engine tolerates a non-member), it is a no-op once the drawer has closed, and it is meaningful only when `snapPoints` are configured. The surface movement is the consumer's CSS keyed off the reflected `data-active-snap-point` attribute (see [Positioning the snaps](#positioning-the-snaps-css-contract)); `setActiveSnapPoint` only sets the state. As with `[(activeSnapPoint)]`, driving the snap this way does **not** re-fire the `activeSnapPointChange` callback (that fires only on the drawer's own internal transitions).
221
+
208
222
  ### Per-channel dismissal (Escape-only drawers)
209
223
 
210
224
  `dismissible` is **not** all-or-nothing. The four dismiss channels — Escape, pointer-down-outside, focus-outside, and the composite outside-interaction — are independently vetoable on both APIs, so you can keep some live and suppress others (e.g. a non-modal floater that closes on Escape but stays put on an outside click). Programmatically the channels are callbacks on the open config, mirroring the `autoFocusOn*` shape:
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { computed, InjectionToken, inject, signal, Injector, model, input, booleanAttribute, numberAttribute, linkedSignal, effect, afterNextRender, Directive, ElementRef, Injectable } from '@angular/core';
3
- import { compareDateOf, clampToBounds, Collection, createDefaults, IdGenerator, LiveAnnouncer, injectDateAdapter, injectTextDirection, adoptHostId, registerHandle, FOR_DATE_ADAPTER } from 'forty-cdk/core';
3
+ import { compareDateOf, clampToBounds, Collection, createDefaults, IdGenerator, LiveAnnouncer, injectDateAdapter, injectTextDirection, adoptHostId, registerHandle, createFormatterCache, FOR_DATE_ADAPTER } from 'forty-cdk/core';
4
4
  export { FOR_DATE_ADAPTER, assertTimeCapable, injectDateAdapter } from 'forty-cdk/core';
5
5
 
6
6
  /**
@@ -1963,16 +1963,7 @@ function clampDay(year, month, day, hours = 0, minutes = 0, seconds = 0, millise
1963
1963
  * calendar today; non-Gregorian calendar systems are deferred to #354.
1964
1964
  */
1965
1965
  class NativeDateAdapter {
1966
- #formatters = new Map();
1967
- #formatter(locale, options) {
1968
- const key = `${locale ?? ''}${JSON.stringify(options)}`;
1969
- let formatter = this.#formatters.get(key);
1970
- if (formatter === undefined) {
1971
- formatter = new Intl.DateTimeFormat(locale, options);
1972
- this.#formatters.set(key, formatter);
1973
- }
1974
- return formatter;
1975
- }
1966
+ #formatter = createFormatterCache();
1976
1967
  /**
1977
1968
  * Today at local midnight in the runtime time zone. Subject to the
1978
1969
  * SSR/hydration caveat on {@link DateAdapter.today}.