forty-cdk 0.40.0 → 0.41.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 (79) hide show
  1. package/README.md +1 -1
  2. package/breadcrumbs/README.md +1 -1
  3. package/breakpoints/README.md +12 -2
  4. package/calendar/README.md +2 -2
  5. package/dialog/README.md +22 -9
  6. package/drawer/README.md +18 -9
  7. package/fesm2022/forty-cdk-calendar.mjs +14 -4
  8. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  9. package/fesm2022/forty-cdk-combobox.mjs +49 -40
  10. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  11. package/fesm2022/forty-cdk-context-menu.mjs +42 -38
  12. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
  13. package/fesm2022/forty-cdk-core-anchored.mjs +2076 -0
  14. package/fesm2022/forty-cdk-core-anchored.mjs.map +1 -0
  15. package/fesm2022/forty-cdk-core-overlay.mjs +145 -2202
  16. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  17. package/fesm2022/forty-cdk-core.mjs +60 -28
  18. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  19. package/fesm2022/forty-cdk-date-picker.mjs +80 -57
  20. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  21. package/fesm2022/forty-cdk-defaults.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-dialog.mjs +2 -1
  23. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-drawer.mjs +2 -1
  25. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  26. package/fesm2022/forty-cdk-dropdown-menu.mjs +48 -33
  27. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
  28. package/fesm2022/forty-cdk-hover-card.mjs +52 -35
  29. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
  30. package/fesm2022/forty-cdk-input.mjs +19 -13
  31. package/fesm2022/forty-cdk-input.mjs.map +1 -1
  32. package/fesm2022/forty-cdk-listbox.mjs +56 -43
  33. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  34. package/fesm2022/forty-cdk-menu.mjs +32 -6
  35. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  36. package/fesm2022/forty-cdk-menubar.mjs +13 -10
  37. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  38. package/fesm2022/forty-cdk-navigation-menu.mjs +8 -1
  39. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  40. package/fesm2022/forty-cdk-popover.mjs +74 -34
  41. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-select.mjs +50 -28
  43. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  44. package/fesm2022/forty-cdk-shared.mjs +1 -1
  45. package/fesm2022/forty-cdk-table-virtualization.mjs +8 -8
  46. package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
  47. package/fesm2022/forty-cdk-time-picker.mjs +53 -23
  48. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  49. package/fesm2022/forty-cdk-tooltip.mjs +61 -38
  50. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
  51. package/fesm2022/forty-cdk-virtualization.mjs +14 -12
  52. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  53. package/hover-card/README.md +0 -1
  54. package/listbox/README.md +3 -3
  55. package/package.json +5 -1
  56. package/shared/README.md +5 -5
  57. package/table/README.md +8 -8
  58. package/table-virtualization/README.md +1 -1
  59. package/types/forty-cdk-calendar.d.ts +26 -9
  60. package/types/forty-cdk-combobox.d.ts +12 -13
  61. package/types/forty-cdk-context-menu.d.ts +11 -13
  62. package/types/forty-cdk-core-anchored.d.ts +2855 -0
  63. package/types/forty-cdk-core-overlay.d.ts +222 -2984
  64. package/types/forty-cdk-core.d.ts +22 -8
  65. package/types/forty-cdk-date-picker.d.ts +29 -20
  66. package/types/forty-cdk-defaults.d.ts +2 -1
  67. package/types/forty-cdk-dialog.d.ts +51 -15
  68. package/types/forty-cdk-drawer.d.ts +51 -15
  69. package/types/forty-cdk-dropdown-menu.d.ts +3 -2
  70. package/types/forty-cdk-hover-card.d.ts +12 -9
  71. package/types/forty-cdk-listbox.d.ts +15 -14
  72. package/types/forty-cdk-menu.d.ts +5 -3
  73. package/types/forty-cdk-menubar.d.ts +2 -3
  74. package/types/forty-cdk-popover.d.ts +26 -13
  75. package/types/forty-cdk-select.d.ts +10 -5
  76. package/types/forty-cdk-shared.d.ts +1 -1
  77. package/types/forty-cdk-table-virtualization.d.ts +1 -1
  78. package/types/forty-cdk-time-picker.d.ts +8 -3
  79. package/types/forty-cdk-tooltip.d.ts +25 -15
