forty-cdk 0.29.0 → 0.31.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 (175) hide show
  1. package/README.md +11 -8
  2. package/breadcrumbs/README.md +1 -1
  3. package/breakpoints/README.md +1 -1
  4. package/calendar/README.md +1 -1
  5. package/combobox/README.md +3 -2
  6. package/date-adapter/README.md +48 -0
  7. package/date-field/README.md +1 -1
  8. package/date-picker/README.md +6 -3
  9. package/defaults/README.md +100 -0
  10. package/dialog/README.md +2 -2
  11. package/drag-drop/README.md +6 -1
  12. package/drawer/README.md +2 -2
  13. package/fesm2022/forty-cdk-avatar.mjs +4 -26
  14. package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
  15. package/fesm2022/forty-cdk-breadcrumbs.mjs +4 -26
  16. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -1
  17. package/fesm2022/forty-cdk-breakpoints.mjs +5 -52
  18. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
  19. package/fesm2022/forty-cdk-calendar.mjs +6 -167
  20. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  21. package/fesm2022/forty-cdk-carousel.mjs +7 -46
  22. package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
  23. package/fesm2022/forty-cdk-combobox.mjs +30 -60
  24. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  25. package/fesm2022/forty-cdk-context-menu.mjs +4 -30
  26. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
  27. package/fesm2022/forty-cdk-core-overlay.mjs +70 -112
  28. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  29. package/fesm2022/forty-cdk-core.mjs +584 -357
  30. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  31. package/fesm2022/forty-cdk-date-adapter.mjs +188 -0
  32. package/fesm2022/forty-cdk-date-adapter.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-date-field.mjs +4 -87
  34. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  35. package/fesm2022/forty-cdk-date-picker.mjs +16 -70
  36. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  37. package/fesm2022/forty-cdk-defaults.mjs +1506 -0
  38. package/fesm2022/forty-cdk-defaults.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-dialog.mjs +15 -51
  40. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  41. package/fesm2022/forty-cdk-drag-drop.mjs +133 -94
  42. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  43. package/fesm2022/forty-cdk-drawer.mjs +15 -65
  44. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  45. package/fesm2022/forty-cdk-dropdown-menu.mjs +4 -30
  46. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
  47. package/fesm2022/forty-cdk-field.mjs +3 -8
  48. package/fesm2022/forty-cdk-field.mjs.map +1 -1
  49. package/fesm2022/forty-cdk-file-upload.mjs +14 -3
  50. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
  51. package/fesm2022/forty-cdk-hover-card.mjs +6 -56
  52. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
  53. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  54. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  55. package/fesm2022/forty-cdk-listbox.mjs +90 -62
  56. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  57. package/fesm2022/forty-cdk-menu.mjs +6 -39
  58. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  59. package/fesm2022/forty-cdk-menubar.mjs +22 -40
  60. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  61. package/fesm2022/forty-cdk-navigation-menu.mjs +6 -30
  62. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  63. package/fesm2022/forty-cdk-number-input.mjs +4 -26
  64. package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
  65. package/fesm2022/forty-cdk-pagination.mjs +4 -27
  66. package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
  67. package/fesm2022/forty-cdk-popover.mjs +4 -30
  68. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-progress.mjs +4 -27
  70. package/fesm2022/forty-cdk-progress.mjs.map +1 -1
  71. package/fesm2022/forty-cdk-radio-group.mjs +7 -35
  72. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
  73. package/fesm2022/forty-cdk-scroll-area.mjs +4 -29
  74. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
  75. package/fesm2022/forty-cdk-search.mjs +5 -27
  76. package/fesm2022/forty-cdk-search.mjs.map +1 -1
  77. package/fesm2022/forty-cdk-select.mjs +66 -50
  78. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  79. package/fesm2022/forty-cdk-shared.mjs +2 -1
  80. package/fesm2022/forty-cdk-shared.mjs.map +1 -1
  81. package/fesm2022/forty-cdk-slider.mjs +4 -26
  82. package/fesm2022/forty-cdk-slider.mjs.map +1 -1
  83. package/fesm2022/forty-cdk-stepper.mjs +7 -38
  84. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  85. package/fesm2022/forty-cdk-table-virtualization.mjs +6 -0
  86. package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
  87. package/fesm2022/forty-cdk-table.mjs +288 -137
  88. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  89. package/fesm2022/forty-cdk-tabs.mjs +7 -33
  90. package/fesm2022/forty-cdk-tabs.mjs.map +1 -1
  91. package/fesm2022/forty-cdk-testing.mjs +383 -0
  92. package/fesm2022/forty-cdk-testing.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-time-field.mjs +4 -81
  94. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  95. package/fesm2022/forty-cdk-time-picker.mjs +26 -37
  96. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  97. package/fesm2022/forty-cdk-toast.mjs +5 -37
  98. package/fesm2022/forty-cdk-toast.mjs.map +1 -1
  99. package/fesm2022/forty-cdk-toggle.mjs +9 -37
  100. package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
  101. package/fesm2022/forty-cdk-toolbar.mjs +10 -44
  102. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
  103. package/fesm2022/forty-cdk-tooltip.mjs +6 -80
  104. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
  105. package/fesm2022/forty-cdk-tree.mjs +174 -134
  106. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  107. package/fesm2022/forty-cdk-virtual-reorder.mjs +136 -43
  108. package/fesm2022/forty-cdk-virtual-reorder.mjs.map +1 -1
  109. package/file-upload/README.md +20 -12
  110. package/hover-card/README.md +1 -1
  111. package/internationalized-date/README.md +1 -1
  112. package/listbox/README.md +20 -20
  113. package/menu/README.md +2 -2
  114. package/package.json +13 -1
  115. package/popover/README.md +1 -1
  116. package/select/README.md +6 -5
  117. package/shared/README.md +14 -14
  118. package/table/README.md +139 -36
  119. package/table-virtualization/README.md +24 -3
  120. package/testing/README.md +97 -0
  121. package/time-field/README.md +1 -1
  122. package/time-picker/README.md +20 -19
  123. package/toast/README.md +1 -1
  124. package/toolbar/README.md +1 -1
  125. package/tooltip/README.md +1 -1
  126. package/tree/README.md +55 -33
  127. package/types/forty-cdk-avatar.d.ts +4 -31
  128. package/types/forty-cdk-breadcrumbs.d.ts +2 -31
  129. package/types/forty-cdk-breakpoints.d.ts +5 -56
  130. package/types/forty-cdk-calendar.d.ts +7 -109
  131. package/types/forty-cdk-carousel.d.ts +6 -92
  132. package/types/forty-cdk-combobox.d.ts +32 -113
  133. package/types/forty-cdk-context-menu.d.ts +7 -58
  134. package/types/forty-cdk-core-overlay.d.ts +76 -104
  135. package/types/forty-cdk-core.d.ts +261 -316
  136. package/types/forty-cdk-date-adapter.d.ts +296 -0
  137. package/types/forty-cdk-date-field.d.ts +6 -130
  138. package/types/forty-cdk-date-picker.d.ts +18 -104
  139. package/types/forty-cdk-defaults.d.ts +2125 -0
  140. package/types/forty-cdk-dialog.d.ts +13 -76
  141. package/types/forty-cdk-drag-drop.d.ts +68 -77
  142. package/types/forty-cdk-drawer.d.ts +16 -134
  143. package/types/forty-cdk-dropdown-menu.d.ts +6 -56
  144. package/types/forty-cdk-field.d.ts +0 -1
  145. package/types/forty-cdk-file-upload.d.ts +30 -20
  146. package/types/forty-cdk-hover-card.d.ts +8 -80
  147. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  148. package/types/forty-cdk-listbox.d.ts +16 -56
  149. package/types/forty-cdk-menu.d.ts +5 -88
  150. package/types/forty-cdk-menubar.d.ts +10 -58
  151. package/types/forty-cdk-navigation-menu.d.ts +4 -33
  152. package/types/forty-cdk-number-input.d.ts +4 -29
  153. package/types/forty-cdk-pagination.d.ts +4 -27
  154. package/types/forty-cdk-popover.d.ts +7 -55
  155. package/types/forty-cdk-progress.d.ts +4 -38
  156. package/types/forty-cdk-radio-group.d.ts +4 -29
  157. package/types/forty-cdk-scroll-area.d.ts +5 -53
  158. package/types/forty-cdk-search.d.ts +5 -31
  159. package/types/forty-cdk-select.d.ts +26 -60
  160. package/types/forty-cdk-shared.d.ts +2 -1
  161. package/types/forty-cdk-slider.d.ts +4 -29
  162. package/types/forty-cdk-stepper.d.ts +5 -50
  163. package/types/forty-cdk-table-virtualization.d.ts +3 -1
  164. package/types/forty-cdk-table.d.ts +140 -22
  165. package/types/forty-cdk-tabs.d.ts +5 -37
  166. package/types/forty-cdk-testing.d.ts +200 -0
  167. package/types/forty-cdk-time-field.d.ts +6 -130
  168. package/types/forty-cdk-time-picker.d.ts +23 -58
  169. package/types/forty-cdk-toast.d.ts +6 -86
  170. package/types/forty-cdk-toggle.d.ts +4 -34
  171. package/types/forty-cdk-toolbar.d.ts +4 -29
  172. package/types/forty-cdk-tooltip.d.ts +8 -117
  173. package/types/forty-cdk-tree.d.ts +76 -72
  174. package/types/forty-cdk-virtual-reorder.d.ts +22 -4
  175. package/virtual-reorder/README.md +13 -10
