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,803 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, DOCUMENT, model, input, computed, booleanAttribute, output, signal, DestroyRef, Directive, numberAttribute } from '@angular/core';
3
+ import { createDefaults, FormUiControlBase, injectTextDirection, Collection, injectHiddenInput, snapToStep, registerHandle } from 'forty-cdk/core';
4
+
5
+ const FOR_SLIDER_CONTEXT = new InjectionToken('FOR_SLIDER_CONTEXT');
6
+ function injectSliderContext(piece) {
7
+ const ctx = inject(FOR_SLIDER_CONTEXT, { optional: true });
8
+ if (!ctx) {
9
+ throw new Error(`[forty-cdk/slider] ${piece} must be used inside a [forSlider] element.`);
10
+ }
11
+ return ctx;
12
+ }
13
+
14
+ /**
15
+ * Library fallback for slider defaults, read at the root injector when no
16
+ * consumer has called `provideForSliderDefaults`. Exported for the shared defaults
17
+ * contract spec; not re-exported from the primitive's public entry.
18
+ */
19
+ const FOR_SLIDER_FALLBACK_DEFAULTS = {
20
+ largeStep: 10,
21
+ };
22
+ const { token, provideDefaults } = createDefaults('FOR_SLIDER_DEFAULTS', FOR_SLIDER_FALLBACK_DEFAULTS);
23
+ /** Token holding the resolved slider defaults for the current scope. */
24
+ const FOR_SLIDER_DEFAULTS = token;
25
+ /**
26
+ * Configures forty-cdk slider defaults for this injector scope. Partial
27
+ * overrides inherit unspecified keys from the parent scope (or library
28
+ * defaults at the root).
29
+ */
30
+ function provideForSliderDefaults(defaults = {}) {
31
+ return provideDefaults(defaults);
32
+ }
33
+
34
+ /**
35
+ * Headless implementation of the [WAI-ARIA Slider pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/)
36
+ * (single thumb) and [Slider (Multi-Thumb)](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multi-thumb/)
37
+ * (range / N thumbs). Implements `FormValueControl<readonly number[]>` from
38
+ * `@angular/forms/signals` for `[formField]` auto-wiring.
39
+ *
40
+ * Selection is always modeled as `readonly number[]`:
41
+ * - 1 entry → single-thumb slider.
42
+ * - 2 entries → range slider.
43
+ * - N entries → multi-thumb slider.
44
+ *
45
+ * Values are kept clamped to `[min, max]` and snapped to `step` increments.
46
+ * In multi-thumb mode they're constrained between neighbors so thumbs can't
47
+ * cross (`minStepsBetweenThumbs` forces a minimum gap, in step units).
48
+ *
49
+ * The `model()` change emitter (`(valueChange)`) fires only on internal
50
+ * updates (drag, keyboard, track click), never on consumer writes via
51
+ * `[(value)]` — observe transitions without binding back.
52
+ *
53
+ * For trailing-edge work (network calls, undo entries) bind `(valueCommit)`
54
+ * instead — it fires once at the end of an interaction with the final value
55
+ * array, never per drag step.
56
+ */
57
+ class ForSlider extends FormUiControlBase {
58
+ #host = inject(ElementRef);
59
+ #document = inject(DOCUMENT);
60
+ #defaults = inject(FOR_SLIDER_DEFAULTS);
61
+ /**
62
+ * Two-way bindable. Selected values, one per thumb. Single-thumb sliders
63
+ * keep one entry, range sliders two, multi-thumb N. Values must be sorted
64
+ * ascending for multi-thumb; thumb-by-thumb clamping keeps the order
65
+ * during interaction. Defaults to `[0]`; set an explicit `[(value)]`
66
+ * when `min` is non-zero.
67
+ */
68
+ value = model([0], /* @ts-ignore */
69
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
70
+ /**
71
+ * Minimum value. Falls back to `0` internally. A slider's bounds are scalar
72
+ * by the ARIA slider pattern, but since Signal Forms v22 `FormUiControl.min`
73
+ * is typed `NonNullable<TValue>` — an array for this control — so `min` and
74
+ * `max` are excluded from the `FormValueControl` `implements` clause, and
75
+ * the transform widens the write type to satisfy the `[formField]` template
76
+ * type-check (which pushes `readonly number[] | undefined`). The form never
77
+ * produces a `min`/`max` state for an array-valued field at runtime (the
78
+ * `min()`/`max()` validators only apply to numeric fields), so non-number
79
+ * writes normalize to `undefined`.
80
+ */
81
+ min = input(0, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: (value) => typeof value === 'number' ? value : undefined });
82
+ /**
83
+ * Maximum value. Falls back to `100` internally. Excluded from the
84
+ * `FormValueControl` `implements` clause and write-type-widened — see
85
+ * {@link min}.
86
+ */
87
+ max = input(100, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: (value) => typeof value === 'number' ? value : undefined });
88
+ /**
89
+ * Increment values snap to. Fractional steps (e.g. `0.1`) are supported: the
90
+ * snapped value is rounded to the step's decimal precision so float noise
91
+ * (`0.1 * 3`) can't spuriously emit `valueCommit` or leak into
92
+ * `aria-valuenow`.
93
+ */
94
+ step = input(1, /* @ts-ignore */
95
+ ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
96
+ /**
97
+ * Step used for PageUp / PageDown. Defaults to 10× `step`. The default is
98
+ * read from `provideForSliderDefaults` for the surrounding scope.
99
+ */
100
+ largeStep = input(this.#defaults.largeStep, /* @ts-ignore */
101
+ ...(ngDevMode ? [{ debugName: "largeStep" }] : /* istanbul ignore next */ []));
102
+ /** Effective minimum (defaults `0` when input is unset). Exposed to children via context. */
103
+ minValue = computed(() => this.min() ?? 0, /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "minValue" }] : /* istanbul ignore next */ []));
105
+ /** Effective maximum (defaults `100`). Exposed to children via context. */
106
+ maxValue = computed(() => this.max() ?? 100, /* @ts-ignore */
107
+ ...(ngDevMode ? [{ debugName: "maxValue" }] : /* istanbul ignore next */ []));
108
+ orientation = input('horizontal', /* @ts-ignore */
109
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
110
+ /**
111
+ * Writing direction. When unset (default `null`), the inherited ambient
112
+ * direction is resolved from the nearest ancestor carrying a `dir` attribute
113
+ * (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
114
+ * The resolved value is reflected to the host `dir` attribute and flips the
115
+ * horizontal increase direction in RTL.
116
+ */
117
+ _dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
118
+ dir = injectTextDirection(this._dirInput);
119
+ /**
120
+ * Visual inversion. Flips the mapping between value and screen position
121
+ * (e.g. horizontal LTR with `inverted=true`: max sits on the left).
122
+ * Keyboard semantics still target "toward max" with ArrowUp / ArrowRight
123
+ * (LTR), independent of visual flip.
124
+ */
125
+ inverted = input(false, { ...(ngDevMode ? { debugName: "inverted" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
126
+ /**
127
+ * Multi-thumb only: minimum gap between adjacent thumbs, in step units.
128
+ * Default `0` (touch but never cross — non-passing per APG).
129
+ */
130
+ minStepsBetweenThumbs = input(0, /* @ts-ignore */
131
+ ...(ngDevMode ? [{ debugName: "minStepsBetweenThumbs" }] : /* istanbul ignore next */ []));
132
+ /**
133
+ * Emitted at the end of a value-changing interaction — pointerup /
134
+ * pointercancel after a drag, or keyup after one or more keyboard
135
+ * adjustments — with the final value array. Use it to defer expensive work
136
+ * (network calls,
137
+ * history entries) to the trailing edge of the interaction instead of
138
+ * running it per step. Stays silent when nothing changed (e.g. press +
139
+ * release without movement, or a non-navigation key on a focused thumb).
140
+ */
141
+ valueCommit = output();
142
+ #thumbs = new Collection();
143
+ #trackEl = signal(null, /* @ts-ignore */
144
+ ...(ngDevMode ? [{ debugName: "#trackEl" }] : /* istanbul ignore next */ []));
145
+ #destroyRef = inject(DestroyRef);
146
+ #activeDragCleanups = new Set();
147
+ #interactionMutated = false;
148
+ #armedThumb = null;
149
+ fractions = computed(() => {
150
+ const min = this.minValue();
151
+ const max = this.maxValue();
152
+ const inv = this.inverted();
153
+ const span = max - min;
154
+ if (span <= 0) {
155
+ return this.value().map(() => 0);
156
+ }
157
+ return this.value().map((v) => {
158
+ const f = (v - min) / span;
159
+ const clamped = f < 0 ? 0 : f > 1 ? 1 : f;
160
+ return inv ? 1 - clamped : clamped;
161
+ });
162
+ }, /* @ts-ignore */
163
+ ...(ngDevMode ? [{ debugName: "fractions" }] : /* istanbul ignore next */ []));
164
+ /**
165
+ * Range start fraction. Visual semantics: in single-thumb mode the range
166
+ * always grows from the min edge to the thumb (`0 → fraction[0]`); in
167
+ * multi-thumb mode it spans `min(values) → max(values)`. `inverted` is
168
+ * already baked into the fractions, so consumers can paint blindly.
169
+ */
170
+ rangeStart = computed(() => {
171
+ const fr = this.fractions();
172
+ if (fr.length === 0) {
173
+ return 0;
174
+ }
175
+ if (fr.length === 1) {
176
+ return Math.min(fr[0], this.inverted() ? 1 : 0);
177
+ }
178
+ return Math.min(...fr);
179
+ }, /* @ts-ignore */
180
+ ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
181
+ rangeEnd = computed(() => {
182
+ const fr = this.fractions();
183
+ if (fr.length === 0) {
184
+ return 0;
185
+ }
186
+ if (fr.length === 1) {
187
+ return Math.max(fr[0], this.inverted() ? 1 : 0);
188
+ }
189
+ return Math.max(...fr);
190
+ }, /* @ts-ignore */
191
+ ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
192
+ constructor() {
193
+ super();
194
+ const stringValues = computed(() => this.value().map((v) => String(v)), /* @ts-ignore */
195
+ ...(ngDevMode ? [{ debugName: "stringValues" }] : /* istanbul ignore next */ []));
196
+ injectHiddenInput({
197
+ name: this.name,
198
+ values: stringValues,
199
+ disabled: this.effectiveDisabled,
200
+ });
201
+ this.#destroyRef.onDestroy(() => {
202
+ for (const cleanup of this.#activeDragCleanups) {
203
+ cleanup();
204
+ }
205
+ this.#activeDragCleanups.clear();
206
+ });
207
+ }
208
+ setValueAt(index, raw) {
209
+ if (this.effectiveDisabled() || this.readonly()) {
210
+ return;
211
+ }
212
+ const current = this.value();
213
+ if (index < 0 || index >= current.length) {
214
+ return;
215
+ }
216
+ const next = this.#clampForIndex(current, index, raw);
217
+ if (next === current[index]) {
218
+ return;
219
+ }
220
+ const updated = current.slice();
221
+ updated[index] = next;
222
+ this.value.set(updated);
223
+ this.#interactionMutated = true;
224
+ this.#armedThumb = index;
225
+ }
226
+ bumpAt(index, key, large) {
227
+ if (this.effectiveDisabled() || this.readonly()) {
228
+ return;
229
+ }
230
+ const current = this.value();
231
+ if (index < 0 || index >= current.length) {
232
+ return;
233
+ }
234
+ const direction = this.#directionFor(key);
235
+ if (direction === 0) {
236
+ return;
237
+ }
238
+ const stepUnit = large ? this.largeStep() : this.step();
239
+ const target = current[index] + direction * stepUnit;
240
+ this.setValueAt(index, target);
241
+ }
242
+ setExtreme(index, which) {
243
+ if (this.effectiveDisabled() || this.readonly()) {
244
+ return;
245
+ }
246
+ this.setValueAt(index, which === 'min' ? this.minValue() : this.maxValue());
247
+ }
248
+ pointerToValue(clientX, clientY) {
249
+ const track = this.#trackEl();
250
+ const min = this.minValue();
251
+ const max = this.maxValue();
252
+ if (!track) {
253
+ return min;
254
+ }
255
+ const rect = track.getBoundingClientRect();
256
+ const horizontal = this.orientation() === 'horizontal';
257
+ let fraction;
258
+ if (horizontal) {
259
+ const width = rect.width || 1;
260
+ fraction = (clientX - rect.left) / width;
261
+ if (this.dir() === 'rtl') {
262
+ fraction = 1 - fraction;
263
+ }
264
+ }
265
+ else {
266
+ const height = rect.height || 1;
267
+ // Vertical default: bottom = min, top = max.
268
+ fraction = (rect.bottom - clientY) / height;
269
+ }
270
+ if (this.inverted()) {
271
+ fraction = 1 - fraction;
272
+ }
273
+ fraction = fraction < 0 ? 0 : fraction > 1 ? 1 : fraction;
274
+ return min + fraction * (max - min);
275
+ }
276
+ nearestThumbIndex(target) {
277
+ const values = this.value();
278
+ if (values.length === 0) {
279
+ return -1;
280
+ }
281
+ let bestIndex = 0;
282
+ let bestDelta = Math.abs(values[0] - target);
283
+ let bestValue = values[0];
284
+ for (let i = 1; i < values.length; i++) {
285
+ const value = values[i];
286
+ const delta = Math.abs(value - target);
287
+ if (delta < bestDelta) {
288
+ bestIndex = i;
289
+ bestDelta = delta;
290
+ bestValue = value;
291
+ }
292
+ else if (delta === bestDelta) {
293
+ if (target > bestValue) {
294
+ bestIndex = i;
295
+ bestValue = value;
296
+ }
297
+ }
298
+ }
299
+ return bestIndex;
300
+ }
301
+ beginDrag(index, event) {
302
+ if (this.effectiveDisabled() || this.readonly()) {
303
+ return;
304
+ }
305
+ const pointerId = event.pointerId;
306
+ const move = (e) => {
307
+ if (e.pointerId !== pointerId) {
308
+ return;
309
+ }
310
+ e.preventDefault();
311
+ this.setValueAt(index, this.pointerToValue(e.clientX, e.clientY));
312
+ };
313
+ // Drag tracking listens on the document's defaultView (the window)
314
+ // so the pointer can leave the slider track without losing the drag.
315
+ // Read it through the injected DOCUMENT to stay SSR-friendly — pointer
316
+ // events themselves only fire in the browser, so the optional chain
317
+ // simply makes this a no-op on the server.
318
+ const win = this.#document.defaultView;
319
+ const cleanup = () => {
320
+ win?.removeEventListener('pointermove', move);
321
+ win?.removeEventListener('pointerup', stop);
322
+ win?.removeEventListener('pointercancel', stop);
323
+ this.#activeDragCleanups.delete(cleanup);
324
+ };
325
+ const stop = (e) => {
326
+ if (e.pointerId !== pointerId) {
327
+ return;
328
+ }
329
+ cleanup();
330
+ this.markTouched();
331
+ this.commitInteraction();
332
+ };
333
+ win?.addEventListener('pointermove', move);
334
+ win?.addEventListener('pointerup', stop);
335
+ win?.addEventListener('pointercancel', stop);
336
+ this.#activeDragCleanups.add(cleanup);
337
+ }
338
+ setTrack(el) {
339
+ this.#trackEl.set(el);
340
+ }
341
+ trackElement() {
342
+ return this.#trackEl();
343
+ }
344
+ markTouched() {
345
+ if (!this.touched()) {
346
+ super.markTouched();
347
+ }
348
+ }
349
+ /**
350
+ * Emit `valueCommit` if the running interaction mutated the value at least
351
+ * once, and clear the flag. Pointer drags call this on pointerup with no
352
+ * argument (the drag's own pointerup is already scoped to one thumb). Thumbs
353
+ * call it on keyup of a navigation key, passing their own `thumbIndex` so a
354
+ * keyup on a thumb that did not arm the pending commit (e.g. a second thumb
355
+ * focused and released without moving) cannot mis-attribute or steal another
356
+ * thumb's pending commit — only the thumb that armed it commits.
357
+ */
358
+ commitInteraction(thumbIndex) {
359
+ if (!this.#interactionMutated) {
360
+ return;
361
+ }
362
+ if (thumbIndex !== undefined && this.#armedThumb !== thumbIndex) {
363
+ return;
364
+ }
365
+ this.#interactionMutated = false;
366
+ this.#armedThumb = null;
367
+ this.valueCommit.emit(this.value());
368
+ }
369
+ /**
370
+ * Move focus to a thumb, implementing `FormUiControl.focus` from
371
+ * `@angular/forms/signals`. Without this override Signal Forms would focus
372
+ * the host `role="group"` wrapper — which is not focusable and carries no
373
+ * keyboard map — so focus-on-error would silently go nowhere. Targets the
374
+ * first registered thumb (the roving-tabindex entry point); no-op when the
375
+ * slider is disabled or has no thumbs.
376
+ */
377
+ focus(options) {
378
+ if (this.effectiveDisabled()) {
379
+ return;
380
+ }
381
+ this.#thumbs.items()[0]?.host.focus(options);
382
+ }
383
+ registerThumb(handle) {
384
+ this.#thumbs.register(handle);
385
+ }
386
+ unregisterThumb(handle) {
387
+ this.#thumbs.unregister(handle);
388
+ }
389
+ onFocusOut(event) {
390
+ const next = event.relatedTarget;
391
+ if (next && this.#host.nativeElement.contains(next)) {
392
+ return;
393
+ }
394
+ this.markTouched();
395
+ }
396
+ /** Snap, clamp, and (multi-thumb) keep within neighbor bounds. */
397
+ #clampForIndex(values, index, raw) {
398
+ const min = this.minValue();
399
+ const max = this.maxValue();
400
+ const step = this.step();
401
+ const gap = this.minStepsBetweenThumbs() * step;
402
+ const snapped = snapToStep(raw, step, min);
403
+ let lo = min;
404
+ let hi = max;
405
+ if (index > 0) {
406
+ lo = Math.max(lo, values[index - 1] + gap);
407
+ }
408
+ if (index < values.length - 1) {
409
+ hi = Math.min(hi, values[index + 1] - gap);
410
+ }
411
+ return snapped < lo ? lo : snapped > hi ? hi : snapped;
412
+ }
413
+ /**
414
+ * Returns +1 / -1 / 0 for the keyboard direction's effect on the value,
415
+ * resolving orientation × dir × inverted. Up / Right (LTR) increase by
416
+ * default; `inverted` flips both axes.
417
+ */
418
+ #directionFor(key) {
419
+ let positive;
420
+ switch (key) {
421
+ case 'ArrowUp':
422
+ positive = true;
423
+ break;
424
+ case 'ArrowDown':
425
+ positive = false;
426
+ break;
427
+ case 'ArrowRight':
428
+ positive = this.dir() !== 'rtl';
429
+ break;
430
+ case 'ArrowLeft':
431
+ positive = this.dir() === 'rtl';
432
+ break;
433
+ default:
434
+ return 0;
435
+ }
436
+ if (this.inverted()) {
437
+ positive = !positive;
438
+ }
439
+ return positive ? 1 : -1;
440
+ }
441
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSlider, deps: [], target: i0.ɵɵFactoryTarget.Directive });
442
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForSlider, isStandalone: true, selector: "[forSlider]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, inverted: { classPropertyName: "inverted", publicName: "inverted", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenThumbs: { classPropertyName: "minStepsBetweenThumbs", publicName: "minStepsBetweenThumbs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", valueCommit: "valueCommit" }, host: { attributes: { "role": "group" }, listeners: { "focusout": "onFocusOut($event)" }, properties: { "attr.aria-disabled": "effectiveDisabled() ? \"true\" : null", "attr.aria-readonly": "readonly() ? \"true\" : null", "attr.aria-required": "required() ? \"true\" : null", "attr.aria-invalid": "invalid() ? \"true\" : null", "attr.aria-busy": "pending() ? \"true\" : null", "attr.data-orientation": "orientation()", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.data-readonly": "readonly() ? \"\" : null", "attr.dir": "dir()" } }, providers: [{ provide: FOR_SLIDER_CONTEXT, useExisting: ForSlider }], exportAs: ["forSlider"], usesInheritance: true, ngImport: i0 });
443
+ }
444
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSlider, decorators: [{
445
+ type: Directive,
446
+ args: [{
447
+ selector: '[forSlider]',
448
+ exportAs: 'forSlider',
449
+ host: {
450
+ role: 'group',
451
+ '[attr.aria-disabled]': 'effectiveDisabled() ? "true" : null',
452
+ '[attr.aria-readonly]': 'readonly() ? "true" : null',
453
+ '[attr.aria-required]': 'required() ? "true" : null',
454
+ '[attr.aria-invalid]': 'invalid() ? "true" : null',
455
+ '[attr.aria-busy]': 'pending() ? "true" : null',
456
+ '[attr.data-orientation]': 'orientation()',
457
+ '[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
458
+ '[attr.data-readonly]': 'readonly() ? "" : null',
459
+ '[attr.dir]': 'dir()',
460
+ '(focusout)': 'onFocusOut($event)',
461
+ },
462
+ providers: [{ provide: FOR_SLIDER_CONTEXT, useExisting: ForSlider }],
463
+ }]
464
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], inverted: [{ type: i0.Input, args: [{ isSignal: true, alias: "inverted", required: false }] }], minStepsBetweenThumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "minStepsBetweenThumbs", required: false }] }], valueCommit: [{ type: i0.Output, args: ["valueCommit"] }] } });
465
+
466
+ /**
467
+ * The clickable track surface. PointerDown anywhere on the track that isn't
468
+ * a thumb finds the nearest thumb, jumps it to the clicked position, focuses
469
+ * it, and starts a drag (so the user can keep refining without releasing).
470
+ *
471
+ * Thumbs handle their own pointerdown, so a click directly on a thumb
472
+ * skips the "nearest" lookup and drags that specific thumb.
473
+ *
474
+ * Reflects `data-orientation` and `data-disabled` so consumers can paint
475
+ * the track from CSS. Position the visible track however you want — this
476
+ * directive just owns the bounding-rect for the math.
477
+ */
478
+ class ForSliderTrack {
479
+ ctx = injectSliderContext('ForSliderTrack');
480
+ #host = inject(ElementRef);
481
+ constructor() {
482
+ this.ctx.setTrack(this.#host.nativeElement);
483
+ inject(DestroyRef).onDestroy(() => this.ctx.setTrack(null));
484
+ }
485
+ onPointerDown(event) {
486
+ if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
487
+ return;
488
+ }
489
+ // Only react to primary button / touch / pen contact.
490
+ if (event.button !== undefined && event.button !== 0) {
491
+ return;
492
+ }
493
+ // Direct thumb clicks are handled by the thumb's own listener; the thumb
494
+ // calls `stopPropagation()` so we don't double-handle.
495
+ event.preventDefault();
496
+ const target = this.ctx.pointerToValue(event.clientX, event.clientY);
497
+ const index = this.ctx.nearestThumbIndex(target);
498
+ if (index < 0) {
499
+ return;
500
+ }
501
+ this.ctx.setValueAt(index, target);
502
+ this.ctx.beginDrag(index, event);
503
+ }
504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderTrack, deps: [], target: i0.ɵɵFactoryTarget.Directive });
505
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForSliderTrack, isStandalone: true, selector: "[forSliderTrack]", host: { listeners: { "pointerdown": "onPointerDown($event)" }, properties: { "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null" } }, exportAs: ["forSliderTrack"], ngImport: i0 });
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderTrack, decorators: [{
508
+ type: Directive,
509
+ args: [{
510
+ selector: '[forSliderTrack]',
511
+ exportAs: 'forSliderTrack',
512
+ host: {
513
+ '[attr.data-orientation]': 'ctx.orientation()',
514
+ '[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
515
+ '(pointerdown)': 'onPointerDown($event)',
516
+ },
517
+ }]
518
+ }], ctorParameters: () => [] });
519
+
520
+ /**
521
+ * Optional decorative band between the slider's lowest and highest thumbs
522
+ * (or `0 → thumb` for single-thumb sliders). The directive doesn't paint
523
+ * anything — it exposes start / end fractions as CSS variables and `data-*`
524
+ * so the consumer can size and position it from styles.
525
+ *
526
+ * Custom properties on the host:
527
+ * - `--for-slider-range-start` — fraction `[0, 1]`.
528
+ * - `--for-slider-range-end` — fraction `[0, 1]`.
529
+ * - `--for-slider-range-size` — `end - start`, useful for `width` / `height`.
530
+ *
531
+ * The fractions already account for `inverted`, so painting can be blind.
532
+ * Combine with `data-orientation` to pick the right CSS axis.
533
+ */
534
+ class ForSliderRange {
535
+ ctx = injectSliderContext('ForSliderRange');
536
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderRange, deps: [], target: i0.ɵɵFactoryTarget.Directive });
537
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForSliderRange, isStandalone: true, selector: "[forSliderRange]", host: { properties: { "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null", "style.--for-slider-range-start": "ctx.rangeStart()", "style.--for-slider-range-end": "ctx.rangeEnd()", "style.--for-slider-range-size": "ctx.rangeEnd() - ctx.rangeStart()" } }, exportAs: ["forSliderRange"], ngImport: i0 });
538
+ }
539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderRange, decorators: [{
540
+ type: Directive,
541
+ args: [{
542
+ selector: '[forSliderRange]',
543
+ exportAs: 'forSliderRange',
544
+ host: {
545
+ '[attr.data-orientation]': 'ctx.orientation()',
546
+ '[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
547
+ '[style.--for-slider-range-start]': 'ctx.rangeStart()',
548
+ '[style.--for-slider-range-end]': 'ctx.rangeEnd()',
549
+ '[style.--for-slider-range-size]': 'ctx.rangeEnd() - ctx.rangeStart()',
550
+ },
551
+ }]
552
+ }] });
553
+
554
+ /**
555
+ * One slider thumb. Apply on any focusable element (commonly `<span>` /
556
+ * `<div>` with the directive's auto-injected `tabindex`). The directive
557
+ * sets `role="slider"`, the live `aria-value*` attributes, keyboard
558
+ * handling, and starts a drag on pointerdown.
559
+ *
560
+ * `[index]` is the 0-based position in the parent slider's `value()` array
561
+ * — pass the loop index when rendering N thumbs:
562
+ *
563
+ * ```html
564
+ * @for (v of value(); let i = $index; track i) {
565
+ * <span forSliderThumb [index]="i" [label]="i === 0 ? 'Min' : 'Max'"></span>
566
+ * }
567
+ * ```
568
+ *
569
+ * Custom property exposed:
570
+ * - `--for-slider-thumb-position` — fraction `[0, 1]` already accounting
571
+ * for `inverted`. Use it via e.g.
572
+ * `inset-inline-start: calc(var(--for-slider-thumb-position) * 100%)`.
573
+ */
574
+ class ForSliderThumb {
575
+ ctx = injectSliderContext('ForSliderThumb');
576
+ #host = inject(ElementRef);
577
+ /** Position in the parent slider's `value()` array (0-based). */
578
+ index = input.required({ ...(ngDevMode ? { debugName: "index" } : /* istanbul ignore next */ {}), transform: numberAttribute });
579
+ /**
580
+ * Optional fixed label for assistive tech (e.g. "Minimum price",
581
+ * "Maximum price"). Mirrored as `aria-label`. Use `[labelledby]`
582
+ * instead if the label lives elsewhere in the DOM.
583
+ */
584
+ label = input('', /* @ts-ignore */
585
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
586
+ labelledby = input('', /* @ts-ignore */
587
+ ...(ngDevMode ? [{ debugName: "labelledby" }] : /* istanbul ignore next */ []));
588
+ /**
589
+ * Optional human-readable value override (e.g. "$1,200" instead of "1200").
590
+ * Mirrored as `aria-valuetext` only when non-empty; when omitted, no
591
+ * `aria-valuetext` is emitted so assistive tech reads `aria-valuenow`.
592
+ */
593
+ valueText = input('', /* @ts-ignore */
594
+ ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
595
+ tabindex = computed(() => (this.ctx.effectiveDisabled() ? -1 : 0), /* @ts-ignore */
596
+ ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
597
+ currentValue = computed(() => {
598
+ const i = this.index();
599
+ const values = this.ctx.value();
600
+ if (i < 0 || i >= values.length) {
601
+ return this.ctx.minValue();
602
+ }
603
+ return values[i];
604
+ }, /* @ts-ignore */
605
+ ...(ngDevMode ? [{ debugName: "currentValue" }] : /* istanbul ignore next */ []));
606
+ fraction = computed(() => {
607
+ const i = this.index();
608
+ const fr = this.ctx.fractions();
609
+ if (i < 0 || i >= fr.length) {
610
+ return 0;
611
+ }
612
+ return fr[i];
613
+ }, /* @ts-ignore */
614
+ ...(ngDevMode ? [{ debugName: "fraction" }] : /* istanbul ignore next */ []));
615
+ ariaValueNow = computed(() => this.currentValue(), /* @ts-ignore */
616
+ ...(ngDevMode ? [{ debugName: "ariaValueNow" }] : /* istanbul ignore next */ []));
617
+ ariaValueText = computed(() => this.valueText() || null, /* @ts-ignore */
618
+ ...(ngDevMode ? [{ debugName: "ariaValueText" }] : /* istanbul ignore next */ []));
619
+ ariaValueMin = computed(() => {
620
+ const i = this.index();
621
+ const values = this.ctx.value();
622
+ if (i > 0 && i < values.length) {
623
+ // Multi-thumb non-passing: this thumb's lower bound is the previous
624
+ // thumb's value (matches APG multi-thumb guidance).
625
+ return values[i - 1];
626
+ }
627
+ return this.ctx.minValue();
628
+ }, /* @ts-ignore */
629
+ ...(ngDevMode ? [{ debugName: "ariaValueMin" }] : /* istanbul ignore next */ []));
630
+ ariaValueMax = computed(() => {
631
+ const i = this.index();
632
+ const values = this.ctx.value();
633
+ if (i >= 0 && i < values.length - 1) {
634
+ return values[i + 1];
635
+ }
636
+ return this.ctx.maxValue();
637
+ }, /* @ts-ignore */
638
+ ...(ngDevMode ? [{ debugName: "ariaValueMax" }] : /* istanbul ignore next */ []));
639
+ constructor() {
640
+ const handle = {
641
+ host: this.#host.nativeElement,
642
+ index: this.index,
643
+ };
644
+ registerHandle(handle, (h) => this.ctx.registerThumb(h), (h) => this.ctx.unregisterThumb(h));
645
+ }
646
+ onKeyDown(event) {
647
+ if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
648
+ return;
649
+ }
650
+ switch (event.key) {
651
+ case 'ArrowUp':
652
+ case 'ArrowDown':
653
+ case 'ArrowLeft':
654
+ case 'ArrowRight':
655
+ event.preventDefault();
656
+ this.ctx.bumpAt(this.index(), event.key, false);
657
+ return;
658
+ case 'PageUp':
659
+ event.preventDefault();
660
+ this.ctx.bumpAt(this.index(), 'ArrowUp', true);
661
+ return;
662
+ case 'PageDown':
663
+ event.preventDefault();
664
+ this.ctx.bumpAt(this.index(), 'ArrowDown', true);
665
+ return;
666
+ case 'Home':
667
+ event.preventDefault();
668
+ this.ctx.setExtreme(this.index(), 'min');
669
+ return;
670
+ case 'End':
671
+ event.preventDefault();
672
+ this.ctx.setExtreme(this.index(), 'max');
673
+ return;
674
+ default:
675
+ return;
676
+ }
677
+ }
678
+ onKeyUp(event) {
679
+ switch (event.key) {
680
+ case 'ArrowUp':
681
+ case 'ArrowDown':
682
+ case 'ArrowLeft':
683
+ case 'ArrowRight':
684
+ case 'PageUp':
685
+ case 'PageDown':
686
+ case 'Home':
687
+ case 'End':
688
+ this.ctx.commitInteraction(this.index());
689
+ return;
690
+ default:
691
+ return;
692
+ }
693
+ }
694
+ onPointerDown(event) {
695
+ if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
696
+ return;
697
+ }
698
+ if (event.button !== undefined && event.button !== 0) {
699
+ return;
700
+ }
701
+ // Stop the track's pointerdown handler from re-running "nearest thumb"
702
+ // — clicking directly on a thumb should drag *that* thumb.
703
+ event.stopPropagation();
704
+ event.preventDefault();
705
+ this.#host.nativeElement.focus();
706
+ this.ctx.beginDrag(this.index(), event);
707
+ }
708
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderThumb, deps: [], target: i0.ɵɵFactoryTarget.Directive });
709
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForSliderThumb, isStandalone: true, selector: "[forSliderThumb]", inputs: { index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelledby: { classPropertyName: "labelledby", publicName: "labelledby", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "slider" }, listeners: { "keydown": "onKeyDown($event)", "keyup": "onKeyUp($event)", "pointerdown": "onPointerDown($event)" }, properties: { "attr.tabindex": "tabindex()", "attr.aria-valuemin": "ariaValueMin()", "attr.aria-valuemax": "ariaValueMax()", "attr.aria-valuenow": "ariaValueNow()", "attr.aria-valuetext": "ariaValueText()", "attr.aria-orientation": "ctx.orientation()", "attr.aria-label": "label() || null", "attr.aria-labelledby": "labelledby() || null", "attr.aria-disabled": "ctx.effectiveDisabled() ? \"true\" : null", "attr.aria-readonly": "ctx.readonly() ? \"true\" : null", "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null", "attr.data-readonly": "ctx.readonly() ? \"\" : null", "attr.data-index": "index()", "style.--for-slider-thumb-position": "fraction()" } }, exportAs: ["forSliderThumb"], ngImport: i0 });
710
+ }
711
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderThumb, decorators: [{
712
+ type: Directive,
713
+ args: [{
714
+ selector: '[forSliderThumb]',
715
+ exportAs: 'forSliderThumb',
716
+ host: {
717
+ role: 'slider',
718
+ '[attr.tabindex]': 'tabindex()',
719
+ '[attr.aria-valuemin]': 'ariaValueMin()',
720
+ '[attr.aria-valuemax]': 'ariaValueMax()',
721
+ '[attr.aria-valuenow]': 'ariaValueNow()',
722
+ '[attr.aria-valuetext]': 'ariaValueText()',
723
+ '[attr.aria-orientation]': 'ctx.orientation()',
724
+ '[attr.aria-label]': 'label() || null',
725
+ '[attr.aria-labelledby]': 'labelledby() || null',
726
+ '[attr.aria-disabled]': 'ctx.effectiveDisabled() ? "true" : null',
727
+ '[attr.aria-readonly]': 'ctx.readonly() ? "true" : null',
728
+ '[attr.data-orientation]': 'ctx.orientation()',
729
+ '[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
730
+ '[attr.data-readonly]': 'ctx.readonly() ? "" : null',
731
+ '[attr.data-index]': 'index()',
732
+ '[style.--for-slider-thumb-position]': 'fraction()',
733
+ '(keydown)': 'onKeyDown($event)',
734
+ '(keyup)': 'onKeyUp($event)',
735
+ '(pointerdown)': 'onPointerDown($event)',
736
+ },
737
+ }]
738
+ }], ctorParameters: () => [], propDecorators: { index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelledby", required: false }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: false }] }] } });
739
+
740
+ /**
741
+ * Exact public names of every `ForSlider` input, its models included. Spread it into the
742
+ * `inputs` array of a `hostDirectives` entry so a wrapper component re-exposes the
743
+ * primitive's full surface — the Signal Forms members `[formField]` binds among them —
744
+ * without hand-maintaining the list. Always spread into an inline object literal as shown
745
+ * below: the literal is what keeps the entry statically analyzable for consumers compiling
746
+ * against the published package. An anti-drift spec fails when this list no longer matches
747
+ * the directive's actual API. See `docs/wrapping-form-primitives.md` for both supported
748
+ * wrapping patterns.
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * @Component({
753
+ * selector: 'div[mySlider]',
754
+ * template: '',
755
+ * hostDirectives: [
756
+ * {
757
+ * directive: ForSlider,
758
+ * inputs: [...FOR_SLIDER_HOST_DIRECTIVE_INPUTS],
759
+ * outputs: [...FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS],
760
+ * },
761
+ * ],
762
+ * })
763
+ * export class MySlider {}
764
+ * ```
765
+ */
766
+ const FOR_SLIDER_HOST_DIRECTIVE_INPUTS = [
767
+ 'value',
768
+ 'dir',
769
+ 'dirty',
770
+ 'disabled',
771
+ 'errors',
772
+ 'invalid',
773
+ 'inverted',
774
+ 'largeStep',
775
+ 'max',
776
+ 'min',
777
+ 'minStepsBetweenThumbs',
778
+ 'name',
779
+ 'orientation',
780
+ 'pending',
781
+ 'readonly',
782
+ 'required',
783
+ 'step',
784
+ 'touched',
785
+ ];
786
+ /**
787
+ * Exact public names of every `ForSlider` output, the Signal Forms `touch` output
788
+ * included. Spread it into the `outputs` array of the same `hostDirectives` entry as
789
+ * {@link FOR_SLIDER_HOST_DIRECTIVE_INPUTS}.
790
+ */
791
+ const FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS = [
792
+ 'valueChange',
793
+ 'valueCommit',
794
+ 'touchedChange',
795
+ 'touch',
796
+ ];
797
+
798
+ /**
799
+ * Generated bundle index. Do not edit.
800
+ */
801
+
802
+ export { FOR_SLIDER_CONTEXT, FOR_SLIDER_DEFAULTS, FOR_SLIDER_HOST_DIRECTIVE_INPUTS, FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS, ForSlider, ForSliderRange, ForSliderThumb, ForSliderTrack, provideForSliderDefaults };
803
+ //# sourceMappingURL=forty-cdk-slider.mjs.map