package/README.md CHANGED
@@ -54,7 +54,7 @@ Two packages are regular dependencies, installed automatically and never declare
54
54
  | [`forty-cdk/id-salt`](id-salt) | `FOR_ID_SALT` and `provideForIdSalt`, the per-app salt for the ids the primitives generate when several forty-cdk apps share a page. `forty-cdk/shared` re-exports them, but a root provider imported from there loads the code every primitive shares at startup; imported from here, it does not. |
55
55
  | [`forty-cdk/testing`](testing) | Spec helpers: keyboard and mouse events dispatched the way a browser dispatches them, stubs for the browser APIs jsdom lacks, and dialog and drawer refs for content mounted outside its manager. |
56
56
 
57
- `forty-cdk/core` and `forty-cdk/core-overlay` resolve too, but neither is **public**: together they hold the engines and DI singletons the library refactors freely, and they exist so every primitive resolves that shared implementation to one compiled module. They are two rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the positioning engine (`@floating-ui/dom` and the overlay shells) in its own module means a lazy route that renders no overlay does not load it. Measured on a seven-lazy-route app, that is **41.7 kB raw / 12.1 kB transfer** a non-overlay route no longer pays. If a symbol you need is not exported by the seven specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
57
+ `forty-cdk/core`, `forty-cdk/core-overlay` and `forty-cdk/core-anchored` resolve too, but none is **public**: together they hold the engines and DI singletons the library refactors freely, and they exist so every primitive resolves that shared implementation to one compiled module. They are three rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the overlay machinery in modules of its own means a lazy route that renders no overlay does not load it, and keeping the positioning engine (`@floating-ui/dom` and the anchored overlay shell) apart from the modal shell means an app that opens a dialog from its initial bundle does not load the engine with it. Measured on a seven-lazy-route app, the first split is **41.7 kB raw / 12.1 kB transfer** a non-overlay route no longer pays. If a symbol you need is not exported by the seven specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from any of them.
58
58
 
59
59
  ## Errors
60
60
 
@@ -84,7 +84,7 @@ export class BreadcrumbsDefaultExample {
84
84
 
85
85
  ### Collapsing a long trail
86
86
 
87
- The primitive renders whatever items you give it, so collapsing a deep path is a consumer decision. Here the middle is folded into an expandable ellipsis button that reveals the hidden crumbs. The trail stays a single accessible navigation landmark either way.
87
+ The primitive renders whatever items you give it, so collapsing a deep path is a consumer decision. Here the middle is folded into an expandable ellipsis button that reveals the hidden crumbs. Expanding removes the button, so focus moves to the first crumb it revealed instead of falling back to the page. The trail stays a single accessible navigation landmark either way.
88
88
 
89
89
  ## API
90
90
 
@@ -38,12 +38,14 @@ Resize the browser window and watch the matched breakpoint change: the breakpoin
38
38
  ```ts
39
39
  import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
40
40
  import { forBreakpointsTailwind, injectBreakpoints } from 'forty-cdk/breakpoints';
41
+ import { ForVisuallyHidden } from 'forty-cdk/visually-hidden';
41
42
 
42
43
  type TailwindName = keyof typeof forBreakpointsTailwind;
43
44
 
44
45
  @Component({
45
46
  selector: 'app-breakpoints-active-example',
46
47
  changeDetection: ChangeDetectionStrategy.OnPush,
48
+ imports: [ForVisuallyHidden],
47
49
  template: `
48
50
  <div class="bp-demo">
49
51
  <div class="bp-readout">
@@ -58,8 +60,16 @@ type TailwindName = keyof typeof forBreakpointsTailwind;
58
60
  <span class="bp-name">{{ row.name }}</span>
59
61
  <span class="bp-min">≥ {{ row.min }}px</span>
60
62
  <span class="bp-flags">
61
- <span class="bp-flag" [class.bp-flag--on]="row.up()">up</span>
62
- <span class="bp-flag" [class.bp-flag--on]="row.only()">only</span>
63
+ <span class="bp-flag" [class.bp-flag--on]="row.up()">
64
+ <span aria-hidden="true">{{ row.up() ? '✓' : '✕' }}</span>
65
+ up
66
+ <span forVisuallyHidden>{{ row.up() ? 'matches' : 'does not match' }}</span>
67
+ </span>
68
+ <span class="bp-flag" [class.bp-flag--on]="row.only()">
69
+ <span aria-hidden="true">{{ row.only() ? '✓' : '✕' }}</span>
70
+ only
71
+ <span forVisuallyHidden>{{ row.only() ? 'matches' : 'does not match' }}</span>
72
+ </span>
63
73
  </span>
64
74
  </li>
65
75
  }
