forty-cdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/accordion/README.md +122 -0
  2. package/aspect-ratio/README.md +76 -0
  3. package/avatar/README.md +100 -0
  4. package/breadcrumbs/README.md +49 -0
  5. package/breakpoints/README.md +81 -0
  6. package/button/README.md +49 -0
  7. package/calendar/README.md +458 -0
  8. package/carousel/README.md +358 -0
  9. package/checkbox/README.md +146 -0
  10. package/combobox/README.md +535 -0
  11. package/context-menu/README.md +139 -0
  12. package/date-field/README.md +184 -0
  13. package/date-picker/README.md +338 -0
  14. package/dialog/README.md +388 -0
  15. package/disclosure/README.md +114 -0
  16. package/drag-drop/README.md +359 -0
  17. package/drawer/README.md +560 -0
  18. package/dropdown-menu/README.md +176 -0
  19. package/fesm2022/forty-cdk-accordion.mjs +348 -0
  20. package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
  21. package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
  22. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
  23. package/fesm2022/forty-cdk-avatar.mjs +308 -0
  24. package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
  25. package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
  26. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
  27. package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
  28. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
  29. package/fesm2022/forty-cdk-button.mjs +134 -0
  30. package/fesm2022/forty-cdk-button.mjs.map +1 -0
  31. package/fesm2022/forty-cdk-calendar.mjs +2034 -0
  32. package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-carousel.mjs +968 -0
  34. package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
  35. package/fesm2022/forty-cdk-checkbox.mjs +226 -0
  36. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
  37. package/fesm2022/forty-cdk-combobox.mjs +2596 -0
  38. package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-context-menu.mjs +413 -0
  40. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
  41. package/fesm2022/forty-cdk-core.mjs +9022 -0
  42. package/fesm2022/forty-cdk-core.mjs.map +1 -0
  43. package/fesm2022/forty-cdk-date-field.mjs +744 -0
  44. package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
  45. package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
  46. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
  47. package/fesm2022/forty-cdk-dialog.mjs +707 -0
  48. package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-disclosure.mjs +190 -0
  50. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
  51. package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
  52. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
  53. package/fesm2022/forty-cdk-drawer.mjs +1641 -0
  54. package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
  55. package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
  56. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
  57. package/fesm2022/forty-cdk-field.mjs +425 -0
  58. package/fesm2022/forty-cdk-field.mjs.map +1 -0
  59. package/fesm2022/forty-cdk-fieldset.mjs +164 -0
  60. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
  61. package/fesm2022/forty-cdk-file-upload.mjs +221 -0
  62. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
  63. package/fesm2022/forty-cdk-hover-card.mjs +496 -0
  64. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
  65. package/fesm2022/forty-cdk-input.mjs +274 -0
  66. package/fesm2022/forty-cdk-input.mjs.map +1 -0
  67. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  68. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-listbox.mjs +1279 -0
  70. package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
  71. package/fesm2022/forty-cdk-menu.mjs +1439 -0
  72. package/fesm2022/forty-cdk-menu.mjs.map +1 -0
  73. package/fesm2022/forty-cdk-menubar.mjs +787 -0
  74. package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
  75. package/fesm2022/forty-cdk-meter.mjs +211 -0
  76. package/fesm2022/forty-cdk-meter.mjs.map +1 -0
  77. package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
  78. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
  79. package/fesm2022/forty-cdk-number-input.mjs +559 -0
  80. package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
  81. package/fesm2022/forty-cdk-otp-input.mjs +527 -0
  82. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
  83. package/fesm2022/forty-cdk-pagination.mjs +323 -0
  84. package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
  85. package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
  86. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
  87. package/fesm2022/forty-cdk-popover.mjs +698 -0
  88. package/fesm2022/forty-cdk-popover.mjs.map +1 -0
  89. package/fesm2022/forty-cdk-progress.mjs +226 -0
  90. package/fesm2022/forty-cdk-progress.mjs.map +1 -0
  91. package/fesm2022/forty-cdk-radio-group.mjs +378 -0
  92. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
  94. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
  95. package/fesm2022/forty-cdk-search.mjs +205 -0
  96. package/fesm2022/forty-cdk-search.mjs.map +1 -0
  97. package/fesm2022/forty-cdk-select.mjs +1661 -0
  98. package/fesm2022/forty-cdk-select.mjs.map +1 -0
  99. package/fesm2022/forty-cdk-separator.mjs +82 -0
  100. package/fesm2022/forty-cdk-separator.mjs.map +1 -0
  101. package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
  102. package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
  103. package/fesm2022/forty-cdk-slider.mjs +803 -0
  104. package/fesm2022/forty-cdk-slider.mjs.map +1 -0
  105. package/fesm2022/forty-cdk-stepper.mjs +886 -0
  106. package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
  107. package/fesm2022/forty-cdk-switch.mjs +137 -0
  108. package/fesm2022/forty-cdk-switch.mjs.map +1 -0
  109. package/fesm2022/forty-cdk-table.mjs +1518 -0
  110. package/fesm2022/forty-cdk-table.mjs.map +1 -0
  111. package/fesm2022/forty-cdk-tabs.mjs +400 -0
  112. package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
  113. package/fesm2022/forty-cdk-time-field.mjs +593 -0
  114. package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
  115. package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
  116. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
  117. package/fesm2022/forty-cdk-toast.mjs +1153 -0
  118. package/fesm2022/forty-cdk-toast.mjs.map +1 -0
  119. package/fesm2022/forty-cdk-toggle.mjs +516 -0
  120. package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
  121. package/fesm2022/forty-cdk-toolbar.mjs +374 -0
  122. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
  123. package/fesm2022/forty-cdk-tooltip.mjs +672 -0
  124. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
  125. package/fesm2022/forty-cdk-tree.mjs +2007 -0
  126. package/fesm2022/forty-cdk-tree.mjs.map +1 -0
  127. package/fesm2022/forty-cdk-virtualization.mjs +1 -1
  128. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  129. package/fesm2022/forty-cdk.mjs +0 -43310
  130. package/fesm2022/forty-cdk.mjs.map +1 -1
  131. package/field/README.md +97 -0
  132. package/fieldset/README.md +86 -0
  133. package/file-upload/README.md +73 -0
  134. package/hover-card/README.md +171 -0
  135. package/input/README.md +156 -0
  136. package/listbox/README.md +424 -0
  137. package/menu/README.md +181 -0
  138. package/menubar/README.md +140 -0
  139. package/meter/README.md +128 -0
  140. package/navigation-menu/README.md +253 -0
  141. package/number-input/README.md +171 -0
  142. package/otp-input/README.md +198 -0
  143. package/package.json +213 -1
  144. package/pagination/README.md +61 -0
  145. package/pane-resizer/README.md +136 -0
  146. package/popover/README.md +262 -0
  147. package/progress/README.md +115 -0
  148. package/radio-group/README.md +129 -0
  149. package/scroll-area/README.md +184 -0
  150. package/search/README.md +42 -0
  151. package/select/README.md +488 -0
  152. package/separator/README.md +84 -0
  153. package/signal-forms/README.md +72 -0
  154. package/slider/README.md +152 -0
  155. package/stepper/README.md +292 -0
  156. package/switch/README.md +116 -0
  157. package/table/README.md +769 -0
  158. package/tabs/README.md +130 -0
  159. package/time-field/README.md +157 -0
  160. package/time-picker/README.md +172 -0
  161. package/toast/README.md +398 -0
  162. package/toggle/README.md +224 -0
  163. package/toolbar/README.md +109 -0
  164. package/tooltip/README.md +274 -0
  165. package/tree/README.md +708 -0
  166. package/types/forty-cdk-accordion.d.ts +242 -0
  167. package/types/forty-cdk-aspect-ratio.d.ts +59 -0
  168. package/types/forty-cdk-avatar.d.ts +133 -0
  169. package/types/forty-cdk-breadcrumbs.d.ts +92 -0
  170. package/types/forty-cdk-breakpoints.d.ts +141 -0
  171. package/types/forty-cdk-button.d.ts +80 -0
  172. package/types/forty-cdk-calendar.d.ts +914 -0
  173. package/types/forty-cdk-carousel.d.ts +530 -0
  174. package/types/forty-cdk-checkbox.d.ts +141 -0
  175. package/types/forty-cdk-combobox.d.ts +1259 -0
  176. package/types/forty-cdk-context-menu.d.ts +313 -0
  177. package/types/forty-cdk-core.d.ts +5774 -0
  178. package/types/forty-cdk-date-field.d.ts +307 -0
  179. package/types/forty-cdk-date-picker.d.ts +622 -0
  180. package/types/forty-cdk-dialog.d.ts +546 -0
  181. package/types/forty-cdk-disclosure.d.ts +127 -0
  182. package/types/forty-cdk-drag-drop.d.ts +456 -0
  183. package/types/forty-cdk-drawer.d.ts +871 -0
  184. package/types/forty-cdk-dropdown-menu.d.ts +242 -0
  185. package/types/forty-cdk-field.d.ts +236 -0
  186. package/types/forty-cdk-fieldset.d.ts +119 -0
  187. package/types/forty-cdk-file-upload.d.ts +124 -0
  188. package/types/forty-cdk-hover-card.d.ts +320 -0
  189. package/types/forty-cdk-input.d.ts +169 -0
  190. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  191. package/types/forty-cdk-listbox.d.ts +513 -0
  192. package/types/forty-cdk-menu.d.ts +629 -0
  193. package/types/forty-cdk-menubar.d.ts +451 -0
  194. package/types/forty-cdk-meter.d.ts +122 -0
  195. package/types/forty-cdk-navigation-menu.d.ts +514 -0
  196. package/types/forty-cdk-number-input.d.ts +319 -0
  197. package/types/forty-cdk-otp-input.d.ts +248 -0
  198. package/types/forty-cdk-pagination.d.ts +214 -0
  199. package/types/forty-cdk-pane-resizer.d.ts +145 -0
  200. package/types/forty-cdk-popover.d.ts +509 -0
  201. package/types/forty-cdk-progress.d.ts +143 -0
  202. package/types/forty-cdk-radio-group.d.ts +222 -0
  203. package/types/forty-cdk-scroll-area.d.ts +258 -0
  204. package/types/forty-cdk-search.d.ts +142 -0
  205. package/types/forty-cdk-select.d.ts +899 -0
  206. package/types/forty-cdk-separator.d.ts +59 -0
  207. package/types/forty-cdk-signal-forms.d.ts +58 -0
  208. package/types/forty-cdk-slider.d.ts +379 -0
  209. package/types/forty-cdk-stepper.d.ts +650 -0
  210. package/types/forty-cdk-switch.d.ts +87 -0
  211. package/types/forty-cdk-table.d.ts +723 -0
  212. package/types/forty-cdk-tabs.d.ts +235 -0
  213. package/types/forty-cdk-time-field.d.ts +307 -0
  214. package/types/forty-cdk-time-picker.d.ts +578 -0
  215. package/types/forty-cdk-toast.d.ts +598 -0
  216. package/types/forty-cdk-toggle.d.ts +310 -0
  217. package/types/forty-cdk-toolbar.d.ts +217 -0
  218. package/types/forty-cdk-tooltip.d.ts +436 -0
  219. package/types/forty-cdk-tree.d.ts +688 -0
  220. package/types/forty-cdk.d.ts +1 -19743