package/README.md CHANGED
@@ -28,7 +28,7 @@ Optional — install only if you use the matching entry point / primitives:
28
28
  | Peer | Needed by |
29
29
  | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
30
  | `@angular/forms` `^22.0.1` | Form-control primitives (`Switch`, `Checkbox`, `RadioGroup`, `Listbox`, `Select`, `Slider`, `Combobox`, …). They implement `FormValueControl` / `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring. The contract is type-only, so the published bundle never references the package — consumers using only non-form primitives can skip it. |
31
- | `@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/shared` — install this peer only when you import that entry point. |
31
+ | `@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. |
32
32
 
33
33
  ### Regular dependencies
34
34
 
@@ -41,15 +41,18 @@ Two packages are regular dependencies, installed automatically and never declare
41
41
 
42
42
  ## Entry points
43
43
 
44
- **The package name itself exports nothing.** `import { … } from 'forty-cdk'` resolves to no symbol, and your editor will not auto-import anything under the bare package name — by design, so that every symbol has exactly one import path. There are three specifiers you do import from:
44
+ **The package name itself exports nothing.** `import { … } from 'forty-cdk'` resolves to no symbol, and your editor will not auto-import anything under the bare package name — by design, so that the bare name never becomes a second import path for a symbol. There are six specifiers you do import from:
45
45
 