@@ -393,7 +393,7 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
393
393
 
394
394
  The default window is **anchored to the current year** (not the visible year), so navigating far away never drops the current year off the list. Out-of-`[min, max]` entries have `disabled: true`, matching the `CalendarYearOption` shape. Both directives set the native `disabled` attribute on the `<select>` itself when the calendar is disabled.
395
395
 
396
- **Keep `[forCalendarHeading]` in the DOM.** The grid's `aria-labelledby` points at the heading's id. When dropdowns replace the visible heading, keep a visually-hidden `[forCalendarHeading]` so the grid stays named. Removing the heading entirely leaves `aria-labelledby` pointing at a missing element.
396
+ **Keep `[forCalendarHeading]` in the DOM.** The grid's `aria-labelledby` points at the heading's id while a heading is mounted. When dropdowns replace the visible heading, keep a visually-hidden `[forCalendarHeading]` so the grid stays named. Without a heading the grids emit no `aria-labelledby` at all, so they are unnamed unless you name them: a static `aria-labelledby` pointing at your own title, or an `aria-label`, written on each grid.
397
397
 
398
398
  **Name each `<select>` yourself.** Neither directive names the element it sits on, and the heading names the grid, not the dropdowns. Without a name a screen reader announces only the value each one holds, so give each an `aria-label` as the snippet above does, or a `<label for>`.
399
399
 
@@ -582,7 +582,7 @@ Focus that crosses a month boundary re-pages the visible grid and keeps the focu
582
582
  Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
583
583
 
584
584
  - **`role="grid"`** on the table, `columnheader` weekday headers and `gridcell` days follow the APG Date Picker Dialog technique over a real `<table>`.
585
- - **`aria-labelledby`** wires the grid to the heading so it names the visible period. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
585
+ - **`aria-labelledby`** wires the grid to the heading so it names the visible period. It is emitted only while a `[forCalendarHeading]` is mounted, and a static `aria-labelledby` you write on the grid replaces it. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
586
586
  - **`aria-label`** on every cell carries the full localized date (e.g. `"Monday, June 15, 2026"`) so screen readers announce the whole date, not the bare day number that stays the cell's visible content. Outside-month padding days are suffixed (`" (outside month)"`) so they are distinguishable. Override the format via `ForCalendar`'s `dateLabel` input.
587
587
  - **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
588
588
  - **`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.
package/dialog/README.md CHANGED
@@ -221,7 +221,19 @@ The auto-focus pair is bound as **function references** (input callbacks), not a
221
221
 
222
222
  ```ts
223
223
  import { Component, inject } from '@angular/core';
224
- import { ForDialogManager, ForDialogRef, injectDialogData } from 'forty-cdk/dialog';
224
+ import {
225
+ FOR_DIALOG_CONTRACT,
226
+ type ForDialogContract,
227
+ ForDialogManager,
228
+ ForDialogRef,
229
+ injectDialogData,
230
+ } from 'forty-cdk/dialog';
231
+
232
+ interface ConfirmData {
233
+ message: string;
234
+ }
235
+
236
+ type ConfirmResult = 'confirm' | 'cancel';
225
237
 