@@ -0,0 +1,886 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, model, output, input, booleanAttribute, computed, effect, Directive, ElementRef } from '@angular/core';
3
+ import { createDefaults, injectTextDirection, RovingTabindex, Collection, firstEnabledHost, reconcileRovingActive, moveIndex, registerHandle, hostId, resolveListNavigation, injectHasFocusableContent } from 'forty-cdk/core';
4
+
5
+ /** Injection token for the root stepper context (`ForStepper`). */
6
+ const FOR_STEPPER_CONTEXT = new InjectionToken('FOR_STEPPER_CONTEXT');
7
+ /** Injection token for the per-step item context (`ForStepperItem`). */
8
+ const FOR_STEPPER_ITEM_CONTEXT = new InjectionToken('FOR_STEPPER_ITEM_CONTEXT');
9
+ function injectStepperContext(piece) {
10
+ const ctx = inject(FOR_STEPPER_CONTEXT, { optional: true });
11
+ if (!ctx) {
12
+ throw new Error(`[forty-cdk/stepper] ${piece} must be used inside a [forStepper] element.`);
13
+ }
14
+ return ctx;
15
+ }
16
+ function injectStepperItemContext(piece) {
17
+ const ctx = inject(FOR_STEPPER_ITEM_CONTEXT, { optional: true });
18
+ if (!ctx) {
19
+ throw new Error(`[forty-cdk/stepper] ${piece} must be used inside a [forStepperItem] element.`);
20
+ }
21
+ return ctx;
22
+ }
23
+
24
+ /**
25
+ * Library fallback for stepper defaults, read at the root injector when no
26
+ * consumer has called `provideForStepperDefaults`. Exported for the shared
27
+ * defaults contract spec; not re-exported from the primitive's public entry.
28
+ */
29
+ const FOR_STEPPER_FALLBACK_DEFAULTS = {
30
+ activationMode: 'manual',
31
+ loop: true,
32
+ };
33
+ const { token, provideDefaults } = createDefaults('FOR_STEPPER_DEFAULTS', FOR_STEPPER_FALLBACK_DEFAULTS);
34
+ /** Token holding the resolved stepper defaults for the current scope. */
35
+ const FOR_STEPPER_DEFAULTS = token;
36
+ /**
37
+ * Configures forty-cdk stepper defaults for this injector scope. Partial
38
+ * overrides inherit unspecified keys from the parent scope (or library defaults
39
+ * at the root).
40
+ */
41
+ function provideForStepperDefaults(defaults = {}) {
42
+ return provideDefaults(defaults);
43
+ }
44
+
45
+ /**
46
+ * Root of the Stepper primitive. Owns the selected step index, linear
47
+ * progression, accessibility mode, orientation, and disabled state. Provides
48
+ * the shared context to descendant directives.
49
+ *
50
+ * Implements the [WAI-ARIA Tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)
51
+ * in `mode="interactive"` (roving tabindex, `role="tablist"`, `aria-selected`)
52
+ * and an ordered-list progress pattern with `aria-current="step"` in
53
+ * `mode="progress"`.
54
+ *
55
+ * `activationMode='manual'` (default): arrow navigation only moves focus;
56
+ * Space / Enter activates. Recommended for wizards where step activation
57
+ * triggers validation. `activationMode='automatic'`: arrow navigation moves
58
+ * focus AND selects the step.
59
+ */
60
+ class ForStepper {
61
+ #defaults = inject(FOR_STEPPER_DEFAULTS);
62
+ /**
63
+ * Two-way bindable selected step index in the inclusive range `0 … count`.
64
+ * Defaults to `0`. The terminal value `=== count()` (one past the last step) is
65
+ * the **completed** state (see `isCompleted` / `complete`). The `model()` change
66
+ * emitter (`(selectedIndexChange)`) fires only on internal selection changes
67
+ * (trigger click, Next/Previous, automatic-mode arrow navigation), never on
68
+ * consumer writes via `[(selectedIndex)]`.
69
+ */
70
+ selectedIndex = model(0, /* @ts-ignore */
71
+ ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
72
+ /**
73
+ * Emits once each time the stepper transitions **into** the completed terminal
74
+ * state — i.e. when `selectedIndex()` reaches `count()`. Does not re-emit while
75
+ * it stays completed; retreating via `previous()` and re-entering emits again.
76
+ */
77
+ complete = output();
78
+ /**
79
+ * When true, steps are only reachable after all preceding steps are completed
80
+ * or optional. Navigating back (`previous()`) always works regardless of this
81
+ * flag.
82
+ */
83
+ linear = input(false, { ...(ngDevMode ? { debugName: "linear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
84
+ /**
85
+ * Accessibility model. `'interactive'` (default): full WAI-ARIA Tabs pattern
86
+ * — `role="tablist"`, `role="tab"`, `role="tabpanel"`, roving tabindex, arrow
87
+ * navigation. `'progress'`: ordered list with `aria-current="step"` on the
88
+ * active trigger — no tab stop manipulation.
89
+ */
90
+ mode = input('interactive', /* @ts-ignore */
91
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
92
+ /**
93
+ * Layout axis for the step list. Affects `aria-orientation` on the list and
94
+ * which arrow keys navigate in interactive mode.
95
+ */
96
+ orientation = input('horizontal', /* @ts-ignore */
97
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
98
+ /**
99
+ * Arrow-key activation timing in interactive mode. Defaults to the value from
100
+ * `provideForStepperDefaults` for the surrounding scope (library default:
101
+ * `'manual'`).
102
+ */
103
+ activationMode = input(this.#defaults.activationMode, /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "activationMode" }] : /* istanbul ignore next */ []));
105
+ /**
106
+ * Whether arrow navigation wraps around past the first / last selectable
107
+ * trigger. Defaults to the value from `provideForStepperDefaults` for the
108
+ * surrounding scope (library default: `true`).
109
+ */
110
+ loop = input(this.#defaults.loop, { ...(ngDevMode ? { debugName: "loop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
111
+ /**
112
+ * When true, all step triggers are non-interactive and the root carries
113
+ * `data-disabled`.
114
+ */
115
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
116
+ /**
117
+ * Writing direction. When unset (default `null`), the inherited ambient
118
+ * direction is resolved from the nearest ancestor carrying a `dir` attribute
119
+ * (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
120
+ * The resolved value is reflected to the host `dir` attribute and swaps
121
+ * ArrowLeft / ArrowRight semantics in RTL.
122
+ */
123
+ _dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
124
+ dir = injectTextDirection(this._dirInput);
125
+ roving = new RovingTabindex();
126
+ #items = new Collection();
127
+ #triggers = new Collection();
128
+ #contents = new Collection();
129
+ /** Total number of registered step items. */
130
+ count = computed(() => this.#items.items().length, /* @ts-ignore */
131
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
132
+ /**
133
+ * True when the stepper is in the terminal **completed** state: `selectedIndex()`
134
+ * has reached `count()` (one past the last step). No step is current and every
135
+ * `[forStepperContent]` panel is inactive while this holds.
136
+ */
137
+ isCompleted = computed(() => this.selectedIndex() >= this.count(), /* @ts-ignore */
138
+ ...(ngDevMode ? [{ debugName: "isCompleted" }] : /* istanbul ignore next */ []));
139
+ #firstSelectableTriggerHost = computed(() => firstEnabledHost(this.#triggers.items()), /* @ts-ignore */
140
+ ...(ngDevMode ? [{ debugName: "#firstSelectableTriggerHost" }] : /* istanbul ignore next */ []));
141
+ constructor() {
142
+ reconcileRovingActive(this.roving, this.#triggers.items);
143
+ let wasCompleted = this.isCompleted();
144
+ effect(() => {
145
+ const completed = this.isCompleted();
146
+ if (completed && !wasCompleted) {
147
+ this.complete.emit();
148
+ }
149
+ wasCompleted = completed;
150
+ });
151
+ }
152
+ indexOf(item) {
153
+ return this.#items.items().indexOf(item);
154
+ }
155
+ isCurrent(index) {
156
+ return this.selectedIndex() === index;
157
+ }
158
+ isReachable(index) {
159
+ if (!this.linear()) {
160
+ return true;
161
+ }
162
+ if (index === 0) {
163
+ return true;
164
+ }
165
+ const items = this.#items.items();
166
+ for (let i = 0; i < index; i++) {
167
+ const item = items[i];
168
+ if (!item || (!item.completed() && !item.optional())) {
169
+ return false;
170
+ }
171
+ }
172
+ return true;
173
+ }
174
+ resolvedStateFor(index) {
175
+ return this.#items.items()[index]?.resolvedState() ?? 'pending';
176
+ }
177
+ select(index) {
178
+ if (this.disabled()) {
179
+ return;
180
+ }
181
+ const count = this.count();
182
+ if (count === 0) {
183
+ return;
184
+ }
185
+ const clamped = Math.max(0, Math.min(index, count));
186
+ if (!this.isReachable(clamped)) {
187
+ return;
188
+ }
189
+ this.selectedIndex.set(clamped);
190
+ }
191
+ next() {
192
+ if (!this.canAdvance()) {
193
+ return;
194
+ }
195
+ this.selectedIndex.set(this.selectedIndex() + 1);
196
+ }
197
+ previous() {
198
+ if (this.disabled() || this.selectedIndex() <= 0) {
199
+ return;
200
+ }
201
+ this.selectedIndex.set(this.selectedIndex() - 1);
202
+ }
203
+ canAdvance() {
204
+ if (this.disabled() || this.selectedIndex() >= this.count()) {
205
+ return false;
206
+ }
207
+ if (!this.linear()) {
208
+ return true;
209
+ }
210
+ const current = this.#items.items()[this.selectedIndex()];
211
+ return (current?.completed() ?? false) || (current?.optional() ?? false);
212
+ }
213
+ canRetreat() {
214
+ return this.selectedIndex() > 0 && !this.disabled();
215
+ }
216
+ navigate(currentTrigger, action) {
217
+ if (this.disabled()) {
218
+ return;
219
+ }
220
+ const triggers = this.#triggers.items();
221
+ if (triggers.length === 0) {
222
+ return;
223
+ }
224
+ const currentIndex = triggers.findIndex((t) => t.host === currentTrigger);
225
+ const next = moveIndex(currentIndex < 0 ? 0 : currentIndex, triggers.length, action, {
226
+ loop: this.loop(),
227
+ isDisabled: (i) => !triggers[i].selectable(),
228
+ });
229
+ if (next === null) {
230
+ return;
231
+ }
232
+ const target = triggers[next];
233
+ if (!target) {
234
+ return;
235
+ }
236
+ target.host.focus();
237
+ if (this.activationMode() === 'automatic') {
238
+ this.selectedIndex.set(next);
239
+ }
240
+ }
241
+ registerItem(h) {
242
+ this.#items.register(h);
243
+ }
244
+ unregisterItem(h) {
245
+ this.#items.unregister(h);
246
+ }
247
+ registerTrigger(h) {
248
+ this.#triggers.register(h);
249
+ }
250
+ unregisterTrigger(h) {
251
+ this.#triggers.unregister(h);
252
+ this.roving.unregister(h.host);
253
+ }
254
+ registerContent(h) {
255
+ this.#contents.register(h);
256
+ }
257
+ unregisterContent(h) {
258
+ this.#contents.unregister(h);
259
+ }
260
+ triggerIdFor(index) {
261
+ return this.#triggers.items()[index]?.id() ?? null;
262
+ }
263
+ contentIdFor(index) {
264
+ return this.#contents.items()[index]?.id() ?? null;
265
+ }
266
+ indexOfContent(host) {
267
+ return this.#contents.indexOfHost(host);
268
+ }
269
+ isFirstSelectableTrigger(el) {
270
+ return this.#firstSelectableTriggerHost() === el;
271
+ }
272
+ hasCurrentTrigger() {
273
+ const idx = this.selectedIndex();
274
+ const triggers = this.#triggers.items();
275
+ if (idx < 0 || idx >= triggers.length) {
276
+ return false;
277
+ }
278
+ const trigger = triggers[idx];
279
+ return trigger !== undefined && trigger.selectable();
280
+ }
281
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepper, deps: [], target: i0.ɵɵFactoryTarget.Directive });
282
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForStepper, isStandalone: true, selector: "[forStepper]", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, activationMode: { classPropertyName: "activationMode", publicName: "activationMode", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange", complete: "complete" }, host: { properties: { "attr.data-orientation": "orientation()", "attr.data-mode": "mode()", "attr.data-disabled": "disabled() ? \"\" : null", "attr.dir": "dir()" } }, providers: [{ provide: FOR_STEPPER_CONTEXT, useExisting: ForStepper }], exportAs: ["forStepper"], ngImport: i0 });
283
+ }
284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepper, decorators: [{
285
+ type: Directive,
286
+ args: [{
287
+ selector: '[forStepper]',
288
+ exportAs: 'forStepper',
289
+ host: {
290
+ '[attr.data-orientation]': 'orientation()',
291
+ '[attr.data-mode]': 'mode()',
292
+ '[attr.data-disabled]': 'disabled() ? "" : null',
293
+ '[attr.dir]': 'dir()',
294
+ },
295
+ providers: [{ provide: FOR_STEPPER_CONTEXT, useExisting: ForStepper }],
296
+ }]
297
+ }], ctorParameters: () => [], propDecorators: { selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], complete: [{ type: i0.Output, args: ["complete"] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], activationMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "activationMode", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }] } });
298
+
299
+ /**
300
+ * The step-list container. In `mode="interactive"` renders as `role="tablist"`
301
+ * with `aria-orientation`; in `mode="progress"` renders as `role="list"`.
302
+ *
303
+ * Apply on an `<ol>` (recommended for semantic list markup) wrapping the
304
+ * `[forStepperItem]` elements. The `[forStepperContent]` panels live as
305
+ * siblings of the list.
306
+ */
307
+ class ForStepperList {
308
+ ctx = injectStepperContext('ForStepperList');
309
+ /** Accessible name for the step list. Defers to a consumer `aria-labelledby`. */
310
+ ariaLabel = input(null, /* @ts-ignore */
311
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
312
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperList, deps: [], target: i0.ɵɵFactoryTarget.Directive });
313
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForStepperList, isStandalone: true, selector: "[forStepperList]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "ctx.mode() === 'interactive' ? 'tablist' : 'list'", "attr.aria-orientation": "ctx.mode() === 'interactive' ? ctx.orientation() : null", "attr.aria-label": "ariaLabel() || null", "attr.data-orientation": "ctx.orientation()" } }, exportAs: ["forStepperList"], ngImport: i0 });
314
+ }
315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperList, decorators: [{
316
+ type: Directive,
317
+ args: [{
318
+ selector: '[forStepperList]',
319
+ exportAs: 'forStepperList',
320
+ host: {
321
+ '[attr.role]': "ctx.mode() === 'interactive' ? 'tablist' : 'list'",
322
+ '[attr.aria-orientation]': "ctx.mode() === 'interactive' ? ctx.orientation() : null",
323
+ '[attr.aria-label]': 'ariaLabel() || null',
324
+ '[attr.data-orientation]': 'ctx.orientation()',
325
+ },
326
+ }]
327
+ }], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
328
+
329
+ /**
330
+ * One step within a `ForStepper`. Owns per-step state (`completed`, `optional`,
331
+ * `disabled`, `hasError`, `state`) and exposes the `ForStepperItemContext` that
332
+ * child `[forStepperTrigger]`, `[forStepperIndicator]`, and
333
+ * `[forStepperSeparator]` directives consume.
334
+ *
335
+ * Apply on a list item element (e.g. `<li forStepperItem>`). The step's index
336
+ * is derived reactively from its DOM position within the stepper via the root's
337
+ * item collection.
338
+ */
339
+ class ForStepperItem {
340
+ ctx = injectStepperContext('ForStepperItem');
341
+ /** Host element — satisfies `ForStepperItemHandle.host` for collection registration. */
342
+ host = inject(ElementRef).nativeElement;
343
+ /**
344
+ * Manual completion flag. When `true`, the step is completed regardless of any
345
+ * bound `field()`. Bind via `[completed]`.
346
+ */
347
+ _completedInput = input(false, { ...(ngDevMode ? { debugName: "_completedInput" } : /* istanbul ignore next */ {}), transform: booleanAttribute, alias: 'completed' });
348
+ /** Marks this step as optional (can be skipped in linear mode). */
349
+ optional = input(false, { ...(ngDevMode ? { debugName: "optional" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
350
+ /** When true, this step's trigger ignores clicks and the step is unreachable by keyboard. */
351
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
352
+ /**
353
+ * Manual error flag. When `true`, the step reflects the `'error'` resolved state
354
+ * (unless current) regardless of any bound `field()`. Bind via `[hasError]`.
355
+ */
356
+ _hasErrorInput = input(false, { ...(ngDevMode ? { debugName: "_hasErrorInput" } : /* istanbul ignore next */ {}), transform: booleanAttribute, alias: 'hasError' });
357
+ /**
358
+ * Optional Signal Forms field. When bound, the step's `completed` and
359
+ * `hasError` derive from the field's reactive validity: `completed` is `true`
360
+ * when the field is valid and touched; `hasError` is `true` when the field is
361
+ * touched and invalid. A manual `[completed]` / `[hasError]` input always wins
362
+ * when set. Leave unset to drive completion manually.
363
+ *
364
+ * The `@angular/forms` peer is optional: the field *type* is imported with
365
+ * `import type`, so binding `[field]` requires the peer but the primitive
366
+ * compiles and tree-shakes without it for consumers who never use it.
367
+ */
368
+ field = input(null, /* @ts-ignore */
369
+ ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
370
+ /**
371
+ * Whether this step is completed. A manual `[completed]` input wins; otherwise,
372
+ * when a `field()` is bound, derives `true` from `field` valid + touched.
373
+ */
374
+ completed = computed(() => {
375
+ if (this._completedInput()) {
376
+ return true;
377
+ }
378
+ const f = this.field();
379
+ if (!f) {
380
+ return false;
381
+ }
382
+ const state = f();
383
+ return state.valid() && state.touched();
384
+ }, /* @ts-ignore */
385
+ ...(ngDevMode ? [{ debugName: "completed" }] : /* istanbul ignore next */ []));
386
+ /**
387
+ * Whether this step has an error. A manual `[hasError]` input wins; otherwise,
388
+ * when a `field()` is bound, derives `true` from `field` touched + invalid.
389
+ */
390
+ hasError = computed(() => {
391
+ if (this._hasErrorInput()) {
392
+ return true;
393
+ }
394
+ const f = this.field();
395
+ if (!f) {
396
+ return false;
397
+ }
398
+ const state = f();
399
+ return state.touched() && state.invalid();
400
+ }, /* @ts-ignore */
401
+ ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
402
+ /**
403
+ * Custom state string override. When non-empty, wins over the derived resolved
404
+ * state (`error`, `active`, `completed`, `pending`) and drives every
405
+ * `data-state` attribute on this step.
406
+ */
407
+ state = input(null, /* @ts-ignore */
408
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
409
+ /** Whether this step is disabled — own `disabled` OR the root stepper's `disabled`. */
410
+ effectiveDisabled = computed(() => this.disabled() || this.ctx.disabled(), /* @ts-ignore */
411
+ ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
412
+ /** DOM-order index of this step within the stepper. */
413
+ index = computed(() => this.ctx.indexOf(this), /* @ts-ignore */
414
+ ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
415
+ /** True when this step is the currently selected step. */
416
+ current = computed(() => this.ctx.isCurrent(this.index()), /* @ts-ignore */
417
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
418
+ /**
419
+ * True when this step can be focused and activated. False when the step is
420
+ * effectively disabled or not yet reachable in linear mode.
421
+ */
422
+ selectable = computed(() => this.ctx.isReachable(this.index()) && !this.effectiveDisabled(), /* @ts-ignore */
423
+ ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
424
+ /**
425
+ * Resolved `data-state` string. Precedence:
426
+ * 1. Custom `state()` when non-empty.
427
+ * 2. `'error'` when `hasError()` and not current.
428
+ * 3. `'active'` when current.
429
+ * 4. `'completed'` when `completed()`.
430
+ * 5. `'pending'` otherwise.
431
+ */
432
+ resolvedState = computed(() => {
433
+ const custom = this.state();
434
+ if (custom) {
435
+ return custom;
436
+ }
437
+ if (this.hasError() && !this.current()) {
438
+ return 'error';
439
+ }
440
+ if (this.current()) {
441
+ return 'active';
442
+ }
443
+ if (this.completed()) {
444
+ return 'completed';
445
+ }
446
+ return 'pending';
447
+ }, /* @ts-ignore */
448
+ ...(ngDevMode ? [{ debugName: "resolvedState" }] : /* istanbul ignore next */ []));
449
+ constructor() {
450
+ registerHandle(this, (h) => this.ctx.registerItem(h), (h) => this.ctx.unregisterItem(h), 'afterNextRender');
451
+ }
452
+ /** Selects this step. Delegates to `ForStepperContext.select(index)`. */
453
+ select() {
454
+ this.ctx.select(this.index());
455
+ }
456
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
457
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForStepperItem, isStandalone: true, selector: "[forStepperItem]", inputs: { _completedInput: { classPropertyName: "_completedInput", publicName: "completed", isSignal: true, isRequired: false, transformFunction: null }, optional: { classPropertyName: "optional", publicName: "optional", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, _hasErrorInput: { classPropertyName: "_hasErrorInput", publicName: "hasError", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-state": "resolvedState()", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.data-orientation": "ctx.orientation()" } }, providers: [{ provide: FOR_STEPPER_ITEM_CONTEXT, useExisting: ForStepperItem }], exportAs: ["forStepperItem"], ngImport: i0 });
458
+ }
459
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperItem, decorators: [{
460
+ type: Directive,
461
+ args: [{
462
+ selector: '[forStepperItem]',
463
+ exportAs: 'forStepperItem',
464
+ host: {
465
+ '[attr.data-state]': 'resolvedState()',
466
+ '[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
467
+ '[attr.data-orientation]': 'ctx.orientation()',
468
+ },
469
+ providers: [{ provide: FOR_STEPPER_ITEM_CONTEXT, useExisting: ForStepperItem }],
470
+ }]
471
+ }], ctorParameters: () => [], propDecorators: { _completedInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "completed", required: false }] }], optional: [{ type: i0.Input, args: [{ isSignal: true, alias: "optional", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], _hasErrorInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasError", required: false }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }] } });
472
+
473
+ /**
474
+ * Header for one step. In `mode="interactive"` carries `role="tab"`, participates
475
+ * in the roving tabindex, and wires `aria-selected` / `aria-controls` /
476
+ * `aria-disabled`. In `mode="progress"` carries `aria-current="step"` on the
477
+ * active step only and is a static element (no tab-stop manipulation).
478
+ *
479
+ * Apply on a `<button type="button">` for interactive mode so Enter / Space
480
+ * activation is native. A static element (`<span>`) is acceptable for progress
481
+ * mode.
482
+ *
483
+ * Disabled triggers in interactive mode keep their tab stop (`aria-disabled`
484
+ * instead of native `disabled`) so assistive tech can announce them.
485
+ */
486
+ class ForStepperTrigger {
487
+ ctx = injectStepperContext('ForStepperTrigger');
488
+ item = injectStepperItemContext('ForStepperTrigger');
489
+ #host = inject(ElementRef).nativeElement;
490
+ /** Generated (or consumer-set static) id for this trigger. Used by content panels for `aria-labelledby`. */
491
+ id = hostId('for-stepper-trigger');
492
+ /** Id of the content panel this trigger controls (the matching panel by step index). */
493
+ controlsId = computed(() => this.ctx.contentIdFor(this.item.index()), /* @ts-ignore */
494
+ ...(ngDevMode ? [{ debugName: "controlsId" }] : /* istanbul ignore next */ []));
495
+ /**
496
+ * APG tabindex cascade (interactive mode only). In progress mode returns
497
+ * `null` so no tab-stop attribute is emitted. In interactive mode:
498
+ * - `null` when not selectable (disabled or unreachable in linear mode).
499
+ * - Roving-tracker value once any trigger has been focused.
500
+ * - `0` when this step is current.
501
+ * - `-1` when another trigger owns the current step.
502
+ * - `0` if this is the first selectable trigger (fallback entry point).
503
+ * - `-1` otherwise.
504
+ */
505
+ tabindex = computed(() => {
506
+ if (this.ctx.mode() !== 'interactive') {
507
+ return null;
508
+ }
509
+ if (!this.item.selectable()) {
510
+ return -1;
511
+ }
512
+ if (this.ctx.roving.hasActive()) {
513
+ return this.ctx.roving.tabindexFor(this.#host);
514
+ }
515
+ if (this.item.current()) {
516
+ return 0;
517
+ }
518
+ if (this.ctx.hasCurrentTrigger()) {
519
+ return -1;
520
+ }
521
+ return this.ctx.isFirstSelectableTrigger(this.#host) ? 0 : -1;
522
+ }, /* @ts-ignore */
523
+ ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
524
+ constructor() {
525
+ const handle = {
526
+ host: this.#host,
527
+ id: this.id,
528
+ selectable: this.item.selectable,
529
+ disabled: computed(() => !this.item.selectable()),
530
+ };
531
+ registerHandle(handle, (h) => this.ctx.registerTrigger(h), (h) => this.ctx.unregisterTrigger(h), 'afterNextRender');
532
+ }
533
+ onClick() {
534
+ if (this.ctx.mode() !== 'interactive' || !this.item.selectable()) {
535
+ return;
536
+ }
537
+ this.item.select();
538
+ }
539
+ onFocus() {
540
+ if (this.ctx.mode() !== 'interactive' || !this.item.selectable()) {
541
+ return;
542
+ }
543
+ this.ctx.roving.setActive(this.#host);
544
+ }
545
+ onKeyDown(event) {
546
+ if (this.ctx.mode() !== 'interactive' || !this.item.selectable()) {
547
+ return;
548
+ }
549
+ const action = resolveListNavigation(event, {
550
+ orientation: this.ctx.orientation(),
551
+ dir: this.ctx.dir(),
552
+ });
553
+ if (!action) {
554
+ return;
555
+ }
556
+ event.preventDefault();
557
+ this.ctx.navigate(this.#host, action);
558
+ }
559
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
560
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperTrigger, isStandalone: true, selector: "[forStepperTrigger]", host: { listeners: { "click": "onClick()", "focus": "onFocus()", "keydown": "onKeyDown($event)" }, properties: { "attr.role": "ctx.mode() === 'interactive' ? 'tab' : null", "id": "id()", "attr.aria-selected": "ctx.mode() === 'interactive' ? (item.current() ? 'true' : 'false') : null", "attr.aria-controls": "ctx.mode() === 'interactive' && item.current() ? controlsId() : null", "attr.aria-current": "ctx.mode() === 'progress' && item.current() ? 'step' : null", "attr.aria-disabled": "ctx.mode() === 'interactive' && !item.selectable() ? 'true' : null", "attr.tabindex": "tabindex()", "attr.data-state": "item.resolvedState()", "attr.data-disabled": "item.effectiveDisabled() ? \"\" : null", "attr.data-orientation": "ctx.orientation()" } }, exportAs: ["forStepperTrigger"], ngImport: i0 });
561
+ }
562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperTrigger, decorators: [{
563
+ type: Directive,
564
+ args: [{
565
+ selector: '[forStepperTrigger]',
566
+ exportAs: 'forStepperTrigger',
567
+ host: {
568
+ '[attr.role]': "ctx.mode() === 'interactive' ? 'tab' : null",
569
+ '[id]': 'id()',
570
+ '[attr.aria-selected]': "ctx.mode() === 'interactive' ? (item.current() ? 'true' : 'false') : null",
571
+ '[attr.aria-controls]': "ctx.mode() === 'interactive' && item.current() ? controlsId() : null",
572
+ '[attr.aria-current]': "ctx.mode() === 'progress' && item.current() ? 'step' : null",
573
+ '[attr.aria-disabled]': "ctx.mode() === 'interactive' && !item.selectable() ? 'true' : null",
574
+ '[attr.tabindex]': 'tabindex()',
575
+ '[attr.data-state]': 'item.resolvedState()',
576
+ '[attr.data-disabled]': 'item.effectiveDisabled() ? "" : null',
577
+ '[attr.data-orientation]': 'ctx.orientation()',
578
+ '(click)': 'onClick()',
579
+ '(focus)': 'onFocus()',
580
+ '(keydown)': 'onKeyDown($event)',
581
+ },
582
+ }]
583
+ }], ctorParameters: () => [] });
584
+
585
+ /**
586
+ * Decorative icon slot for a step. Hidden from the accessibility tree
587
+ * (`aria-hidden="true"`). Reflects the step's resolved `data-state` so
588
+ * consumers can swap icons via CSS `[data-state="completed"]`,
589
+ * `[data-state="error"]`, etc., or with `@if` keyed on the exported
590
+ * `#indicator="forStepperIndicator"` reference.
591
+ *
592
+ * Must be used inside a `[forStepperItem]` element.
593
+ */
594
+ class ForStepperIndicator {
595
+ item = injectStepperItemContext('ForStepperIndicator');
596
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperIndicator, deps: [], target: i0.ɵɵFactoryTarget.Directive });
597
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperIndicator, isStandalone: true, selector: "[forStepperIndicator]", host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "item.resolvedState()" } }, exportAs: ["forStepperIndicator"], ngImport: i0 });
598
+ }
599
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperIndicator, decorators: [{
600
+ type: Directive,
601
+ args: [{
602
+ selector: '[forStepperIndicator]',
603
+ exportAs: 'forStepperIndicator',
604
+ host: {
605
+ 'aria-hidden': 'true',
606
+ '[attr.data-state]': 'item.resolvedState()',
607
+ },
608
+ }]
609
+ }] });
610
+
611
+ /**
612
+ * Decorative connector between steps. Hidden from the accessibility tree
613
+ * (`aria-hidden="true"`). Reflects `data-state="completed"` when the preceding
614
+ * step is marked completed, `data-state="pending"` otherwise. Also reflects
615
+ * `data-orientation` so consumers can style horizontal vs vertical separators
616
+ * differently.
617
+ *
618
+ * Must be used inside a `[forStepperItem]` element.
619
+ */
620
+ class ForStepperSeparator {
621
+ ctx = injectStepperContext('ForStepperSeparator');
622
+ item = injectStepperItemContext('ForStepperSeparator');
623
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperSeparator, deps: [], target: i0.ɵɵFactoryTarget.Directive });
624
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperSeparator, isStandalone: true, selector: "[forStepperSeparator]", host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "item.completed() ? 'completed' : 'pending'", "attr.data-orientation": "ctx.orientation()" } }, exportAs: ["forStepperSeparator"], ngImport: i0 });
625
+ }
626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperSeparator, decorators: [{
627
+ type: Directive,
628
+ args: [{
629
+ selector: '[forStepperSeparator]',
630
+ exportAs: 'forStepperSeparator',
631
+ host: {
632
+ 'aria-hidden': 'true',
633
+ '[attr.data-state]': "item.completed() ? 'completed' : 'pending'",
634
+ '[attr.data-orientation]': 'ctx.orientation()',
635
+ },
636
+ }]
637
+ }] });
638
+
639
+ /**
640
+ * Panel for one step. Correlates to a step by **position** — the Nth
641
+ * `[forStepperContent]` panel in registration (DOM document) order corresponds
642
+ * to the Nth step.
643
+ *
644
+ * In `mode="interactive"` the panel carries `role="tabpanel"` and is a tab
645
+ * stop (`tabindex="0"`) only when it has no focusable content of its own
646
+ * (WAI-ARIA Tabs APG rule). In `mode="progress"` the panel carries
647
+ * `role="group"` and is never a tab stop.
648
+ *
649
+ * While inactive the directive reflects `aria-hidden="true"` and `inert` so
650
+ * mounted-but-inactive panels are removed from the accessibility tree and focus
651
+ * order automatically. DOM presence is the consumer's responsibility — wrap
652
+ * with `@if (current())` to unmount or leave mounted and toggle visibility with
653
+ * CSS via `[data-state="inactive"]`.
654
+ */
655
+ class ForStepperContent {
656
+ ctx = injectStepperContext('ForStepperContent');
657
+ #host = inject(ElementRef).nativeElement;
658
+ /** Generated (or consumer-set static) id for this panel. Used by triggers for `aria-controls`. */
659
+ id = hostId('for-stepper-content');
660
+ #hasFocusableContent = injectHasFocusableContent();
661
+ #index = computed(() => this.ctx.indexOfContent(this.#host), /* @ts-ignore */
662
+ ...(ngDevMode ? [{ debugName: "#index" }] : /* istanbul ignore next */ []));
663
+ /** True when this panel corresponds to the currently selected step. */
664
+ current = computed(() => this.ctx.isCurrent(this.#index()), /* @ts-ignore */
665
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
666
+ /** Id of the trigger that labels this panel (`aria-labelledby`). */
667
+ labelledBy = computed(() => this.ctx.triggerIdFor(this.#index()), /* @ts-ignore */
668
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
669
+ /**
670
+ * APG tabindex: `'0'` only when in interactive mode and the panel has no
671
+ * focusable content of its own, so screen-reader users can reach the panel
672
+ * itself. A panel containing buttons / links / form controls gets no
673
+ * `tabindex`, avoiding a redundant tab stop.
674
+ */
675
+ tabindex = computed(() => {
676
+ if (this.ctx.mode() !== 'interactive') {
677
+ return null;
678
+ }
679
+ return this.#hasFocusableContent() ? null : '0';
680
+ }, /* @ts-ignore */
681
+ ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
682
+ constructor() {
683
+ const handle = { host: this.#host, id: this.id };
684
+ registerHandle(handle, (h) => this.ctx.registerContent(h), (h) => this.ctx.unregisterContent(h), 'afterNextRender');
685
+ }
686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
687
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperContent, isStandalone: true, selector: "[forStepperContent]", host: { properties: { "attr.role": "ctx.mode() === 'interactive' ? 'tabpanel' : 'group'", "id": "id()", "attr.aria-labelledby": "labelledBy()", "attr.tabindex": "tabindex()", "attr.aria-hidden": "current() ? null : \"true\"", "attr.inert": "current() ? null : \"\"", "attr.data-state": "current() ? \"active\" : \"inactive\"", "attr.data-orientation": "ctx.orientation()" } }, exportAs: ["forStepperContent"], ngImport: i0 });
688
+ }
689
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperContent, decorators: [{
690
+ type: Directive,
691
+ args: [{
692
+ selector: '[forStepperContent]',
693
+ exportAs: 'forStepperContent',
694
+ host: {
695
+ '[attr.role]': "ctx.mode() === 'interactive' ? 'tabpanel' : 'group'",
696
+ '[id]': 'id()',
697
+ '[attr.aria-labelledby]': 'labelledBy()',
698
+ '[attr.tabindex]': 'tabindex()',
699
+ '[attr.aria-hidden]': 'current() ? null : "true"',
700
+ '[attr.inert]': 'current() ? null : ""',
701
+ '[attr.data-state]': 'current() ? "active" : "inactive"',
702
+ '[attr.data-orientation]': 'ctx.orientation()',
703
+ },
704
+ }]
705
+ }], ctorParameters: () => [] });
706
+
707
+ /**
708
+ * Terminal "all steps complete" panel. Shown (active) only while the stepper is in
709
+ * the completed state (`selectedIndex` has reached `count`); gated the same way as
710
+ * `[forStepperContent]` — `aria-hidden` / `inert` / `data-state` flip with
711
+ * `isCompleted()`. The natural place for a wizard's final success panel.
712
+ *
713
+ * DOM presence is the consumer's responsibility — wrap with `@if` driven by the same
714
+ * completed condition, or leave it mounted and toggle visibility with CSS via
715
+ * `[data-state="active"]`.
716
+ */
717
+ class ForStepperCompletedContent {
718
+ ctx = injectStepperContext('ForStepperCompletedContent');
719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperCompletedContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
720
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperCompletedContent, isStandalone: true, selector: "[forStepperCompletedContent]", host: { attributes: { "role": "group" }, properties: { "attr.aria-hidden": "ctx.isCompleted() ? null : \"true\"", "attr.inert": "ctx.isCompleted() ? null : \"\"", "attr.data-state": "ctx.isCompleted() ? \"active\" : \"inactive\"" } }, exportAs: ["forStepperCompletedContent"], ngImport: i0 });
721
+ }
722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperCompletedContent, decorators: [{
723
+ type: Directive,
724
+ args: [{
725
+ selector: '[forStepperCompletedContent]',
726
+ exportAs: 'forStepperCompletedContent',
727
+ host: {
728
+ role: 'group',
729
+ '[attr.aria-hidden]': 'ctx.isCompleted() ? null : "true"',
730
+ '[attr.inert]': 'ctx.isCompleted() ? null : ""',
731
+ '[attr.data-state]': 'ctx.isCompleted() ? "active" : "inactive"',
732
+ },
733
+ }]
734
+ }] });
735
+
736
+ /**
737
+ * "Next" button for the stepper. Apply on a `<button>` element. Reflects
738
+ * `aria-disabled="true"` when `canAdvance()` is false (last step reached, root
739
+ * disabled, or in linear mode the current step is not completed / optional).
740
+ *
741
+ * The button retains its native `type="button"` attribute via the `type` host
742
+ * binding to prevent accidental form submission.
743
+ *
744
+ * Clicking while `aria-disabled` is a no-op because `next()` guards internally.
745
+ */
746
+ class ForStepperNext {
747
+ ctx = injectStepperContext('ForStepperNext');
748
+ onClick() {
749
+ this.ctx.next();
750
+ }
751
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperNext, deps: [], target: i0.ɵɵFactoryTarget.Directive });
752
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperNext, isStandalone: true, selector: "button[forStepperNext]", host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "attr.aria-disabled": "!ctx.canAdvance() ? \"true\" : null" } }, exportAs: ["forStepperNext"], ngImport: i0 });
753
+ }
754
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperNext, decorators: [{
755
+ type: Directive,
756
+ args: [{
757
+ selector: 'button[forStepperNext]',
758
+ exportAs: 'forStepperNext',
759
+ host: {
760
+ type: 'button',
761
+ '[attr.aria-disabled]': '!ctx.canAdvance() ? "true" : null',
762
+ '(click)': 'onClick()',
763
+ },
764
+ }]
765
+ }] });
766
+
767
+ /**
768
+ * "Previous" button for the stepper. Apply on a `<button>` element. Reflects
769
+ * `aria-disabled="true"` when `canRetreat()` is false (first step reached or
770
+ * root disabled).
771
+ *
772
+ * The button retains its native `type="button"` attribute via the `type` host
773
+ * binding to prevent accidental form submission.
774
+ *
775
+ * Clicking while `aria-disabled` is a no-op because `previous()` guards
776
+ * internally.
777
+ */
778
+ class ForStepperPrevious {
779
+ ctx = injectStepperContext('ForStepperPrevious');
780
+ onClick() {
781
+ this.ctx.previous();
782
+ }
783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperPrevious, deps: [], target: i0.ɵɵFactoryTarget.Directive });
784
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForStepperPrevious, isStandalone: true, selector: "button[forStepperPrevious]", host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "attr.aria-disabled": "!ctx.canRetreat() ? \"true\" : null" } }, exportAs: ["forStepperPrevious"], ngImport: i0 });
785
+ }
786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperPrevious, decorators: [{
787
+ type: Directive,
788
+ args: [{
789
+ selector: 'button[forStepperPrevious]',
790
+ exportAs: 'forStepperPrevious',
791
+ host: {
792
+ type: 'button',
793
+ '[attr.aria-disabled]': '!ctx.canRetreat() ? "true" : null',
794
+ '(click)': 'onClick()',
795
+ },
796
+ }]
797
+ }] });
798
+
799
+ /**
800
+ * Optional progress indicator for the Stepper. Reads the stepper context and
801
+ * reflects how far through the steps the user is as a WAI-ARIA
802
+ * [`progressbar`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/progressbar_role):
803
+ * `aria-valuemin="0"`, `aria-valuemax="100"`, `aria-valuenow` = the computed
804
+ * percent, and an `aria-valuetext` describing the position. Publishes a
805
+ * `--for-stepper-progress` (0–1) custom property for styling; ships no visuals.
806
+ *
807
+ * Must be used inside a `[forStepper]` element.
808
+ */
809
+ class ForStepperProgress {
810
+ ctx = injectStepperContext('ForStepperProgress');
811
+ /**
812
+ * Basis for the reported percent. `'index'` (default) derives progress from
813
+ * the current step index; `'completed'`
814
+ * derives it from the count of steps whose resolved state is `completed`.
815
+ */
816
+ valueBy = input('index', /* @ts-ignore */
817
+ ...(ngDevMode ? [{ debugName: "valueBy" }] : /* istanbul ignore next */ []));
818
+ /**
819
+ * Accessible name for the progressbar. Defaults to `null`, emitting no
820
+ * `aria-label`; prefer a visible label referenced via `aria-labelledby` when
821
+ * one exists.
822
+ */
823
+ ariaLabel = input(null, /* @ts-ignore */
824
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
825
+ #completedCount = computed(() => {
826
+ const total = this.ctx.count();
827
+ let n = 0;
828
+ for (let i = 0; i < total; i++) {
829
+ if (this.ctx.resolvedStateFor(i) === 'completed') {
830
+ n++;
831
+ }
832
+ }
833
+ return n;
834
+ }, /* @ts-ignore */
835
+ ...(ngDevMode ? [{ debugName: "#completedCount" }] : /* istanbul ignore next */ []));
836
+ #value = computed(() => this.valueBy() === 'completed' ? this.#completedCount() : this.ctx.selectedIndex(), /* @ts-ignore */
837
+ ...(ngDevMode ? [{ debugName: "#value" }] : /* istanbul ignore next */ []));
838
+ /**
839
+ * Progress as a whole-number percent in `[0, 100]`, reflected to
840
+ * `aria-valuenow` and (divided by 100) to the `--for-stepper-progress`
841
+ * custom property.
842
+ */
843
+ percent = computed(() => {
844
+ const total = this.ctx.count();
845
+ if (total <= 1) {
846
+ return total === 1 && this.#value() >= 1 ? 100 : 0;
847
+ }
848
+ const raw = Math.round((this.#value() / (total - 1)) * 100);
849
+ return Math.min(100, Math.max(0, raw));
850
+ }, /* @ts-ignore */
851
+ ...(ngDevMode ? [{ debugName: "percent" }] : /* istanbul ignore next */ []));
852
+ /**
853
+ * Human-readable position string reflected to `aria-valuetext`: `"Step X of N"`
854
+ * on the `'index'` basis, `"P% complete"` on the `'completed'` basis.
855
+ */
856
+ valueText = computed(() => this.valueBy() === 'completed'
857
+ ? `${this.percent()}% complete`
858
+ : `Step ${Math.min(this.ctx.selectedIndex() + 1, this.ctx.count())} of ${this.ctx.count()}`, /* @ts-ignore */
859
+ ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
860
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperProgress, deps: [], target: i0.ɵɵFactoryTarget.Directive });
861
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForStepperProgress, isStandalone: true, selector: "[forStepperProgress]", inputs: { valueBy: { classPropertyName: "valueBy", publicName: "valueBy", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "attr.aria-valuemin": "0", "attr.aria-valuemax": "100", "attr.aria-valuenow": "percent()", "attr.aria-valuetext": "valueText()", "attr.aria-label": "ariaLabel() || null", "attr.data-orientation": "ctx.orientation()", "style.--for-stepper-progress": "percent() / 100" } }, exportAs: ["forStepperProgress"], ngImport: i0 });
862
+ }
863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForStepperProgress, decorators: [{
864
+ type: Directive,
865
+ args: [{
866
+ selector: '[forStepperProgress]',
867
+ exportAs: 'forStepperProgress',
868
+ host: {
869
+ role: 'progressbar',
870
+ '[attr.aria-valuemin]': '0',
871
+ '[attr.aria-valuemax]': '100',
872
+ '[attr.aria-valuenow]': 'percent()',
873
+ '[attr.aria-valuetext]': 'valueText()',
874
+ '[attr.aria-label]': 'ariaLabel() || null',
875
+ '[attr.data-orientation]': 'ctx.orientation()',
876
+ '[style.--for-stepper-progress]': 'percent() / 100',
877
+ },
878
+ }]
879
+ }], propDecorators: { valueBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueBy", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
880
+
881
+ /**
882
+ * Generated bundle index. Do not edit.
883
+ */
884
+
885
+ export { FOR_STEPPER_CONTEXT, FOR_STEPPER_DEFAULTS, FOR_STEPPER_ITEM_CONTEXT, ForStepper, ForStepperCompletedContent, ForStepperContent, ForStepperIndicator, ForStepperItem, ForStepperList, ForStepperNext, ForStepperPrevious, ForStepperProgress, ForStepperSeparator, ForStepperTrigger, provideForStepperDefaults };
886
+ //# sourceMappingURL=forty-cdk-stepper.mjs.map