46
- | Specifier | What it exports |
47
- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
- | `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below. |
49
- | [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once. Eight ship from their own primitive instead; that README names them. |
50
- | `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional. |
46
+ | Specifier | What it exports |
47
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below. |
49
+ | [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once. Eight ship from their own primitive instead; that README names them. |
50
+ | `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional. |
51
+ | [`forty-cdk/defaults`](defaults) | Every `provideFor<Primitive>Defaults` with its token and interface. Each primitive re-exports its own pair too, but a root provider imported from the primitive loads the primitive at startup; imported from here, it does not. |
52
+ | [`forty-cdk/date-adapter`](date-adapter) | The `DateAdapter` contract, the `FOR_DATE_ADAPTER` token and `provideNativeDateAdapter`. `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. |
53
+ | [`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. |
51
54
 
52
- `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 three specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
55
+ `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 six specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
53
56
 
54
57
  ## Errors
55
58
 
@@ -93,7 +93,7 @@ The primitive renders whatever items you give it, so collapsing a deep path is a
93
93
  <!-- snippet: fragment -->
94
94
 
95
95
  ```ts
96
- import { provideForBreadcrumbsDefaults } from 'forty-cdk/breadcrumbs';
96
+ import { provideForBreadcrumbsDefaults } from 'forty-cdk/defaults';
97
97
 
98
98
  bootstrapApplication(App, {
99
99
  providers: [provideForBreadcrumbsDefaults({ label: 'Ruta de navegación' })],
@@ -16,7 +16,7 @@ Configuring is optional: without a provider the Tailwind scale (`sm` 640, `md` 7
16
16
 
17
17
  ```ts
18
18
  import { ApplicationConfig } from '@angular/core';
19
- import { provideForBreakpointsDefaults } from 'forty-cdk/breakpoints';
19
+ import { provideForBreakpointsDefaults } from 'forty-cdk/defaults';
20
20
 
21
21
  export const appConfig: ApplicationConfig = {
22
22
  providers: [
@@ -535,7 +535,7 @@ Auto-disabled when the entire previous / next page would be outside `[min, max]`
535
535
  ## Scoped defaults
536
536
 
537
537
  ```ts
538
- import { provideForCalendarDefaults } from 'forty-cdk/calendar';
538
+ import { provideForCalendarDefaults } from 'forty-cdk/defaults';
539
539
 
540
540
  // app config or a component's providers — Monday-first weeks for this scope
541
541
  providers: [
@@ -220,7 +220,7 @@ Input tables are not yet tabulated for this primitive. See the feature sections
220
220
  | `[forComboboxChip]` | `data-disabled` | present / absent |
221
221
  | `[forComboboxSeparator]` | `data-orientation` | `horizontal` \| `vertical` |
222
222
 
223
- Focus stays on the `<input>` the whole time the listbox is open, so options never get `:focus`. `data-highlighted` is the canonical hook for styling the keyboard-active option.
223
+ Focus stays on the `<input>` the whole time the listbox is open, so options never get `:focus`. `data-highlighted` is the canonical hook for styling the keyboard-active option. A mouse press anywhere on the popup that lands on no focusable element (padding, a group label, an empty or status row) does not move focus either, so typing and the arrow keys keep working after it; the trade-off is that popup text cannot be selected with the mouse, as in a native combobox. A press on a focusable element inside the popup, such as a `[forComboboxAction]` or your own `<button>`, still focuses it.
224
224
 
225
225
  ## Mount/visibility convention
226
226
 
@@ -718,7 +718,7 @@ When `[totalCount]` is omitted, the directive falls back to `options().length` a
718
718
  `[forCombobox]` exposes a `dir: 'ltr' | 'rtl'` input (default `'ltr'`). It drives:
719
719
 
720
720
  - **Chip keyboard navigation**: ArrowLeft / ArrowRight roles swap so they follow the visual order of the chip cluster, not the DOM order. See _Chip keyboard_ above.
721
- - **Default popover placement**: `align` defaults to `'start'` in LTR and `'end'` in RTL so the listbox anchors to the visually-leading edge of the input (`side` defaults to `'bottom'` in both). A consumer-provided `[align]` is honoured as-is, with no automatic flip, so advanced layouts can pin an alignment regardless of writing direction. `provideForComboboxDefaults({ align })` pins it for a whole scope the same way; its default is `null`, which is what "follow the writing direction" is spelled as there. `side` is scope-defaultable through the same provider, with the plain `'bottom'` fallback. Writing direction does not enter into it.
721
+ - **Default popover placement**: `align` defaults to `'start'` and `side` to `'bottom'`. `start` and `end` resolve against the writing direction, so the default anchors the listbox to the visually-leading edge of the input in both LTR and RTL, and a consumer-provided `[align]` follows the direction the same way. `provideForComboboxDefaults({ align, side })` sets either for a whole scope.
722
722
 
723
723
  The native `<input>` handles caret movement and BiDi from the document's CSS `direction` already, so there's nothing extra to do for the typed text itself.
724
724
 
@@ -804,6 +804,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
804
804
  - `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 role-less so it can hold the input next to the list without an `aria-required-owned-elements` violation.
805
805
  - `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.
806
806
  - `[forComboboxTrigger]` (picker anatomy) is a real `<button>` reflecting `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls` (the popup surface, while open), and native `disabled` from the combobox's effective disabled. It is exempt from the popup's outside-pointer dismissal layer, like the input.
807
+ - `[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.
807
808
  - `[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.
808
809
  - In single mode, `aria-selected="true"` follows the activedescendant (the option Enter would activate). In multi mode it follows membership in `value()`, so every selected option carries `aria-selected="true"` simultaneously.
809
810
  - `data-state="checked" | "unchecked"` always reflects membership in `value()`, so consumers can paint a checkmark icon with pure CSS regardless of mode.
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Date adapter
3
+ group: utilities
4
+ archetype: [headless-utility]
5
+ ---
6
+
7
+ # Date adapter
8
+
9
+ The `DateAdapter` contract, the `FOR_DATE_ADAPTER` token that provides one, and the zero-dependency `NativeDateAdapter`, imported from `forty-cdk/date-adapter` so that providing an adapter at the application root does not load the date and time primitives, or the code they share, before bootstrap.
10
+
11
+ Every date and time primitive (Calendar, Date Field, Date Picker, Time Field, Time Picker) delegates its arithmetic and formatting to the adapter in scope, and none ships a default. Provide one where the [date adapters guide](../../../docs/date-adapters.md) says to, usually the application config:
12
+
13
+ ```ts
14
+ import { type ApplicationConfig } from '@angular/core';
15
+ import { provideNativeDateAdapter } from 'forty-cdk/date-adapter';
16
+
17
+ export const appConfig: ApplicationConfig = {
18
+ providers: [provideNativeDateAdapter()],
19
+ };
20
+ ```
21
+
22
+ `forty-cdk/shared` re-exports the same objects, and `forty-cdk/calendar` re-exports the native pair, so an import from either keeps compiling and binds the same token. Only the import from this entry point keeps the root provider cheap.
23
+
24
+ ## Why this exists
25
+
26
+ A bundler splits code into chunks one module at a time, and each forty-cdk entry point is published as a single module. A root `providers` array is in the static graph of your `main` bundle, so the module it imports from is loaded before the application bootstraps, on every route. This entry point imports nothing but `@angular/core`, so the code the primitives share stays in the lazy chunks of the routes that render them. `provideInternationalizedDateAdapter()` and `provideInternationalizedDateTimeAdapter()` read the token and the formatter cache from here too.
27
+
28
+ Measured on a production build of an app with four lazy routes (Tooltip, Calendar, Combobox, Menu) and `provideForTooltipDefaults`, `provideForCalendarDefaults`, `provideForComboboxDefaults` and `provideForMenuDefaults` from [`forty-cdk/defaults`](../defaults) at the root:
29
+
30
+ | Also at the root | `main` size (raw / gzip) | Shared chunks the lazy routes load |
31
+ | -------------------------------------------------------------------------- | ------------------------ | ---------------------------------- |
32
+ | No date adapter | 209,552 B / 63,580 B | two |
33
+ | A `NativeDateAdapter` subclass, imported from `forty-cdk/date-adapter` | 212,012 B / 64,263 B | two |
34
+ | A hand-written adapter on `FOR_DATE_ADAPTER` from `forty-cdk/date-adapter` | 209,723 B / 63,610 B | two |
35
+ | The same subclass, imported from `forty-cdk/shared` | 246,013 B / 74,209 B | none: they moved into `main` |
36
+
37
+ ## API
38
+
39
+ | Export | Kind | Contract |
40
+ | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | `DateAdapter<D>` | interface | The date-library seam every date and time primitive calls. Its members, and the Gregorian and locale limits, are in the [date adapters guide](../../../docs/date-adapters.md#writing-your-own-adapter). |
42
+ | `TimeCapableDateAdapter<D>` | type | A `DateAdapter<D>` that implements the optional `getHours` / `getMinutes` / `getSeconds` / `setTime`. The time primitives require one. |
43
+ | `FOR_DATE_ADAPTER` | token | Holds the active adapter. A date or time primitive with no adapter in scope throws `FORCDK-CORE-002`. |
44
+ | `NativeDateAdapter` | class | A time-capable adapter over the built-in `Date`. Subclass it and override `locale()` to format in the app's language. |
45
+ | `provideNativeDateAdapter()` | provider | Binds `NativeDateAdapter` to `FOR_DATE_ADAPTER`. |
46
+ | `createFormatterCache()` | function | Returns a function that hands back one `Intl.DateTimeFormat` per distinct `(locale, options)` pair, for an adapter of your own to format through. |
47
+
48
+ `injectDateAdapter` and `assertTimeCapable` are not here: they throw through the library's error helpers, which live with the primitives, and ship from [`forty-cdk/shared`](../shared).
@@ -205,7 +205,7 @@ On the AM/PM segment, `a` / `p` set the period and ArrowUp / ArrowDown toggle it
205
205
  ## Scoped defaults
206
206
 
207
207
  ```ts
208
- import { provideForDateFieldDefaults } from 'forty-cdk/date-field';
208
+ import { provideForDateFieldDefaults } from 'forty-cdk/defaults';
209
209
 
210
210
  // app config or a component's providers — localize segment labels, the
211
211
  // empty-segment announcement and the placeholders for every nested [forDateField].
@@ -207,7 +207,8 @@ Open the picker and click a first day: the trigger keeps its placeholder, becaus
207
207
  | `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured at `granularity="day"`, and at any granularity with `anatomy="field"`.<br>**Default:** `true` |
208
208
  | `anatomy` | `input<'trigger' \| 'field'>` | Which piece is the control. `'field'` makes a projected `[forDateField]` the control and the trigger a plain button; see [Field anatomy](#field-anatomy).<br>**Default:** `'trigger'` |
209
209
  | `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` (default) is a pure date picker; coarser-than-day off composes a time field.<br>**Default:** `'day'` |
210
- | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display (and typically the projected `[forTimeField]`).<br>**Default:** `null` → the scope's `hourCycle` (`provideForDatePickerDefaults`), then the locale |
210
+ | `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display; a projected time field takes `resolvedHourCycle()`.<br>**Default:** `null` → the scope's `hourCycle` (`provideForDatePickerDefaults`), then the locale |
211
+ | `resolvedHourCycle` | `Signal<12 \| 24 \| null>` | The effective cycle: `hourCycle`, then the scope's, or `null` for the locale. Bind it to a projected time field's `[hourCycle]`.<br>**Default:** — |
211
212
  | `modal` | `input<boolean>` | Trap focus + inert background + scroll lock (centered dialog) instead of an anchored popover.<br>**Default:** `false` |
212
213
  | `dismissible` | `input<boolean>` | Escape / outside-pointer dismiss the surface.<br>**Default:** `true` |
213
214
  | `returnFocus` | `input<boolean>` | Return focus to the trigger on close.<br>**Default:** `true` |
@@ -314,7 +315,7 @@ The content is a field boundary. Inside a [`[forField]`](../field/README.md#how-
314
315
  <div
315
316
  forTimeField
316
317
  [value]="picker.value()"
317
- [hourCycle]="picker.hourCycle()"
318
+ [hourCycle]="picker.resolvedHourCycle()"
318
319
  #field="forTimeField"
319
320
  >
320
321
  @for (seg of field.segments(); track seg.id) { @if (seg.isLiteral) {
@@ -328,7 +329,9 @@ The content is a field boundary. Inside a [`[forField]`](../field/README.md#how-
328
329
  </div>
329
330
  ```
330
331
 
331
- The value display (`[forDatePickerValue]`) automatically appends the time to its formatting when `granularity > 'day'` and you haven't set time fields in `formatOptions`.
332
+ The value display (`[forDatePickerValue]`) automatically appends a two-digit hour and minute to its formatting when `granularity > 'day'` and you haven't set time fields in `formatOptions`, so it matches the projected `[forTimeField]`. The resolved `hourCycle` applies to that default and to time fields you set yourself, unless `formatOptions` sets `hour12` or `hourCycle`.
333
+
334
+ Bind the time field's `[hourCycle]` to `picker.resolvedHourCycle()`, not `picker.hourCycle()`. The raw input is `null` when the cycle comes from `provideForDatePickerDefaults`, and the time field would then fall back to its own defaults and the locale, so an `en-US` browser would show `14:30` on the trigger and `2:30 PM` in the panel. The same applies to a projected `[forTimePicker]`.
332
335
 
333
336
  ## Field anatomy
334
337
 
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Defaults
3
+ group: utilities
4
+ archetype: [headless-utility]
5
+ ---
6
+
7
+ # Defaults
8
+
9
+ Every `provideFor<Primitive>Defaults` provider, its `FOR_<PRIMITIVE>_DEFAULTS` token and its `For<Primitive>Defaults` interface, imported from `forty-cdk/defaults` so that configuring a primitive at the application root does not load the primitive itself.
10
+
11
+ Each primitive's entry point still re-exports its own pair, so `import { provideForTooltipDefaults } from 'forty-cdk/tooltip'` keeps compiling. Both paths name the same token object, so a provider from one and a primitive reading the other share one scope.
12
+
13
+ ```ts
14
+ import { type ApplicationConfig } from '@angular/core';
15
+ import { provideForCalendarDefaults, provideForTooltipDefaults } from 'forty-cdk/defaults';
16
+
17
+ export const appConfig: ApplicationConfig = {
18
+ providers: [
19
+ provideForTooltipDefaults({ openDelay: 500 }),
20
+ provideForCalendarDefaults({ firstDayOfWeek: 1 }),
21
+ ],
22
+ };
23
+ ```
24
+
25
+ ## Why this exists
26
+
27
+ A bundler splits code into chunks one module at a time, and each forty-cdk entry point is published as a single module. A root `providers` array is in the static graph of your `main` bundle, so whatever module it imports is loaded before the application bootstraps, on every route, including the ones that never render that primitive.
28
+
29
+ Importing a provider from the primitive's own entry point therefore puts that primitive's directives in `main`. The re-export does not change it, because the bundler still has to load the module the re-export lives in. `forty-cdk/defaults` holds only the pairs and imports nothing but `@angular/core`, so the directives stay in the lazy chunks of the routes that render them.
30
+
31
+ Measured on a production build of an app with four lazy routes and these providers at the root, `provideForTooltipDefaults`, `provideForCalendarDefaults`, `provideForComboboxDefaults` and `provideForMenuDefaults`:
32
+
33
+ | Imported from | `main` holds | `main` size (raw / gzip) |
34
+ | ---------------------- | ----------------------------------------------------- | ------------------------ |
35
+ | Each primitive's entry | the Tooltip, Calendar, Combobox and Menu directives | 281,035 B / 83,020 B |
36
+ | `forty-cdk/defaults` | the four defaults pairs and the tooltip's coordinator | 209,552 B / 63,578 B |
37
+ | No root providers | nothing from forty-cdk | 206,303 B / 62,301 B |
38
+
39
+ A pair you do not import is not bundled: every token is a top-level `new InjectionToken(...)`, which a bundler drops when nothing reads it.
40
+
41
+ A date adapter follows the same rule from [`forty-cdk/date-adapter`](../date-adapter): `NativeDateAdapter`, `provideNativeDateAdapter` and the `FOR_DATE_ADAPTER` token they provide import nothing but `@angular/core`, so providing one next to these pairs keeps the primitives and the code they share out of `main`.
42
+
43
+ ## API
44
+
45
+ Each pair resolves the same way: a scope's overrides merge key by key over its parent scope's, and the library fallback fills the rest. A key set to `undefined` is treated as omitted. Pass a function instead of an object to build the overrides where `inject()` is available; it runs once per injector that resolves the token. The primitive's README documents its keys.
46
+
47
+ | Configures | Provider | Token | Also exported |
48
+ | --------------- | ----------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------- |
49
+ | avatar | `provideForAvatarDefaults` | `FOR_AVATAR_DEFAULTS` | `ForAvatarDefaults` |
50
+ | breadcrumbs | `provideForBreadcrumbsDefaults` | `FOR_BREADCRUMBS_DEFAULTS` | `ForBreadcrumbsDefaults` |
51
+ | breakpoints | `provideForBreakpointsDefaults` | `FOR_BREAKPOINTS_DEFAULTS` | `BreakpointMap`, `ForBreakpointsDefaults`, `forBreakpointsTailwind`, `TailwindBreakpointName` |
52
+ | calendar | `provideForCalendarDefaults` | `FOR_CALENDAR_DEFAULTS` | `ForCalendarDefaults` |
53
+ | carousel | `provideForCarouselDefaults` | `FOR_CAROUSEL_DEFAULTS` | `CarouselAlign`, `ForCarouselDefaults` |
54
+ | combobox | `provideForComboboxDefaults` | `FOR_COMBOBOX_DEFAULTS` | `ForComboboxDefaults`, `ForComboboxOpenHighlight` |
55
+ | context-menu | `provideForContextMenuDefaults` | `FOR_CONTEXT_MENU_DEFAULTS` | `ForContextMenuDefaults` |
56
+ | date-field | `provideForDateFieldDefaults` | `FOR_DATE_FIELD_DEFAULTS` | `DEFAULT_DATE_FIELD_SEGMENT_LABELS`, `ForDateFieldDefaults`, `ForDateFieldSegmentLabels` |
57
+ | date-picker | `provideForDatePickerDefaults` | `FOR_DATE_PICKER_DEFAULTS` | `ForDatePickerDefaults` |
58
+ | date-field | `provideForDateRangeFieldDefaults` | `FOR_DATE_RANGE_FIELD_DEFAULTS` | `DEFAULT_DATE_RANGE_FIELD_SEGMENT_LABELS`, `ForDateRangeFieldDefaults`, `ForDateRangeFieldSegmentLabels` |
59
+ | date-picker | `provideForDateRangePickerDefaults` | `FOR_DATE_RANGE_PICKER_DEFAULTS` | `ForDateRangePickerDefaults` |
60
+ | dialog | `provideForDialogDefaults` | `FOR_DIALOG_DEFAULTS` | `ForDialogDefaults` |
61
+ | drag-drop | `provideForDragDropDefaults` | `FOR_DRAG_DROP_DEFAULTS` | `ForDragDropDefaults` |
62
+ | drawer | `provideForDrawerDefaults` | `FOR_DRAWER_DEFAULTS` | `ForDrawerDefaults` |
63
+ | dropdown-menu | `provideForDropdownMenuDefaults` | `FOR_DROPDOWN_MENU_DEFAULTS` | `ForDropdownMenuDefaults` |
64
+ | hover-card | `provideForHoverCardDefaults` | `FOR_HOVER_CARD_DEFAULTS` | `ForHoverCardDefaults`, `HoverCardCoordinator`, `HoverCardDefaultsOptions` |
65
+ | listbox | `provideForListboxDefaults` | `FOR_LISTBOX_DEFAULTS` | `ForListboxDefaults` |
66
+ | menubar | `provideForMenubarDefaults` | `FOR_MENUBAR_DEFAULTS` | `ForMenubarDefaults` |
67
+ | menu | `provideForMenuDefaults` | `FOR_MENU_DEFAULTS` | `ForMenuDefaults` |
68
+ | navigation-menu | `provideForNavigationMenuDefaults` | `FOR_NAVIGATION_MENU_DEFAULTS` | `ForNavigationMenuDefaults` |
69
+ | number-input | `provideForNumberInputDefaults` | `FOR_NUMBER_INPUT_DEFAULTS` | `ForNumberInputDefaults` |
70
+ | pagination | `provideForPaginationDefaults` | `FOR_PAGINATION_DEFAULTS` | `ForPaginationDefaults` |
71
+ | popover | `provideForPopoverDefaults` | `FOR_POPOVER_DEFAULTS` | `ForPopoverDefaults` |
72
+ | progress | `provideForProgressDefaults` | `FOR_PROGRESS_DEFAULTS` | `ForProgressDefaults` |
73
+ | radio-group | `provideForRadioGroupDefaults` | `FOR_RADIO_GROUP_DEFAULTS` | `ForRadioGroupDefaults` |
74
+ | scroll-area | `provideForScrollAreaDefaults` | `FOR_SCROLL_AREA_DEFAULTS` | `ForScrollAreaDefaults`, `ForScrollAreaTrackPress` |
75
+ | search | `provideForSearchDefaults` | `FOR_SEARCH_DEFAULTS` | `ForSearchDefaults` |
76
+ | select | `provideForSelectDefaults` | `FOR_SELECT_DEFAULTS` | `ForSelectDefaults` |
77
+ | slider | `provideForSliderDefaults` | `FOR_SLIDER_DEFAULTS` | `ForSliderDefaults` |
78
+ | stepper | `provideForStepperDefaults` | `FOR_STEPPER_DEFAULTS` | `ForStepperDefaults`, `StepperActivationMode` |
79
+ | tabs | `provideForTabsDefaults` | `FOR_TABS_DEFAULTS` | `ForTabsDefaults`, `TabsActivationMode` |
80
+ | time-field | `provideForTimeFieldDefaults` | `FOR_TIME_FIELD_DEFAULTS` | `DEFAULT_TIME_FIELD_SEGMENT_LABELS`, `ForTimeFieldDefaults`, `ForTimeFieldSegmentLabels` |
81
+ | time-picker | `provideForTimePickerDefaults` | `FOR_TIME_PICKER_DEFAULTS` | `ForTimePickerDefaults` |
82
+ | time-field | `provideForTimeRangeFieldDefaults` | `FOR_TIME_RANGE_FIELD_DEFAULTS` | `DEFAULT_TIME_RANGE_FIELD_SEGMENT_LABELS`, `ForTimeRangeFieldDefaults`, `ForTimeRangeFieldSegmentLabels` |
83
+ | toast | `provideForToastDefaults` | `FOR_TOAST_DEFAULTS` | `ForToastDefaults`, `ForToastStackShift` |
84
+ | toggle | `provideForToggleDefaults` | `FOR_TOGGLE_DEFAULTS` | `ForToggleDefaults` |
85
+ | toolbar | `provideForToolbarDefaults` | `FOR_TOOLBAR_DEFAULTS` | `ForToolbarDefaults` |
86
+ | tooltip | `provideForTooltipDefaults` | `FOR_TOOLTIP_DEFAULTS` | `ForTooltipDefaults`, `TooltipCoordinator`, `TooltipDefaultsOptions` |
87
+ | tree | `provideForTreeDefaults` | `FOR_TREE_DEFAULTS` | `ForTreeDefaults` |
88
+
89
+ `forBreakpointsTailwind` is the default breakpoint map, the Tailwind CSS scale value for value. `provideForBreakpointsDefaults` takes a whole map and replaces it rather than merging key by key.
90
+
91
+ ### Skip-delay windows
92
+
93
+ Tooltips and hover cards share a skip-delay window per scope: right after one closes, the next one in the same window opens without its open delay. `provideForTooltipDefaults` and `provideForHoverCardDefaults` take a second `options` argument whose `skipDelayScope` decides whether the scope starts its own window (`'own'`) or shares its parent's (`'inherit'`).
94
+
95
+ | Export | What it is |
96
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
97
+ | `TooltipCoordinator` | The window of a tooltip scope. Inject it to read `skipDelay()` or to call `startSkipDelay()` / `cancelSkipDelay()` from your own code. |
98
+ | `HoverCardCoordinator` | The same window for hover cards. It is never shared with a tooltip scope. |
99
+ | `createSkipDelayWindow(duration)` | A standalone window, owned by its caller: `active` reads `true` from `start()` until `duration()` ms later, or until `cancel()`. |
100
+ | `SkipDelayWindow` | The handle `createSkipDelayWindow` returns. |
package/dialog/README.md CHANGED
@@ -334,9 +334,9 @@ The four dismiss callbacks mirror the declarative `(escapeKeyDown)` / `(pointerD
334
334
 
335
335
  ## Keyboard
336
336
 
337
- - **Escape** requests close (reason `'escape'`) when `dismissible`.
337
+ - **Escape** requests close (reason `'escape'`) when `dismissible`. An Escape that cancels an IME composition is left to the IME.
338
338
  - **Tab / Shift+Tab** cycles focus inside the dialog (focus trap, only when `modal`).
339
- - **Click** on `[forDialogBackdrop]` requests close (reason `'backdrop'`) when `dismissible`.
339
+ - **Click** on `[forDialogBackdrop]` requests close (reason `'backdrop'`) when `dismissible` and the dialog was the topmost layer as the press began. A press that closes a stacked dialog or a popover open above it leaves this dialog open.
340
340
 
341
341
  ## Accessibility
342
342
 
@@ -149,7 +149,7 @@ than starting through `[forDraggable]` itself: the keyboard lift of `[forVirtual
149
149
  the virtualized branch of `[forTableRowReorder]`. Those intercept the lift key before the item
150
150
  sees it, so the list carries no lift state for the gesture, and the coordinator marks the item
151
151
  instead. Styling keyed off either attribute therefore behaves the same whether the collection is
152
- windowed or not.
152
+ windowed or not, and the item's `(dragStart)` / `(dragEnd)` fire for those gestures too.
153
153
 
154
154
  The `data-for-drag-preview` row is also the supported hook for **keeping the clone out of element
155
155
  queries**. The default preview is a `cloneNode(true)` copy appended to `document.body`, so for the
@@ -515,6 +515,11 @@ additionally engages **windowed scrub**: the scroll viewport maps onto the whole
515
515
  dataset (top edge → first item, bottom edge → last), so one gesture drops the
516
516
  lifted row at an arbitrary far item without auto-scroll having to reach it.
517
517
 
518
+ Every lift, move and drop announcement of either companion, pointer or keyboard,
519
+ counts dataset positions against the dataset size. Both keep their list **closed**
520
+ to transfers: inside a `[forDropListGroup]` it joins nothing and connects to no
521
+ other list, because their reorder output describes a move within one dataset.
522
+
518
523
  ```html
519
524
  <div
520
525
  forVirtualViewport
package/drawer/README.md CHANGED
@@ -589,7 +589,7 @@ Always nest the child's `@if` inside the parent's `@if`. That guarantees Angular
589
589
  <!-- snippet: fragment -->
590
590
 
591
591
  ```ts
592
- import { provideForDrawerDefaults } from 'forty-cdk/drawer';
592
+ import { provideForDrawerDefaults } from 'forty-cdk/defaults';
593
593
 
594
594
  bootstrapApplication(App, {
595
595
  providers: [
@@ -725,7 +725,7 @@ Implements the [WAI-ARIA Modal Dialog pattern](https://www.w3.org/WAI/ARIA/apg/p
725
725
 
726
726
  A drawer that mounts with neither a `[forDrawerTitle]` nor an `ariaLabel` logs a dev-mode warning (`FORCDK-CORE-011`) after its first render. To land focus somewhere other than the first focusable element on open, such as the least destructive action or a static heading carrying `tabindex="-1"`, mark it with [`[forDrawerInitialFocus]`](#fordrawerinitialfocus).
727
727
 
728
- Keyboard: **Escape** closes the topmost drawer when `dismissible`; **Tab / Shift+Tab** cycles focus inside the drawer when `modal`; **Click** on `[forDrawerBackdrop]` closes when `dismissible`.
728
+ Keyboard: **Escape** closes the topmost drawer when `dismissible`, unless it cancels an IME composition; **Tab / Shift+Tab** cycles focus inside the drawer when `modal`; **Click** on `[forDrawerBackdrop]` closes when `dismissible` and the drawer was the topmost layer as the press began, so a press on a parent's backdrop closes only the child drawer above it.
729
729
 
730
730
  ## Styling
731
731
 
@@ -1,7 +1,9 @@
1
1
  import { isPlatformBrowser } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, inject, input, numberAttribute, signal, linkedSignal, PLATFORM_ID, computed, effect, DestroyRef, Directive, ElementRef, output, afterNextRender } from '@angular/core';
4
- import { orphanContextError, assertRootContext, createDefaults } from 'forty-cdk/core';
4
+ import { orphanContextError, assertRootContext } from 'forty-cdk/core';
5
+ import { FOR_AVATAR_DEFAULTS } from 'forty-cdk/defaults';
6
+ export { FOR_AVATAR_DEFAULTS, provideForAvatarDefaults } from 'forty-cdk/defaults';
5
7
 
6
8
 
7
9
 
@@ -77,30 +79,6 @@ function injectAvatarImageContext() {
77
79
 
78
80
 
79
81
 
80
- const FOR_AVATAR_FALLBACK_DEFAULTS = {
81
- fallbackDelayMs: 0,
82
- };
83
- const { token, provideDefaults } = createDefaults('FOR_AVATAR_DEFAULTS', FOR_AVATAR_FALLBACK_DEFAULTS);
84
-
85
- const FOR_AVATAR_DEFAULTS = token;
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
- function provideForAvatarDefaults(defaults = {}) {
96
- return provideDefaults(defaults);
97
- }
98
-
99
-
100
-
101
-
102
-
103
-
104
82
 
105
83
 
106
84
 
@@ -373,5 +351,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
373
351
 
374
352
 
375
353
 
376
- export { FOR_AVATAR_CONTEXT, FOR_AVATAR_DEFAULTS, ForAvatar, ForAvatarFallback, ForAvatarImage, provideForAvatarDefaults };
354
+ export { FOR_AVATAR_CONTEXT, ForAvatar, ForAvatarFallback, ForAvatarImage };
377
355
  //# sourceMappingURL=forty-cdk-avatar.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar-defaults.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\nimport { assertRootContext, orphanContextError } from 'forty-cdk/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\n/** Read surface `[forAvatar]` publishes through {@link FOR_AVATAR_CONTEXT}. */\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n}\n\n/**\n * The avatar's piece-coordination surface: the one call `[forAvatarImage]`\n * makes so the root can transition its state machine.\n *\n * **Not** part of {@link ForAvatarContext} and never exported from\n * `public-api.ts` — a consumer drives the status by binding `[src]`, never by\n * reporting a transition into the root.\n */\nexport interface AvatarPieceContext {\n /** Reported by `[forAvatarImage]` so the root can transition its state machine. */\n reportStatus(status: ForAvatarStatus): void;\n}\n\n/**\n * The avatar's internal coordination surface: everything\n * {@link ForAvatarContext} publishes plus the {@link AvatarPieceContext} call.\n *\n * Never exported from `public-api.ts`. It is the type the pieces read\n * {@link FOR_AVATAR_CONTEXT} at, so a consumer who injects that token gets the\n * read surface while `[forAvatarImage]` gets the transition channel. `ForAvatar`\n * declares `reportStatus` TS-`private`, which keeps it out of the emitted\n * `.d.ts` while `useExisting` still satisfies this contract at runtime.\n */\nexport interface AvatarContext extends ForAvatarContext, AvatarPieceContext {}\n\n/**\n * DI token for the avatar's coordination surface, provided by `[forAvatar]`.\n *\n * Publicly typed as the read surface {@link ForAvatarContext}, which is the whole of\n * what the token promises a consumer. `[forAvatarImage]` reads the same token at an\n * internal type that adds the status-report channel, so a wrapper re-providing it must\n * alias it to the root: `{ provide: FOR_AVATAR_CONTEXT, useExisting: MyAvatar }`, where\n * `MyAvatar` extends `ForAvatar`. A value that merely satisfies the declared type\n * resolves too, and is rejected in dev mode by the first piece to reach the channel.\n */\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * The constant half of both resolvers' {@link assertRootContext} calls, so the\n * fallback and the image state the same requirement.\n */\nconst ROOT_ASSERTION = {\n entryPoint: 'avatar',\n token: 'FOR_AVATAR_CONTEXT',\n root: '[forAvatar]',\n};\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-001',\n piece,\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece,\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n\n/**\n * `[forAvatarImage]`'s own resolver. It reports a distinct failure from the rest\n * of the anatomy — the image is the piece that drives the state machine, so an\n * orphaned one silently never transitions — and therefore carries its own code\n * rather than routing through {@link injectAvatarContext}.\n */\nexport function injectAvatarImageContext(): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-002',\n piece: 'ForAvatarImage',\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece: 'ForAvatarImage',\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant avatars in the surrounding injector\n * scope. Configure with `provideForAvatarDefaults` either at the\n * application root or in any component's `providers` array; partial\n * overrides merge with the parent scope.\n */\nexport interface ForAvatarDefaults {\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` to show immediately during\n * `idle` / `loading`, or to e.g. `500` to skip rendering during quick\n * cached loads. `error` always shows the fallback immediately.\n */\n fallbackDelayMs: number;\n}\n\n/**\n * Library fallback for avatar defaults, read at the root injector when no\n * consumer has called `provideForAvatarDefaults`. Exported for the shared defaults\n * contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_AVATAR_FALLBACK_DEFAULTS: ForAvatarDefaults = {\n fallbackDelayMs: 0,\n};\n\nconst { token, provideDefaults } = createDefaults<ForAvatarDefaults>(\n 'FOR_AVATAR_DEFAULTS',\n FOR_AVATAR_FALLBACK_DEFAULTS,\n);\n\n/** Token holding the resolved avatar defaults for the current scope. */\nexport const FOR_AVATAR_DEFAULTS = token;\n\n/**\n * Configures forty-cdk avatar defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n *\n * Pass a function instead of an object to build the overrides where\n * `inject()` is available; it runs once per injector that resolves the\n * defaults.\n */\nexport function provideForAvatarDefaults(\n defaults: Partial<ForAvatarDefaults> | (() => Partial<ForAvatarDefaults>) = {},\n): Provider[] {\n return provideDefaults(defaults);\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from './avatar-defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: (value: unknown) => {\n const parsed = numberAttribute(value);\n return Number.isFinite(parsed) ? parsed : this.#defaults.fallbackDelayMs;\n },\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n private reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n PLATFORM_ID,\n} from '@angular/core';\n\nimport { type ForAvatarStatus, injectAvatarImageContext } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = injectAvatarImageContext();\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChange = output<ForAvatarStatus>();\n\n constructor() {\n const parent = this.#parent;\n const host = this.#host;\n\n let destroyed = false;\n inject(DestroyRef).onDestroy(() => (destroyed = true));\n\n afterNextRender(() => {\n this.#syncFromAttr();\n });\n\n if (isPlatformBrowser(inject(PLATFORM_ID)) && typeof MutationObserver !== 'undefined') {\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n }\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (destroyed) return;\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChange.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('error');\n }\n\n /**\n * Cheap string-identity fast-path: true when the `src` the host currently\n * carries is the one the latest `#syncFromAttr` observed, so a native\n * load/error event has not drifted away from the in-flight request. This\n * proves src-attribute stability only; request *completeness* (a late event\n * from a superseded resource that happens to share the current src) is\n * verified separately in onLoad/onError via `this.#host.complete`.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\nimport { assertRootContext, orphanContextError } from 'forty-cdk/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\n/** Read surface `[forAvatar]` publishes through {@link FOR_AVATAR_CONTEXT}. */\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n}\n\n/**\n * The avatar's piece-coordination surface: the one call `[forAvatarImage]`\n * makes so the root can transition its state machine.\n *\n * **Not** part of {@link ForAvatarContext} and never exported from\n * `public-api.ts` — a consumer drives the status by binding `[src]`, never by\n * reporting a transition into the root.\n */\nexport interface AvatarPieceContext {\n /** Reported by `[forAvatarImage]` so the root can transition its state machine. */\n reportStatus(status: ForAvatarStatus): void;\n}\n\n/**\n * The avatar's internal coordination surface: everything\n * {@link ForAvatarContext} publishes plus the {@link AvatarPieceContext} call.\n *\n * Never exported from `public-api.ts`. It is the type the pieces read\n * {@link FOR_AVATAR_CONTEXT} at, so a consumer who injects that token gets the\n * read surface while `[forAvatarImage]` gets the transition channel. `ForAvatar`\n * declares `reportStatus` TS-`private`, which keeps it out of the emitted\n * `.d.ts` while `useExisting` still satisfies this contract at runtime.\n */\nexport interface AvatarContext extends ForAvatarContext, AvatarPieceContext {}\n\n/**\n * DI token for the avatar's coordination surface, provided by `[forAvatar]`.\n *\n * Publicly typed as the read surface {@link ForAvatarContext}, which is the whole of\n * what the token promises a consumer. `[forAvatarImage]` reads the same token at an\n * internal type that adds the status-report channel, so a wrapper re-providing it must\n * alias it to the root: `{ provide: FOR_AVATAR_CONTEXT, useExisting: MyAvatar }`, where\n * `MyAvatar` extends `ForAvatar`. A value that merely satisfies the declared type\n * resolves too, and is rejected in dev mode by the first piece to reach the channel.\n */\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * The constant half of both resolvers' {@link assertRootContext} calls, so the\n * fallback and the image state the same requirement.\n */\nconst ROOT_ASSERTION = {\n entryPoint: 'avatar',\n token: 'FOR_AVATAR_CONTEXT',\n root: '[forAvatar]',\n};\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-001',\n piece,\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece,\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n\n/**\n * `[forAvatarImage]`'s own resolver. It reports a distinct failure from the rest\n * of the anatomy — the image is the piece that drives the state machine, so an\n * orphaned one silently never transitions — and therefore carries its own code\n * rather than routing through {@link injectAvatarContext}.\n */\nexport function injectAvatarImageContext(): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-002',\n piece: 'ForAvatarImage',\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece: 'ForAvatarImage',\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from 'forty-cdk/defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: (value: unknown) => {\n const parsed = numberAttribute(value);\n return Number.isFinite(parsed) ? parsed : this.#defaults.fallbackDelayMs;\n },\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n private reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n PLATFORM_ID,\n} from '@angular/core';\n\nimport { type ForAvatarStatus, injectAvatarImageContext } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = injectAvatarImageContext();\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChange = output<ForAvatarStatus>();\n\n constructor() {\n const parent = this.#parent;\n const host = this.#host;\n\n let destroyed = false;\n inject(DestroyRef).onDestroy(() => (destroyed = true));\n\n afterNextRender(() => {\n this.#syncFromAttr();\n });\n\n if (isPlatformBrowser(inject(PLATFORM_ID)) && typeof MutationObserver !== 'undefined') {\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n }\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (destroyed) return;\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChange.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('error');\n }\n\n /**\n * Cheap string-identity fast-path: true when the `src` the host currently\n * carries is the one the latest `#syncFromAttr` observed, so a native\n * load/error event has not drifted away from the in-flight request. This\n * proves src-attribute stability only; request *completeness* (a late event\n * from a superseded resource that happens to share the current src) is\n * verified separately in onLoad/onError via `this.#host.complete`.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,30 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, input, Directive, booleanAttribute } from '@angular/core';
3
- import { createDefaults, hostAriaLabel, resolveTextInput } from 'forty-cdk/core';
4
-
5
-
6
-
7
-
8
-
9
-
10
- const FOR_BREADCRUMBS_FALLBACK_DEFAULTS = {
11
- label: 'Breadcrumb',
12
- };
13
- const { token, provideDefaults } = createDefaults('FOR_BREADCRUMBS_DEFAULTS', FOR_BREADCRUMBS_FALLBACK_DEFAULTS);
14
-
15
- const FOR_BREADCRUMBS_DEFAULTS = token;
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
- function provideForBreadcrumbsDefaults(defaults = {}) {
26
- return provideDefaults(defaults);
27
- }
3
+ import { hostAriaLabel, resolveTextInput } from 'forty-cdk/core';
4
+ import { FOR_BREADCRUMBS_DEFAULTS } from 'forty-cdk/defaults';
5
+ export { FOR_BREADCRUMBS_DEFAULTS, provideForBreadcrumbsDefaults } from 'forty-cdk/defaults';
28
6
 
29
7
 
30
8
 
@@ -137,5 +115,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
137
115
 
138
116
 
139
117
 
140
- export { FOR_BREADCRUMBS_DEFAULTS, ForBreadcrumbItem, ForBreadcrumbSeparator, ForBreadcrumbs, provideForBreadcrumbsDefaults };
118
+ export { ForBreadcrumbItem, ForBreadcrumbSeparator, ForBreadcrumbs };
141
119
  //# sourceMappingURL=forty-cdk-breadcrumbs.mjs.map