226
238
  @Component({
227
239
  template: `
@@ -231,8 +243,9 @@ import { ForDialogManager, ForDialogRef, injectDialogData } from 'forty-cdk/dial
231
243
  `,
232
244
  })
233
245
  class ConfirmDialog {
234
- readonly data = injectDialogData<{ message: string }>();
235
- readonly ref = inject(ForDialogRef) as ForDialogRef<'confirm' | 'cancel'>;
246
+ declare readonly [FOR_DIALOG_CONTRACT]: ForDialogContract<ConfirmData, ConfirmResult>;
247
+ readonly data = injectDialogData<ConfirmDialog>();
248
+ readonly ref = inject(ForDialogRef<ConfirmResult>);
236
249
  }
237
250
 
238
251
  @Component({
@@ -243,10 +256,7 @@ export class DemoHost {
243
256
  readonly dialogs = inject(ForDialogManager);
244
257
 
245
258
  async askToDelete() {
246
- const ref = this.dialogs.open<ConfirmDialog, 'confirm' | 'cancel', { message: string }>(
247
- ConfirmDialog,
248
- { data: { message: 'Are you sure?' } },
249
- );
259
+ const ref = this.dialogs.open(ConfirmDialog, { data: { message: 'Are you sure?' } });
250
260
  const { result } = await ref.closed; // result: 'confirm' | 'cancel' | undefined
251
261
  if (result === 'confirm') {
252
262
  /* ... */
@@ -255,7 +265,9 @@ export class DemoHost {
255
265
  }
256
266
  ```
257
267
 
258
- `injectDialogData<T>()` is typed `T | null`: the manager provides `null` when `open()` is called without `data`, so guard (`data?.message`) before dereferencing the payload.
268
+ **Typing the payload and the result.** The `[FOR_DIALOG_CONTRACT]` member states, once, the data a dialog component reads and the result it closes with. Declare it with `declare` and no initializer, and keep it public. `open()` then infers both from the component: a `data` of any other shape fails to compile, and the returned ref is a `ForDialogRef<ConfirmResult>`. `injectDialogData<ConfirmDialog>()` reads the payload at the type the contract names, so the component and every caller share one definition. A component without the member keeps an unchecked `data` and an `unknown` result, and `injectDialogData<T>()` with a plain type still types the payload as `T`. Explicit type arguments (`open<ConfirmDialog, ConfirmResult>`) override what the contract supplies.
269
+
270
+ `injectDialogData()` returns `null` too: the manager provides `null` when `open()` is called without `data`, so guard (`data?.message`) before dereferencing the payload.
259
271
 
260
272
  **Styling the programmatic overlay root.** Declaratively you write the surface yourself (`<div forDialog class="my-dialog">`), so the class lands on the same element that carries `data-state` / `role`. The manager creates that host for you and it is class-less, so pass `class` / `classList` to style it.
261
273
 
@@ -320,7 +332,8 @@ class ScopedHost {
320
332
  | `ForDialogManager` | Injectable. `open(component, config?)` returns a `ForDialogRef<R>`. `closeAll(result?)` closes every open dialog, topmost first, with reason `'programmatic'`; exit animations still play and focus ends where the bottom dialog returns it. `openCount` is a `Signal<number>` of the dialogs still open, counting a closed one until its exit animation finishes. |
321
333
  | `ForDialogRef<R>` | `close(result?)`, `closed: Promise<{ reason: ForDialogCloseReason; result: R \| undefined }>`, `result: Signal<R \| undefined>`, `isClosed: Signal<boolean>`. |
322
334
  | `FOR_DIALOG_DATA` | Token for the `data` payload. Inject in the opened component. |
323
- | `injectDialogData<T>()` | Typed accessor for `FOR_DIALOG_DATA`. Returns `T \| null` (`null` when `open()` got no `data`). |
335
+ | `injectDialogData<T>()` | Typed accessor for `FOR_DIALOG_DATA`. Returns `T \| null` (`null` when `open()` got no `data`), or the data type the contract names when `T` is a component declaring `FOR_DIALOG_CONTRACT`. |
336
+ | `FOR_DIALOG_CONTRACT` | Key of the member a dialog component declares as `ForDialogContract<D, R>` so `open()` checks `data` against `D` and returns a `ForDialogRef<R>`. `ForDialogDataOf<C>` / `ForDialogResultOf<C>` read the two types off a component. |
324
337
 
325
338
  ### `ForDialogOpenConfig`
326
339
 
package/drawer/README.md CHANGED
@@ -100,9 +100,17 @@ import {
100
100
  ForDrawerManager,
101
101
  ForDrawerRef,
102
102
  ForDrawerTitle,
103
+ FOR_DRAWER_CONTRACT,
104
+ type ForDrawerContract,
103
105
  injectDrawerData,
104
106
  } from 'forty-cdk/drawer';
105
107
 
108
+ interface ConfirmData {
109
+ message: string;
110
+ }
111
+
112
+ type ConfirmResult = 'confirm' | 'cancel';
113
+
106
114
  @Component({
107
115
  imports: [
108
116
  ForDrawerBackdrop,
@@ -126,8 +134,9 @@ import {
126
134
  `,
127
135
  })
128
136
  class ConfirmDrawer {
129
- readonly data = injectDrawerData<{ message: string }>();
130
- readonly ref = inject(ForDrawerRef) as ForDrawerRef<'confirm' | 'cancel'>;
137
+ declare readonly [FOR_DRAWER_CONTRACT]: ForDrawerContract<ConfirmData, ConfirmResult>;
138
+ readonly data = injectDrawerData<ConfirmDrawer>();
139
+ readonly ref = inject(ForDrawerRef<ConfirmResult>);
131
140
  }
132
141
 
133
142
  @Component({
@@ -138,7 +147,7 @@ class DemoHost {
138
147
  readonly #drawers = inject(ForDrawerManager);
139
148
 
140
149
  async askToDelete(): Promise<void> {
141
- const ref = this.#drawers.open<ConfirmDrawer, 'confirm' | 'cancel'>(ConfirmDrawer, {
150
+ const ref = this.#drawers.open(ConfirmDrawer, {
142
151
  data: { message: 'This action cannot be undone.' },
143
152
  side: 'bottom',
144
153
  snapPoints: ['148px', 1],
@@ -151,7 +160,7 @@ class DemoHost {
151
160
  }
152
161
  ```
153
162
 
154
- `injectDrawerData<T>()` is typed `T | null`: the manager provides `null` when `open()` is called without `data`, so guard (`data?.message`) before dereferencing the payload. `await ref.closed` resolves `{ reason, result }`, whose `reason` (a `ForDrawerCloseReason`) tells apart an imperative `close()` (`'programmatic'`) from Escape / backdrop / outside / swipe / close-button dismissals.
163
+ The `[FOR_DRAWER_CONTRACT]` member states, once, the data a drawer component reads and the result it closes with: `open()` infers both from the component, so a `data` of any other shape fails to compile and the ref is a `ForDrawerRef<ConfirmResult>`, and `injectDrawerData<ConfirmDrawer>()` reads the payload at that type. Declare it with `declare`, no initializer, and keep it public. Without the member `data` is unchecked, the result is `unknown`, and `injectDrawerData<T>()` with a plain type still types the payload as `T`; explicit type arguments override the contract. `injectDrawerData()` returns `null` too, because the manager provides `null` when `open()` is called without `data`, so guard (`data?.message`) before dereferencing the payload. `await ref.closed` resolves `{ reason, result }`, whose `reason` (a `ForDrawerCloseReason`) tells apart an imperative `close()` (`'programmatic'`) from Escape / backdrop / outside / swipe / close-button dismissals.
155
164
 
156
165
  Drawers opened by the manager share the LIFO dismissible layer with declarative ones, so Escape closes the topmost drawer first whichever API opened it. Nesting is resolved from where a drawer is declared, not from the order drawers open in: a declarative `[forDrawer]` inside a programmatic drawer's component is its child (`data-depth="1"`, and the programmatic parent reflects `data-state-nested`), but a drawer the manager opens over a declarative one is a root, so it reflects `data-depth="0"` and its declarative neighbour gets no `data-state-nested`.
157
166
 
@@ -262,7 +271,7 @@ Declaratively the same recipe is the four vetoable outputs on `[forDrawer]`: `(i
262
271
 
263
272
  ## Examples
264
273
 
265
- Open the drawer, drag it by its edge and let go past the threshold. `data-state` drives the transition, and `data-dragging` is set for the gesture itself.
274
+ Open the drawer, drag it by its edge and let go past the threshold. `animate.enter` / `animate.leave` drive the enter and exit, and `data-dragging` marks the gesture, so a release short of the threshold springs back through the surface's `translate` transition.
266
275
 
267
276
  ```ts
268
277
  import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
@@ -454,13 +463,13 @@ Pass them in **strictly increasing** order (closest-to-edge first); in dev mode
454
463
  <div
455
464
  forDrawer
456
465
  class="sheet"
457
- [snapPoints]="['148px', '50%', 1]"
466
+ [snapPoints]="['25%', '50%', 1]"
458
467
  [(activeSnapPoint)]="snap"
459
468
  [fadeFromIndex]="1"
460
469
  ></div>
461
470
  ```
462
471
 
463
- The three snaps read peek → mid → full; `snap` is the current one, written on every release that changes the snap; the backdrop fades once the active snap reaches the second index.
472
+ The three snaps read peek → mid → full; `snap` is the current one, written on every release that changes the snap; the backdrop fades once the active snap reaches the second index. All three are fractions of the sheet, so their order holds at any viewport height. A `'148px'` peek beside `'50%'` would only stay below the mid snap while the sheet is taller than 296px, so the `80vh` sheet below would throw `FORCDK-DRAWER-010` on a viewport shorter than 370px.
464
473
 
465
474
  The `model<>()` change emitter (`(activeSnapPointChange)`) fires on internal transitions (the mount-time default and every release that changes the snap), and stays silent on consumer writes through `[(activeSnapPoint)]`.
466
475
 
@@ -486,8 +495,8 @@ For a seamless release, transition **both** `translate` and your snap-position p
486
495
  .sheet[data-active-snap-point][data-dragging] {
487
496
  transition: none; /* the drag follows the pointer 1:1 */
488
497
  }
489
- .sheet[data-active-snap-point='148px'] {
490
- bottom: calc(148px - 80vh);
498
+ .sheet[data-active-snap-point='25%'] {
499
+ bottom: -60vh;
491
500
  }
492
501
  .sheet[data-active-snap-point='50%'] {
493
502
  bottom: -40vh;
@@ -1,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Directive, ElementRef, InjectionToken, Injector, afterNextRender, booleanAttribute, computed, effect, inject, input, linkedSignal, model, numberAttribute, signal } from "@angular/core";
2
+ import { DestroyRef, Directive, ElementRef, InjectionToken, Injector, afterNextRender, booleanAttribute, computed, effect, inject, input, linkedSignal, model, numberAttribute, signal } from "@angular/core";
3
3
  import { Collection, FOR_PICKER_SOURCE_HOST, IdGenerator, LiveAnnouncer, adoptHostId, assertInputBound, assertRootContext, clampToBounds, compareDateOf, hostAriaLabel, hostButtonType, hostLabelledBy, injectDateAdapter, injectTextDirection, isUnset, orphanContextError, registerHandle, unsetInput } from "forty-cdk/core";
4
4
  import { FOR_CALENDAR_DEFAULTS, FOR_CALENDAR_DEFAULTS as FOR_CALENDAR_DEFAULTS$1, provideForCalendarDefaults } from "forty-cdk/defaults";
5
5
  import { NativeDateAdapter, provideNativeDateAdapter } from "forty-cdk/date-adapter";
@@ -766,6 +766,14 @@ var ForCalendar = class ForCalendar {
766
766
  }, ...ngDevMode ? [{ debugName: "visibleMonth" }] : /* istanbul ignore next */ []);
767
767
  #headingId = signal(this.#idGen.next("for-calendar-heading"), ...ngDevMode ? [{ debugName: "#headingId" }] : /* istanbul ignore next */ []);
768
768
  headingId = this.#headingId.asReadonly();
769
+ #headingCount = signal(0, ...ngDevMode ? [{ debugName: "#headingCount" }] : /* istanbul ignore next */ []);
770
+ hasHeading = computed(() => this.#headingCount() > 0, ...ngDevMode ? [{ debugName: "hasHeading" }] : /* istanbul ignore next */ []);
771
+ registerHeading() {
772
+ this.#headingCount.update((count) => count + 1);
773
+ }
774
+ unregisterHeading() {
775
+ this.#headingCount.update((count) => count - 1);
776
+ }
769
777
  adoptHeadingId(el) {
770
778
  adoptHostId(el, this.#headingId);
771
779
  }
@@ -1251,7 +1259,7 @@ i0.ɵɵngDeclareClassMetadata({
1251
1259
  });
1252
1260
  var ForCalendarGrid = class ForCalendarGrid {
1253
1261
  ctx = injectCalendarContext("ForCalendarGrid");
1254
- labelledBy = hostLabelledBy(() => this.ctx.headingId());
1262
+ labelledBy = hostLabelledBy(() => this.ctx.hasHeading() ? this.ctx.headingId() : null);
1255
1263
  weekDays = this.ctx.weekDays;
1256
1264
  weeks = this.ctx.weeks;
1257
1265
  static ɵfac = i0.ɵɵngDeclareFactory({
@@ -1471,6 +1479,8 @@ var ForCalendarHeading = class ForCalendarHeading {
1471
1479
  label = this.ctx.viewTriggerLabel;
1472
1480
  constructor() {
1473
1481
  this.ctx.adoptHeadingId(inject(ElementRef).nativeElement);
1482
+ this.ctx.registerHeading();
1483
+ inject(DestroyRef).onDestroy(() => this.ctx.unregisterHeading());
1474
1484
  }
1475
1485
  static ɵfac = i0.ɵɵngDeclareFactory({
1476
1486
  minVersion: "12.0.0",
@@ -1652,7 +1662,7 @@ i0.ɵɵngDeclareClassMetadata({
1652
1662
  });
1653
1663
  var ForCalendarMonthGrid = class ForCalendarMonthGrid {
1654
1664
  ctx = injectCalendarContext("ForCalendarMonthGrid");
1655
- labelledBy = hostLabelledBy(() => this.ctx.headingId());
1665
+ labelledBy = hostLabelledBy(() => this.ctx.hasHeading() ? this.ctx.headingId() : null);
1656
1666
  rows = this.ctx.monthRows;
1657
1667
  static ɵfac = i0.ɵɵngDeclareFactory({
1658
1668
  minVersion: "12.0.0",
@@ -1804,7 +1814,7 @@ i0.ɵɵngDeclareClassMetadata({
1804
1814
  });
1805
1815
  var ForCalendarYearGrid = class ForCalendarYearGrid {
1806
1816
  ctx = injectCalendarContext("ForCalendarYearGrid");
1807
- labelledBy = hostLabelledBy(() => this.ctx.headingId());
1817
+ labelledBy = hostLabelledBy(() => this.ctx.hasHeading() ? this.ctx.headingId() : null);
1808
1818
  rows = this.ctx.yearRows;
1809
1819
  static ɵfac = i0.ɵɵngDeclareFactory({
1810
1820
  minVersion: "12.0.0",