@cdevhub/ngx-tw 0.2.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/fesm2022/cdevhub-ngx-tw-accordion.mjs +108 -0
  4. package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -0
  5. package/fesm2022/cdevhub-ngx-tw-alert.mjs +240 -0
  6. package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -0
  7. package/fesm2022/cdevhub-ngx-tw-avatar.mjs +287 -0
  8. package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -0
  9. package/fesm2022/cdevhub-ngx-tw-badge.mjs +332 -0
  10. package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -0
  11. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +452 -0
  12. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -0
  13. package/fesm2022/cdevhub-ngx-tw-button.mjs +227 -0
  14. package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -0
  15. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +417 -0
  16. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -0
  17. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +232 -0
  18. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -0
  19. package/fesm2022/cdevhub-ngx-tw-calendar.mjs +4482 -0
  20. package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -0
  21. package/fesm2022/cdevhub-ngx-tw-card.mjs +157 -0
  22. package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -0
  23. package/fesm2022/cdevhub-ngx-tw-carousel.mjs +1454 -0
  24. package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -0
  25. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +593 -0
  26. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -0
  27. package/fesm2022/cdevhub-ngx-tw-code-block.mjs +228 -0
  28. package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -0
  29. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +510 -0
  30. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -0
  31. package/fesm2022/cdevhub-ngx-tw-combobox.mjs +1721 -0
  32. package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -0
  33. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +721 -0
  34. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -0
  35. package/fesm2022/cdevhub-ngx-tw-core.mjs +861 -0
  36. package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -0
  37. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +1558 -0
  38. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -0
  39. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +1833 -0
  40. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -0
  41. package/fesm2022/cdevhub-ngx-tw-dialog.mjs +693 -0
  42. package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -0
  43. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +331 -0
  44. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -0
  45. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +289 -0
  46. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -0
  47. package/fesm2022/cdevhub-ngx-tw-form-field.mjs +572 -0
  48. package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -0
  49. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +36 -0
  50. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -0
  51. package/fesm2022/cdevhub-ngx-tw-icon.mjs +278 -0
  52. package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -0
  53. package/fesm2022/cdevhub-ngx-tw-input.mjs +355 -0
  54. package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -0
  55. package/fesm2022/cdevhub-ngx-tw-item.mjs +241 -0
  56. package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -0
  57. package/fesm2022/cdevhub-ngx-tw-menu.mjs +433 -0
  58. package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -0
  59. package/fesm2022/cdevhub-ngx-tw-paginator.mjs +691 -0
  60. package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -0
  61. package/fesm2022/cdevhub-ngx-tw-popover.mjs +678 -0
  62. package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -0
  63. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +335 -0
  64. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -0
  65. package/fesm2022/cdevhub-ngx-tw-radio.mjs +708 -0
  66. package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -0
  67. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +319 -0
  68. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -0
  69. package/fesm2022/cdevhub-ngx-tw-select.mjs +1722 -0
  70. package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -0
  71. package/fesm2022/cdevhub-ngx-tw-separator.mjs +115 -0
  72. package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -0
  73. package/fesm2022/cdevhub-ngx-tw-sheet.mjs +768 -0
  74. package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -0
  75. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +149 -0
  76. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -0
  77. package/fesm2022/cdevhub-ngx-tw-slider.mjs +1147 -0
  78. package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -0
  79. package/fesm2022/cdevhub-ngx-tw-sort.mjs +346 -0
  80. package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -0
  81. package/fesm2022/cdevhub-ngx-tw-spinner.mjs +186 -0
  82. package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -0
  83. package/fesm2022/cdevhub-ngx-tw-split.mjs +1082 -0
  84. package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -0
  85. package/fesm2022/cdevhub-ngx-tw-stat.mjs +579 -0
  86. package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -0
  87. package/fesm2022/cdevhub-ngx-tw-stepper.mjs +443 -0
  88. package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -0
  89. package/fesm2022/cdevhub-ngx-tw-switch.mjs +391 -0
  90. package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -0
  91. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +315 -0
  92. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -0
  93. package/fesm2022/cdevhub-ngx-tw-table.mjs +994 -0
  94. package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -0
  95. package/fesm2022/cdevhub-ngx-tw-tabs.mjs +508 -0
  96. package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -0
  97. package/fesm2022/cdevhub-ngx-tw-textarea.mjs +147 -0
  98. package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -0
  99. package/fesm2022/cdevhub-ngx-tw-theme.mjs +178 -0
  100. package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -0
  101. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +1344 -0
  102. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -0
  103. package/fesm2022/cdevhub-ngx-tw-timeline.mjs +1136 -0
  104. package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -0
  105. package/fesm2022/cdevhub-ngx-tw-toast.mjs +1311 -0
  106. package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -0
  107. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +533 -0
  108. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -0
  109. package/fesm2022/cdevhub-ngx-tw.mjs +61 -0
  110. package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -0
  111. package/package.json +265 -0
  112. package/theme/_base.css +394 -0
  113. package/theme/_dark.css +510 -0
  114. package/theme/_high-contrast.css +290 -0
  115. package/theme/_semantic.css +317 -0
  116. package/theme/_typography.css +9 -0
  117. package/theme/index.css +54 -0
  118. package/types/cdevhub-ngx-tw-accordion.d.ts +48 -0
  119. package/types/cdevhub-ngx-tw-alert.d.ts +67 -0
  120. package/types/cdevhub-ngx-tw-avatar.d.ts +79 -0
  121. package/types/cdevhub-ngx-tw-badge.d.ts +94 -0
  122. package/types/cdevhub-ngx-tw-breadcrumbs.d.ts +227 -0
  123. package/types/cdevhub-ngx-tw-button.d.ts +74 -0
  124. package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +200 -0
  125. package/types/cdevhub-ngx-tw-calendar-testing.d.ts +113 -0
  126. package/types/cdevhub-ngx-tw-calendar.d.ts +1704 -0
  127. package/types/cdevhub-ngx-tw-card.d.ts +48 -0
  128. package/types/cdevhub-ngx-tw-carousel.d.ts +406 -0
  129. package/types/cdevhub-ngx-tw-checkbox.d.ts +136 -0
  130. package/types/cdevhub-ngx-tw-code-block.d.ts +93 -0
  131. package/types/cdevhub-ngx-tw-collapsible.d.ts +151 -0
  132. package/types/cdevhub-ngx-tw-combobox.d.ts +389 -0
  133. package/types/cdevhub-ngx-tw-command-palette.d.ts +215 -0
  134. package/types/cdevhub-ngx-tw-core.d.ts +719 -0
  135. package/types/cdevhub-ngx-tw-date-picker.d.ts +317 -0
  136. package/types/cdevhub-ngx-tw-date-range-picker.d.ts +313 -0
  137. package/types/cdevhub-ngx-tw-dialog.d.ts +303 -0
  138. package/types/cdevhub-ngx-tw-empty-state.d.ts +104 -0
  139. package/types/cdevhub-ngx-tw-flip-card.d.ts +95 -0
  140. package/types/cdevhub-ngx-tw-form-field.d.ts +210 -0
  141. package/types/cdevhub-ngx-tw-icon-lucide.d.ts +29 -0
  142. package/types/cdevhub-ngx-tw-icon.d.ts +134 -0
  143. package/types/cdevhub-ngx-tw-input.d.ts +110 -0
  144. package/types/cdevhub-ngx-tw-item.d.ts +70 -0
  145. package/types/cdevhub-ngx-tw-menu.d.ts +99 -0
  146. package/types/cdevhub-ngx-tw-paginator.d.ts +305 -0
  147. package/types/cdevhub-ngx-tw-popover.d.ts +157 -0
  148. package/types/cdevhub-ngx-tw-progress-bar.d.ts +100 -0
  149. package/types/cdevhub-ngx-tw-radio.d.ts +174 -0
  150. package/types/cdevhub-ngx-tw-segmented-control.d.ts +66 -0
  151. package/types/cdevhub-ngx-tw-select.d.ts +384 -0
  152. package/types/cdevhub-ngx-tw-separator.d.ts +28 -0
  153. package/types/cdevhub-ngx-tw-sheet.d.ts +335 -0
  154. package/types/cdevhub-ngx-tw-skeleton.d.ts +52 -0
  155. package/types/cdevhub-ngx-tw-slider.d.ts +258 -0
  156. package/types/cdevhub-ngx-tw-sort.d.ts +136 -0
  157. package/types/cdevhub-ngx-tw-spinner.d.ts +50 -0
  158. package/types/cdevhub-ngx-tw-split.d.ts +265 -0
  159. package/types/cdevhub-ngx-tw-stat.d.ts +140 -0
  160. package/types/cdevhub-ngx-tw-stepper.d.ts +127 -0
  161. package/types/cdevhub-ngx-tw-switch.d.ts +88 -0
  162. package/types/cdevhub-ngx-tw-tab-nav.d.ts +107 -0
  163. package/types/cdevhub-ngx-tw-table.d.ts +479 -0
  164. package/types/cdevhub-ngx-tw-tabs.d.ts +121 -0
  165. package/types/cdevhub-ngx-tw-textarea.d.ts +62 -0
  166. package/types/cdevhub-ngx-tw-theme.d.ts +106 -0
  167. package/types/cdevhub-ngx-tw-time-picker.d.ts +303 -0
  168. package/types/cdevhub-ngx-tw-timeline.d.ts +278 -0
  169. package/types/cdevhub-ngx-tw-toast.d.ts +388 -0
  170. package/types/cdevhub-ngx-tw-tooltip.d.ts +95 -0
  171. package/types/cdevhub-ngx-tw.d.ts +49 -0
@@ -0,0 +1,1147 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, model, output, inject, ElementRef, DestroyRef, signal, computed, viewChild, linkedSignal, afterNextRender, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
5
+ import { FocusMonitor } from '@angular/cdk/a11y';
6
+ import { Directionality } from '@angular/cdk/bidi';
7
+ import { merge } from 'rxjs';
8
+ import { tv } from 'tailwind-variants';
9
+ import { TW_ERROR_STATE_MATCHER } from '@cdevhub/ngx-tw/core';
10
+
11
+ /**
12
+ * `tw-slider` — select a single value or a contiguous range from a numeric scale.
13
+ *
14
+ * Composition usecases:
15
+ * - Single-value control bound via `[(value)]`.
16
+ * - Range selector via `[range]="true"` with a `[number, number]` value.
17
+ * - Quantised scale via `[step]` and optional tick `[marks]`.
18
+ * - Custom mark labels for semantic scales (temperature, volume, brightness).
19
+ * - Forms: works with template-driven, reactive, and signal forms through `ControlValueAccessor`.
20
+ */
21
+ // ── Static color lookups (Tailwind v4 scans class strings statically) ──
22
+ const FILL_SOLID = {
23
+ primary: 'bg-primary-500',
24
+ secondary: 'bg-secondary-500',
25
+ accent: 'bg-accent-500',
26
+ neutral: 'bg-fg',
27
+ info: 'bg-info-500',
28
+ success: 'bg-success-500',
29
+ warning: 'bg-warning-500',
30
+ error: 'bg-error-500',
31
+ };
32
+ const FILL_SOFT = {
33
+ primary: 'bg-primary-300',
34
+ secondary: 'bg-secondary-300',
35
+ accent: 'bg-accent-300',
36
+ neutral: 'bg-fg-muted',
37
+ info: 'bg-info-300',
38
+ success: 'bg-success-300',
39
+ warning: 'bg-warning-300',
40
+ error: 'bg-error-300',
41
+ };
42
+ const FILL_OUTLINE_BORDER = {
43
+ primary: 'border-primary-500',
44
+ secondary: 'border-secondary-500',
45
+ accent: 'border-accent-500',
46
+ neutral: 'border-fg',
47
+ info: 'border-info-500',
48
+ success: 'border-success-500',
49
+ warning: 'border-warning-500',
50
+ error: 'border-error-500',
51
+ };
52
+ const THUMB_BORDER = {
53
+ primary: 'border-primary-500',
54
+ secondary: 'border-secondary-500',
55
+ accent: 'border-accent-500',
56
+ neutral: 'border-fg',
57
+ info: 'border-info-500',
58
+ success: 'border-success-500',
59
+ warning: 'border-warning-500',
60
+ error: 'border-error-500',
61
+ };
62
+ const FOCUS_RING = {
63
+ primary: 'focus-visible:outline-primary-500',
64
+ secondary: 'focus-visible:outline-secondary-500',
65
+ accent: 'focus-visible:outline-accent-500',
66
+ neutral: 'focus-visible:outline-fg',
67
+ info: 'focus-visible:outline-info-500',
68
+ success: 'focus-visible:outline-success-500',
69
+ warning: 'focus-visible:outline-warning-500',
70
+ error: 'focus-visible:outline-error-500',
71
+ };
72
+ // ── tv() config ──────────────────────────────────────────────────
73
+ const sliderVariants = tv({
74
+ slots: {
75
+ root: 'flex flex-col w-full select-none',
76
+ header: 'flex items-center justify-between gap-3',
77
+ label: 'text-sm font-medium text-fg empty:hidden',
78
+ valueText: 'text-sm text-fg-muted font-medium tabular-nums',
79
+ description: 'text-xs text-fg-muted empty:hidden',
80
+ region: 'relative flex items-center touch-none',
81
+ rail: 'relative w-full rounded-full bg-surface-muted transition-colors duration-normal motion-reduce:transition-none',
82
+ fill: 'absolute inset-y-0 rounded-full transition-colors duration-normal motion-reduce:transition-none',
83
+ marksRow: 'absolute inset-0 pointer-events-none',
84
+ mark: 'absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-surface transition-colors duration-normal motion-reduce:transition-none',
85
+ markActive: '',
86
+ thumb: 'absolute top-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full border-2 bg-surface-raised shadow-sm cursor-grab active:cursor-grabbing touch-none focus-visible:outline-2 focus-visible:outline-offset-2 transition-[transform,box-shadow] duration-fast motion-reduce:transition-none hover:shadow-md data-[dragging=true]:shadow-md data-[dragging=true]:scale-110',
87
+ bubble: 'absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-0.5 rounded-md bg-fg text-surface text-xs font-medium tabular-nums whitespace-nowrap pointer-events-none opacity-0 transition-opacity duration-fast motion-reduce:transition-none',
88
+ bubbleVisible: 'opacity-100',
89
+ markLabelsRow: 'relative w-full mt-2',
90
+ markLabel: 'absolute top-0 -translate-x-1/2 text-xs text-fg-muted tabular-nums whitespace-nowrap',
91
+ minMaxRow: 'flex items-center justify-between mt-2 text-xs text-fg-muted tabular-nums',
92
+ },
93
+ variants: {
94
+ size: {
95
+ // Thumb sizes follow the square-interactive scale (WCAG 2.2 SC 2.5.8 target-size).
96
+ // Rail/mark/region adjust independently for proportional visual weight.
97
+ xs: {
98
+ rail: 'h-1',
99
+ thumb: 'size-6',
100
+ mark: 'size-2',
101
+ region: 'h-6 py-1',
102
+ },
103
+ sm: {
104
+ rail: 'h-1.5',
105
+ thumb: 'size-7',
106
+ mark: 'size-2.5',
107
+ region: 'h-7 py-1',
108
+ },
109
+ md: {
110
+ rail: 'h-2',
111
+ thumb: 'size-8',
112
+ mark: 'size-3',
113
+ region: 'h-8 py-1.5',
114
+ },
115
+ lg: {
116
+ rail: 'h-2.5',
117
+ thumb: 'size-9',
118
+ mark: 'size-3',
119
+ region: 'h-9 py-2',
120
+ },
121
+ xl: {
122
+ rail: 'h-3',
123
+ thumb: 'size-10',
124
+ mark: 'size-3',
125
+ region: 'h-10 py-2',
126
+ },
127
+ },
128
+ variant: {
129
+ solid: {},
130
+ soft: {},
131
+ outline: {
132
+ rail: 'border border-border bg-transparent',
133
+ fill: 'border border-transparent',
134
+ },
135
+ },
136
+ disabled: {
137
+ true: { root: 'opacity-50 pointer-events-none' },
138
+ false: { root: '' },
139
+ },
140
+ hasHeader: {
141
+ true: { root: 'gap-2' },
142
+ false: { root: 'gap-0' },
143
+ },
144
+ },
145
+ defaultVariants: {
146
+ size: 'md',
147
+ variant: 'solid',
148
+ disabled: false,
149
+ hasHeader: false,
150
+ },
151
+ }, { twMerge: true });
152
+ let nextId = 0;
153
+ const DEFAULT_FORMATTER = (value) => {
154
+ if (Number.isInteger(value))
155
+ return String(value);
156
+ return String(Math.round(value * 100) / 100);
157
+ };
158
+ const clamp = (value, lo, hi) => value < lo ? lo : value > hi ? hi : value;
159
+ const toRange = (value) => {
160
+ if (Array.isArray(value))
161
+ return [value[0], value[1]];
162
+ return [0, typeof value === 'number' ? value : 0];
163
+ };
164
+ const toNumber = (value, fallback) => {
165
+ if (Array.isArray(value))
166
+ return value[1];
167
+ return typeof value === 'number' ? value : fallback;
168
+ };
169
+ /**
170
+ * Select a single value or a numeric range from a scale.
171
+ *
172
+ * @example
173
+ * ```html
174
+ * <tw-slider [(value)]="volume" [min]="0" [max]="100" [step]="5" />
175
+ * <tw-slider range [(value)]="priceRange" [min]="0" [max]="1000" [step]="10" showMinMax />
176
+ * <tw-slider [marks]="true" [step]="25" showMarkLabels label="Brightness" />
177
+ * ```
178
+ */
179
+ class SliderComponent {
180
+ /** Lower bound of the slider scale. Defaults to `0`. */
181
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
182
+ /** Upper bound of the slider scale. Defaults to `100`. */
183
+ max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
184
+ /** Step increment used for snapping, keyboard nav, and auto-generated marks. Pass `null` for continuous values. Defaults to `1`. */
185
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
186
+ /** Semantic color for the filled portion and thumb border. Defaults to `'primary'`. */
187
+ color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
188
+ /** Overall scale of the rail, thumb, and typography. Defaults to `'md'`. */
189
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
190
+ /** Visual style of the fill: `'solid'` (vivid), `'soft'` (muted), or `'outline'` (rail bordered, fill color-500). Defaults to `'solid'`. */
191
+ variant = input('solid', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
192
+ /** When true, prevents interaction and applies muted styling. Defaults to `false`. */
193
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
194
+ /** When true, sets `aria-required="true"` on the thumb for assistive tech. Defaults to `false`. */
195
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
196
+ /** When true, the slider selects a `[start, end]` range and renders two thumbs. Defaults to `false`. */
197
+ range = input(false, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
198
+ /** Tick marks: `true` auto-generates one mark per step, an array supplies custom marks, `false` renders no marks. Defaults to `false`. */
199
+ marks = input(false, ...(ngDevMode ? [{ debugName: "marks" }] : /* istanbul ignore next */ []));
200
+ /** When true, renders the mark labels beneath the rail. Requires `marks` to resolve to a list. Defaults to `false`. */
201
+ showMarkLabels = input(false, ...(ngDevMode ? [{ debugName: "showMarkLabels" }] : /* istanbul ignore next */ []));
202
+ /** When true, renders the min and max values at the ends of the scale. Defaults to `false`. */
203
+ showMinMax = input(false, ...(ngDevMode ? [{ debugName: "showMinMax" }] : /* istanbul ignore next */ []));
204
+ /** When true, renders a value bubble above the active thumb (on hover/focus/drag) and the current value in the header. Defaults to `false`. */
205
+ showValue = input(false, ...(ngDevMode ? [{ debugName: "showValue" }] : /* istanbul ignore next */ []));
206
+ /** Optional visible label rendered above the rail. When set, the slider is wired to the label via `aria-labelledby`. */
207
+ label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
208
+ /** Optional secondary description rendered under the label. Mirrored to `aria-describedby`. */
209
+ description = input(undefined, ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
210
+ /** Custom formatter for the value bubble, min/max labels, and `aria-valuetext`. Defaults to an integer or 2-decimal string. */
211
+ valueFormatter = input(undefined, ...(ngDevMode ? [{ debugName: "valueFormatter" }] : /* istanbul ignore next */ []));
212
+ /** Optional form name attribute. Purely informational — the component does not render a native input. */
213
+ name = input(undefined, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
214
+ /** Accessible name for the single-value thumb when no visible label is provided. */
215
+ ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
216
+ /** ID of an external element labelling the single-value thumb. */
217
+ ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
218
+ /** ID of an external element describing the slider. */
219
+ ariaDescribedby = input(undefined, { ...(ngDevMode ? { debugName: "ariaDescribedby" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
220
+ /** Accessible name for the start (lower) thumb in range mode. Defaults to `"Minimum"`. */
221
+ ariaLabelStart = input('Minimum', ...(ngDevMode ? [{ debugName: "ariaLabelStart" }] : /* istanbul ignore next */ []));
222
+ /** Accessible name for the end (upper) thumb in range mode. Defaults to `"Maximum"`. */
223
+ ariaLabelEnd = input('Maximum', ...(ngDevMode ? [{ debugName: "ariaLabelEnd" }] : /* istanbul ignore next */ []));
224
+ /** Two-way bound slider value. A `number` for single mode, or `[start, end]` for range mode. Updates on every interaction. */
225
+ value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
226
+ /** Fires continuously while the user drags or holds a key. Payload matches the current `value`. Template event name is `input` — the TS-side identifier is `valueInput` to avoid shadowing the imported `input` factory. */
227
+ valueInput = output({ alias: 'input' });
228
+ /** Fires when the user commits a change (pointer release, key release, or blur after keyboard change). Payload matches the current `value`. */
229
+ change = output();
230
+ /** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the component uses the `TW_ERROR_STATE_MATCHER` token's value. */
231
+ errorStateMatcher = input(undefined, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
232
+ focusMonitor = inject(FocusMonitor);
233
+ elementRef = inject((ElementRef));
234
+ destroyRef = inject(DestroyRef);
235
+ directionality = inject(Directionality, { optional: true });
236
+ ngControl = inject(NgControl, { optional: true, self: true });
237
+ parentForm = inject(NgForm, { optional: true });
238
+ parentFormGroup = inject(FormGroupDirective, { optional: true });
239
+ defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
240
+ _ngControlRev = signal(0, ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
241
+ _formSubmitRev = signal(0, ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
242
+ /** Whether the form control is in an error state per the configured `ErrorStateMatcher`. */
243
+ errorState = computed(() => {
244
+ this._ngControlRev();
245
+ this._formSubmitRev();
246
+ // Read focus so blur-driven `touched` transitions repaint the error
247
+ // border even when the underlying control's status/value didn't change.
248
+ this.focusedThumb();
249
+ const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
250
+ const form = this.parentFormGroup ??
251
+ this.parentForm;
252
+ return matcher.isErrorState(this.ngControl?.control ?? null, form);
253
+ }, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
254
+ region = viewChild.required('region');
255
+ startThumb = viewChild('startThumb', ...(ngDevMode ? [{ debugName: "startThumb" }] : /* istanbul ignore next */ []));
256
+ endThumb = viewChild('endThumb', ...(ngDevMode ? [{ debugName: "endThumb" }] : /* istanbul ignore next */ []));
257
+ singleThumb = viewChild('singleThumb', ...(ngDevMode ? [{ debugName: "singleThumb" }] : /* istanbul ignore next */ []));
258
+ onChange = () => { };
259
+ onTouched = () => { };
260
+ cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
261
+ /** @internal ID of the host element. */
262
+ uid = nextId++;
263
+ /** @internal */
264
+ labelId = `tw-slider-${this.uid}-label`;
265
+ /** @internal */
266
+ descriptionId = `tw-slider-${this.uid}-description`;
267
+ /** @internal */
268
+ singleThumbId = `tw-slider-${this.uid}-thumb`;
269
+ /** @internal */
270
+ startThumbId = `tw-slider-${this.uid}-start`;
271
+ /** @internal */
272
+ endThumbId = `tw-slider-${this.uid}-end`;
273
+ /** @internal Internal values during drag / interaction, independent of external model. */
274
+ internalStart = linkedSignal(() => toRange(this.value())[0], ...(ngDevMode ? [{ debugName: "internalStart" }] : /* istanbul ignore next */ []));
275
+ internalEnd = linkedSignal(() => toRange(this.value())[1], ...(ngDevMode ? [{ debugName: "internalEnd" }] : /* istanbul ignore next */ []));
276
+ internalSingle = linkedSignal(() => toNumber(this.value(), this.min()), ...(ngDevMode ? [{ debugName: "internalSingle" }] : /* istanbul ignore next */ []));
277
+ /** @internal Which thumb currently has an active pointer gesture (or null). Bound in the template to drive the pressed-thumb styling. */
278
+ activeThumb = signal(null, ...(ngDevMode ? [{ debugName: "activeThumb" }] : /* istanbul ignore next */ []));
279
+ /** @internal Which thumb is focused (for bubble visibility). */
280
+ focusedThumb = signal(null, ...(ngDevMode ? [{ debugName: "focusedThumb" }] : /* istanbul ignore next */ []));
281
+ /** @internal Set of pointer IDs currently captured per-thumb for release tracking. */
282
+ capturedPointers = new Map();
283
+ /** @internal Tracks whether a keyboard interaction has mutated the value since last blur. */
284
+ keyboardDirty = false;
285
+ constructor() {
286
+ // Material-style CVA wiring: declare ourselves as the value accessor on any
287
+ // host-level `NgControl` (FormControlDirective, NgModel, etc.). This avoids
288
+ // the circular-DI that a static `NG_VALUE_ACCESSOR` provider would create
289
+ // because `NgControl` is injected with `self: true` on the same element.
290
+ if (this.ngControl) {
291
+ this.ngControl.valueAccessor = this;
292
+ }
293
+ afterNextRender(() => {
294
+ if (isDevMode() && !this.hasAccessibleName()) {
295
+ console.warn('[tw-slider] No accessible name provided. Supply a `label` input, `aria-label`, or `aria-labelledby`. In range mode, `ariaLabelStart` and `ariaLabelEnd` are used as fallbacks.');
296
+ }
297
+ });
298
+ }
299
+ // ── Derived state ─────────────────────────────────────────
300
+ /** @internal Effective disabled state (input OR set via CVA). */
301
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
302
+ /** @internal Whether a label/value header row should render. */
303
+ hasHeader = computed(() => !!this.label() || this.showValue(), ...(ngDevMode ? [{ debugName: "hasHeader" }] : /* istanbul ignore next */ []));
304
+ /** @internal Effective step: min 0 (continuous). */
305
+ effectiveStep = computed(() => {
306
+ const s = this.step();
307
+ if (s === null || s === undefined)
308
+ return 0;
309
+ return Math.abs(s);
310
+ }, ...(ngDevMode ? [{ debugName: "effectiveStep" }] : /* istanbul ignore next */ []));
311
+ /** @internal Current scale range. */
312
+ scaleRange = computed(() => {
313
+ const lo = this.min();
314
+ const hi = this.max();
315
+ return hi > lo ? hi - lo : 0;
316
+ }, ...(ngDevMode ? [{ debugName: "scaleRange" }] : /* istanbul ignore next */ []));
317
+ /** @internal Whether marks are resolved as a visible list. */
318
+ marksResolved = computed(() => {
319
+ const source = this.marks();
320
+ const lo = this.min();
321
+ const hi = this.max();
322
+ if (Array.isArray(source)) {
323
+ return source.filter((m) => m.value >= lo && m.value <= hi);
324
+ }
325
+ if (source !== true)
326
+ return [];
327
+ const step = this.effectiveStep();
328
+ if (step <= 0 || this.scaleRange() <= 0)
329
+ return [];
330
+ const out = [];
331
+ for (let v = lo; v <= hi + 1e-9; v += step) {
332
+ out.push({ value: Math.round(v * 1e6) / 1e6 });
333
+ }
334
+ return out;
335
+ }, ...(ngDevMode ? [{ debugName: "marksResolved" }] : /* istanbul ignore next */ []));
336
+ /** @internal */
337
+ resolvedMarks = computed(() => this.marksResolved(), ...(ngDevMode ? [{ debugName: "resolvedMarks" }] : /* istanbul ignore next */ []));
338
+ /** @internal */
339
+ hasVisibleMarks = computed(() => this.marksResolved().length > 0, ...(ngDevMode ? [{ debugName: "hasVisibleMarks" }] : /* istanbul ignore next */ []));
340
+ /** @internal */
341
+ hasMarkLabels = computed(() => this.showMarkLabels() && this.marksResolved().length > 0, ...(ngDevMode ? [{ debugName: "hasMarkLabels" }] : /* istanbul ignore next */ []));
342
+ /** @internal Clamped current values. */
343
+ startValue = computed(() => this.clampAndSnap(Math.min(this.internalStart(), this.internalEnd())), ...(ngDevMode ? [{ debugName: "startValue" }] : /* istanbul ignore next */ []));
344
+ /** @internal */
345
+ endValue = computed(() => this.clampAndSnap(Math.max(this.internalStart(), this.internalEnd())), ...(ngDevMode ? [{ debugName: "endValue" }] : /* istanbul ignore next */ []));
346
+ /** @internal */
347
+ singleValue = computed(() => this.clampAndSnap(this.internalSingle()), ...(ngDevMode ? [{ debugName: "singleValue" }] : /* istanbul ignore next */ []));
348
+ /** @internal aria-valuemax for the start thumb (cannot cross end thumb). */
349
+ effectiveStartMax = computed(() => this.endValue(), ...(ngDevMode ? [{ debugName: "effectiveStartMax" }] : /* istanbul ignore next */ []));
350
+ /** @internal aria-valuemin for the end thumb (cannot go below start thumb). */
351
+ effectiveEndMin = computed(() => this.startValue(), ...(ngDevMode ? [{ debugName: "effectiveEndMin" }] : /* istanbul ignore next */ []));
352
+ /** @internal Percentage position of the single thumb on the rail. */
353
+ singlePct = computed(() => this.valuePct(this.singleValue()), ...(ngDevMode ? [{ debugName: "singlePct" }] : /* istanbul ignore next */ []));
354
+ /** @internal */
355
+ startPct = computed(() => this.valuePct(this.startValue()), ...(ngDevMode ? [{ debugName: "startPct" }] : /* istanbul ignore next */ []));
356
+ /** @internal */
357
+ endPct = computed(() => this.valuePct(this.endValue()), ...(ngDevMode ? [{ debugName: "endPct" }] : /* istanbul ignore next */ []));
358
+ /** @internal Fill segment position. */
359
+ fillLeftPct = computed(() => (this.range() ? this.startPct() : 0), ...(ngDevMode ? [{ debugName: "fillLeftPct" }] : /* istanbul ignore next */ []));
360
+ /** @internal */
361
+ fillWidthPct = computed(() => this.range() ? this.endPct() - this.startPct() : this.singlePct(), ...(ngDevMode ? [{ debugName: "fillWidthPct" }] : /* istanbul ignore next */ []));
362
+ /** @internal Formatted display of the current value for the header. */
363
+ headerValueText = computed(() => {
364
+ if (this.range()) {
365
+ return `${this.format(this.startValue())} – ${this.format(this.endValue())}`;
366
+ }
367
+ return this.format(this.singleValue());
368
+ }, ...(ngDevMode ? [{ debugName: "headerValueText" }] : /* istanbul ignore next */ []));
369
+ // ── tv slot classes ─────────────────────────────────────────
370
+ variantResult = computed(() => sliderVariants({
371
+ size: this.size(),
372
+ variant: this.variant(),
373
+ disabled: this.isDisabled(),
374
+ hasHeader: this.hasHeader(),
375
+ }), ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
376
+ /** @internal */
377
+ rootClasses = computed(() => this.variantResult().root(), ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
378
+ /** @internal */
379
+ headerClasses = computed(() => this.variantResult().header(), ...(ngDevMode ? [{ debugName: "headerClasses" }] : /* istanbul ignore next */ []));
380
+ /** @internal */
381
+ labelClasses = computed(() => this.variantResult().label(), ...(ngDevMode ? [{ debugName: "labelClasses" }] : /* istanbul ignore next */ []));
382
+ /** @internal */
383
+ valueTextClasses = computed(() => this.variantResult().valueText(), ...(ngDevMode ? [{ debugName: "valueTextClasses" }] : /* istanbul ignore next */ []));
384
+ /** @internal */
385
+ descriptionClasses = computed(() => this.variantResult().description(), ...(ngDevMode ? [{ debugName: "descriptionClasses" }] : /* istanbul ignore next */ []));
386
+ /** @internal */
387
+ regionClasses = computed(() => this.variantResult().region(), ...(ngDevMode ? [{ debugName: "regionClasses" }] : /* istanbul ignore next */ []));
388
+ /** @internal */
389
+ railClasses = computed(() => {
390
+ const base = this.variantResult().rail();
391
+ if (this.variant() === 'outline')
392
+ return base;
393
+ return base;
394
+ }, ...(ngDevMode ? [{ debugName: "railClasses" }] : /* istanbul ignore next */ []));
395
+ /** @internal */
396
+ fillClasses = computed(() => {
397
+ const base = this.variantResult().fill();
398
+ const variant = this.variant();
399
+ const color = this.color();
400
+ if (variant === 'soft')
401
+ return `${base} ${FILL_SOFT[color]}`;
402
+ if (variant === 'outline') {
403
+ return `${base} ${FILL_SOLID[color]} ${FILL_OUTLINE_BORDER[color]}`;
404
+ }
405
+ return `${base} ${FILL_SOLID[color]}`;
406
+ }, ...(ngDevMode ? [{ debugName: "fillClasses" }] : /* istanbul ignore next */ []));
407
+ /** @internal */
408
+ marksRowClasses = computed(() => this.variantResult().marksRow(), ...(ngDevMode ? [{ debugName: "marksRowClasses" }] : /* istanbul ignore next */ []));
409
+ /** @internal */
410
+ markLabelsRowClasses = computed(() => this.variantResult().markLabelsRow(), ...(ngDevMode ? [{ debugName: "markLabelsRowClasses" }] : /* istanbul ignore next */ []));
411
+ /** @internal */
412
+ markLabelClasses = computed(() => this.variantResult().markLabel(), ...(ngDevMode ? [{ debugName: "markLabelClasses" }] : /* istanbul ignore next */ []));
413
+ /** @internal */
414
+ minMaxRowClasses = computed(() => this.variantResult().minMaxRow(), ...(ngDevMode ? [{ debugName: "minMaxRowClasses" }] : /* istanbul ignore next */ []));
415
+ /** @internal */
416
+ thumbClasses = computed(() => `${this.variantResult().thumb()} ${THUMB_BORDER[this.color()]} ${FOCUS_RING[this.color()]}`, ...(ngDevMode ? [{ debugName: "thumbClasses" }] : /* istanbul ignore next */ []));
417
+ /**
418
+ * @internal Memoized class map for mark dots — keyed by mark value. Recomputed
419
+ * only when the variant slots, fill range, or marks list changes; lookups
420
+ * from the template (`markClassFor(markValue)`) are O(1) Map reads.
421
+ */
422
+ markClassMap = computed(() => {
423
+ const base = this.variantResult().mark();
424
+ const lo = this.range() ? this.startValue() : this.min();
425
+ const hi = this.range() ? this.endValue() : this.singleValue();
426
+ const insideClass = `${base} bg-surface`;
427
+ const outsideClass = `${base} bg-fg-muted/30`;
428
+ const map = new Map();
429
+ for (const mark of this.marksResolved()) {
430
+ const inside = mark.value >= lo && mark.value <= hi;
431
+ map.set(mark.value, inside ? insideClass : outsideClass);
432
+ }
433
+ return { map, outsideClass };
434
+ }, ...(ngDevMode ? [{ debugName: "markClassMap" }] : /* istanbul ignore next */ []));
435
+ /** @internal Mark dot color depends on whether it falls inside the filled segment. */
436
+ markClassFor(markValue) {
437
+ const { map, outsideClass } = this.markClassMap();
438
+ return map.get(markValue) ?? outsideClass;
439
+ }
440
+ /**
441
+ * @internal Memoized bubble classes per thumb — enumerated once per
442
+ * dependency change so each `bubbleClassFor(thumb)` is a single object
443
+ * property read.
444
+ */
445
+ bubbleClassMap = computed(() => {
446
+ const base = this.variantResult().bubble();
447
+ if (!this.showValue()) {
448
+ return { single: base, start: base, end: base };
449
+ }
450
+ const visibleClass = `${base} ${this.variantResult().bubbleVisible()}`;
451
+ const active = this.activeThumb();
452
+ const focused = this.focusedThumb();
453
+ const classFor = (thumb) => active === thumb || focused === thumb ? visibleClass : base;
454
+ return {
455
+ single: classFor('single'),
456
+ start: classFor('start'),
457
+ end: classFor('end'),
458
+ };
459
+ }, ...(ngDevMode ? [{ debugName: "bubbleClassMap" }] : /* istanbul ignore next */ []));
460
+ /** @internal Bubble visibility depends on drag / focus / hover state. */
461
+ bubbleClassFor(thumb) {
462
+ return this.bubbleClassMap()[thumb];
463
+ }
464
+ // ── Accessible-name resolution ─────────────────────────────
465
+ /** @internal */
466
+ resolvedAriaDescribedby = computed(() => {
467
+ const external = this.ariaDescribedby();
468
+ if (external)
469
+ return external;
470
+ return this.description() ? this.descriptionId : null;
471
+ }, ...(ngDevMode ? [{ debugName: "resolvedAriaDescribedby" }] : /* istanbul ignore next */ []));
472
+ /** @internal */
473
+ resolvedSingleAriaLabel = computed(() => {
474
+ if (this.label())
475
+ return null;
476
+ if (this.ariaLabelledby())
477
+ return null;
478
+ return this.ariaLabel() ?? null;
479
+ }, ...(ngDevMode ? [{ debugName: "resolvedSingleAriaLabel" }] : /* istanbul ignore next */ []));
480
+ /** @internal */
481
+ resolvedSingleAriaLabelledby = computed(() => {
482
+ if (this.label())
483
+ return this.labelId;
484
+ return this.ariaLabelledby() ?? null;
485
+ }, ...(ngDevMode ? [{ debugName: "resolvedSingleAriaLabelledby" }] : /* istanbul ignore next */ []));
486
+ /** @internal */
487
+ resolvedStartAriaLabel = computed(() => {
488
+ if (this.ariaLabelledby() || this.label())
489
+ return null;
490
+ return this.ariaLabelStart();
491
+ }, ...(ngDevMode ? [{ debugName: "resolvedStartAriaLabel" }] : /* istanbul ignore next */ []));
492
+ /** @internal */
493
+ resolvedStartAriaLabelledby = computed(() => {
494
+ if (this.ariaLabelledby())
495
+ return this.ariaLabelledby() ?? null;
496
+ return this.label() ? this.labelId : null;
497
+ }, ...(ngDevMode ? [{ debugName: "resolvedStartAriaLabelledby" }] : /* istanbul ignore next */ []));
498
+ /** @internal */
499
+ resolvedEndAriaLabel = computed(() => {
500
+ if (this.ariaLabelledby() || this.label())
501
+ return null;
502
+ return this.ariaLabelEnd();
503
+ }, ...(ngDevMode ? [{ debugName: "resolvedEndAriaLabel" }] : /* istanbul ignore next */ []));
504
+ /** @internal */
505
+ resolvedEndAriaLabelledby = computed(() => {
506
+ if (this.ariaLabelledby())
507
+ return this.ariaLabelledby() ?? null;
508
+ return this.label() ? this.labelId : null;
509
+ }, ...(ngDevMode ? [{ debugName: "resolvedEndAriaLabelledby" }] : /* istanbul ignore next */ []));
510
+ // ── Interactions ───────────────────────────────────────────
511
+ /** Track click: move the nearest thumb to the clicked position and begin a drag. */
512
+ onTrackPointerDown(event) {
513
+ if (this.isDisabled())
514
+ return;
515
+ if (event.button !== 0 && event.pointerType === 'mouse')
516
+ return;
517
+ // Ignore presses that started on a thumb — its own handler runs.
518
+ const target = event.target;
519
+ if (target?.closest('[role="slider"]'))
520
+ return;
521
+ const nearest = this.nearestThumbFor(event.clientX);
522
+ const thumbEl = this.thumbElementFor(nearest);
523
+ if (!thumbEl)
524
+ return;
525
+ thumbEl.focus();
526
+ this.updateValueFromClientX(nearest, event.clientX, true);
527
+ this.beginDrag(nearest, thumbEl, event);
528
+ }
529
+ /** Thumb press: capture pointer and begin a drag gesture. */
530
+ onThumbPointerDown(event, thumb) {
531
+ if (this.isDisabled())
532
+ return;
533
+ if (event.button !== 0 && event.pointerType === 'mouse')
534
+ return;
535
+ const target = event.currentTarget;
536
+ target.focus();
537
+ this.beginDrag(thumb, target, event);
538
+ }
539
+ beginDrag(thumb, thumbEl, event) {
540
+ event.preventDefault();
541
+ try {
542
+ thumbEl.setPointerCapture(event.pointerId);
543
+ }
544
+ catch {
545
+ // setPointerCapture can throw in some synthetic test environments — degrade gracefully.
546
+ }
547
+ this.activeThumb.set(thumb);
548
+ this.capturedPointers.set(event.pointerId, { thumb, target: thumbEl });
549
+ thumbEl.addEventListener('pointermove', this.onPointerMove);
550
+ thumbEl.addEventListener('pointerup', this.onPointerUp);
551
+ thumbEl.addEventListener('pointercancel', this.onPointerUp);
552
+ }
553
+ onPointerMove = (event) => {
554
+ const entry = this.capturedPointers.get(event.pointerId);
555
+ if (!entry)
556
+ return;
557
+ this.updateValueFromClientX(entry.thumb, event.clientX, false);
558
+ };
559
+ onPointerUp = (event) => {
560
+ const entry = this.capturedPointers.get(event.pointerId);
561
+ if (!entry)
562
+ return;
563
+ const { target } = entry;
564
+ try {
565
+ target.releasePointerCapture(event.pointerId);
566
+ }
567
+ catch {
568
+ // noop
569
+ }
570
+ target.removeEventListener('pointermove', this.onPointerMove);
571
+ target.removeEventListener('pointerup', this.onPointerUp);
572
+ target.removeEventListener('pointercancel', this.onPointerUp);
573
+ this.capturedPointers.delete(event.pointerId);
574
+ if (this.capturedPointers.size === 0) {
575
+ this.activeThumb.set(null);
576
+ }
577
+ this.commitValue();
578
+ };
579
+ /** Keyboard navigation: arrows for step, PageUp/Down for 10% jumps, Home/End for extremes. */
580
+ onThumbKeyDown(event, thumb) {
581
+ if (this.isDisabled())
582
+ return;
583
+ const rtl = this.directionality?.value === 'rtl';
584
+ const range = this.scaleRange();
585
+ const step = this.effectiveStep() || range / 100 || 1;
586
+ const bigStep = range > 0 ? range / 10 : step * 10;
587
+ let delta = 0;
588
+ let jumpTo = null;
589
+ switch (event.key) {
590
+ case 'ArrowRight':
591
+ delta = rtl ? -step : step;
592
+ break;
593
+ case 'ArrowUp':
594
+ delta = step;
595
+ break;
596
+ case 'ArrowLeft':
597
+ delta = rtl ? step : -step;
598
+ break;
599
+ case 'ArrowDown':
600
+ delta = -step;
601
+ break;
602
+ case 'PageUp':
603
+ delta = bigStep;
604
+ break;
605
+ case 'PageDown':
606
+ delta = -bigStep;
607
+ break;
608
+ case 'Home':
609
+ jumpTo = this.min();
610
+ break;
611
+ case 'End':
612
+ jumpTo = this.max();
613
+ break;
614
+ default:
615
+ return;
616
+ }
617
+ event.preventDefault();
618
+ const current = this.getThumbValue(thumb);
619
+ const next = jumpTo !== null ? jumpTo : current + delta;
620
+ this.setThumbValue(thumb, next, /* emitInput */ true);
621
+ this.commitValue();
622
+ this.keyboardDirty = true;
623
+ }
624
+ /** @internal */
625
+ onThumbBlur() {
626
+ this.focusedThumb.set(null);
627
+ if (this.keyboardDirty) {
628
+ this.keyboardDirty = false;
629
+ this.onTouched();
630
+ }
631
+ else {
632
+ this.onTouched();
633
+ }
634
+ }
635
+ // ── Value math ─────────────────────────────────────────────
636
+ /** @internal Clamp to min/max and snap to step. Returns a finite number. */
637
+ clampAndSnap(raw) {
638
+ if (!Number.isFinite(raw))
639
+ return this.min();
640
+ const lo = this.min();
641
+ const hi = this.max();
642
+ const bounded = clamp(raw, lo, hi);
643
+ const step = this.effectiveStep();
644
+ if (step <= 0)
645
+ return bounded;
646
+ const snapped = Math.round((bounded - lo) / step) * step + lo;
647
+ // Protect against floating-point drift that pushes values just past max.
648
+ return clamp(snapped, lo, hi);
649
+ }
650
+ /** @internal Percent of value along the scale, in [0, 100]. */
651
+ valuePct(value) {
652
+ const range = this.scaleRange();
653
+ if (range <= 0)
654
+ return 0;
655
+ return ((value - this.min()) / range) * 100;
656
+ }
657
+ /** @internal Format a single number using the configured formatter. */
658
+ format(value) {
659
+ const fmt = this.valueFormatter() ?? DEFAULT_FORMATTER;
660
+ return fmt(value);
661
+ }
662
+ getThumbValue(thumb) {
663
+ if (thumb === 'start')
664
+ return this.startValue();
665
+ if (thumb === 'end')
666
+ return this.endValue();
667
+ return this.singleValue();
668
+ }
669
+ setThumbValue(thumb, raw, emitInput) {
670
+ const snapped = this.clampAndSnap(raw);
671
+ if (thumb === 'single') {
672
+ if (snapped === this.internalSingle())
673
+ return;
674
+ this.internalSingle.set(snapped);
675
+ }
676
+ else if (thumb === 'start') {
677
+ const clamped = Math.min(snapped, this.internalEnd());
678
+ if (clamped === this.internalStart())
679
+ return;
680
+ this.internalStart.set(clamped);
681
+ }
682
+ else {
683
+ const clamped = Math.max(snapped, this.internalStart());
684
+ if (clamped === this.internalEnd())
685
+ return;
686
+ this.internalEnd.set(clamped);
687
+ }
688
+ const current = this.currentValue();
689
+ this.value.set(current);
690
+ if (emitInput)
691
+ this.valueInput.emit(current);
692
+ }
693
+ updateValueFromClientX(thumb, clientX, focusThumb) {
694
+ const regionEl = this.region().nativeElement;
695
+ const rect = regionEl.getBoundingClientRect();
696
+ if (rect.width <= 0)
697
+ return;
698
+ const rtl = this.directionality?.value === 'rtl';
699
+ const ratio = (clientX - rect.left) / rect.width;
700
+ const pct = clamp(rtl ? 1 - ratio : ratio, 0, 1);
701
+ const raw = this.min() + pct * this.scaleRange();
702
+ this.setThumbValue(thumb, raw, true);
703
+ if (focusThumb) {
704
+ const thumbEl = this.thumbElementFor(thumb);
705
+ thumbEl?.focus();
706
+ }
707
+ }
708
+ thumbElementFor(thumb) {
709
+ if (thumb === 'single')
710
+ return this.singleThumb()?.nativeElement;
711
+ if (thumb === 'start')
712
+ return this.startThumb()?.nativeElement;
713
+ return this.endThumb()?.nativeElement;
714
+ }
715
+ nearestThumbFor(clientX) {
716
+ if (!this.range())
717
+ return 'single';
718
+ const regionEl = this.region().nativeElement;
719
+ const rect = regionEl.getBoundingClientRect();
720
+ if (rect.width <= 0)
721
+ return 'start';
722
+ const rtl = this.directionality?.value === 'rtl';
723
+ const ratio = (clientX - rect.left) / rect.width;
724
+ const pct = clamp(rtl ? 1 - ratio : ratio, 0, 1);
725
+ const target = this.min() + pct * this.scaleRange();
726
+ const distStart = Math.abs(target - this.startValue());
727
+ const distEnd = Math.abs(target - this.endValue());
728
+ if (distStart === distEnd) {
729
+ // Prefer the end thumb when to the right of the fill, start otherwise.
730
+ return target >= this.endValue() ? 'end' : 'start';
731
+ }
732
+ return distStart < distEnd ? 'start' : 'end';
733
+ }
734
+ currentValue() {
735
+ if (this.range())
736
+ return [this.startValue(), this.endValue()];
737
+ return this.singleValue();
738
+ }
739
+ commitValue() {
740
+ const current = this.currentValue();
741
+ this.value.set(current);
742
+ this.onChange(current);
743
+ this.change.emit(current);
744
+ }
745
+ hasAccessibleName() {
746
+ if (this.label() || this.ariaLabel() || this.ariaLabelledby())
747
+ return true;
748
+ // In range mode, explicit per-thumb labels default to Minimum/Maximum.
749
+ return this.range();
750
+ }
751
+ // ── ControlValueAccessor ──────────────────────────────────
752
+ writeValue(value) {
753
+ if (value === null || value === undefined) {
754
+ this.internalSingle.set(this.clampAndSnap(this.min()));
755
+ this.internalStart.set(this.clampAndSnap(this.min()));
756
+ this.internalEnd.set(this.clampAndSnap(this.max()));
757
+ return;
758
+ }
759
+ if (Array.isArray(value)) {
760
+ const [start, end] = value;
761
+ this.internalStart.set(this.clampAndSnap(start));
762
+ this.internalEnd.set(this.clampAndSnap(end));
763
+ }
764
+ else {
765
+ this.internalSingle.set(this.clampAndSnap(value));
766
+ }
767
+ }
768
+ registerOnChange(fn) {
769
+ this.onChange = fn;
770
+ }
771
+ registerOnTouched(fn) {
772
+ this.onTouched = fn;
773
+ }
774
+ setDisabledState(isDisabled) {
775
+ this.cvaDisabled.set(isDisabled);
776
+ }
777
+ // ── Lifecycle ─────────────────────────────────────────────
778
+ ngOnInit() {
779
+ this.focusMonitor
780
+ .monitor(this.elementRef, true)
781
+ .pipe(takeUntilDestroyed(this.destroyRef))
782
+ .subscribe((origin) => {
783
+ if (origin === null) {
784
+ this.focusedThumb.set(null);
785
+ return;
786
+ }
787
+ const active = this.elementRef.nativeElement.ownerDocument?.activeElement;
788
+ if (!active)
789
+ return;
790
+ if (active === this.singleThumb()?.nativeElement)
791
+ this.focusedThumb.set('single');
792
+ else if (active === this.startThumb()?.nativeElement)
793
+ this.focusedThumb.set('start');
794
+ else if (active === this.endThumb()?.nativeElement)
795
+ this.focusedThumb.set('end');
796
+ else
797
+ this.focusedThumb.set(null);
798
+ });
799
+ // Re-run the error-state matcher when the bound control's status/value
800
+ // changes or the parent form is submitted. `ngOnInit` is the natural
801
+ // mount point — by here, the parent `FormControl*` directive's
802
+ // `ngOnChanges` has already populated `ngControl.control`.
803
+ const ctrl = this.ngControl?.control;
804
+ if (ctrl) {
805
+ const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
806
+ if (streams.length) {
807
+ merge(...streams)
808
+ .pipe(takeUntilDestroyed(this.destroyRef))
809
+ .subscribe(() => this._ngControlRev.update((v) => v + 1));
810
+ }
811
+ }
812
+ const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
813
+ if (submit) {
814
+ submit
815
+ .pipe(takeUntilDestroyed(this.destroyRef))
816
+ .subscribe(() => this._formSubmitRev.update((v) => v + 1));
817
+ }
818
+ this.destroyRef.onDestroy(() => {
819
+ this.focusMonitor.stopMonitoring(this.elementRef);
820
+ for (const { target } of this.capturedPointers.values()) {
821
+ target.removeEventListener('pointermove', this.onPointerMove);
822
+ target.removeEventListener('pointerup', this.onPointerUp);
823
+ target.removeEventListener('pointercancel', this.onPointerUp);
824
+ }
825
+ this.capturedPointers.clear();
826
+ });
827
+ }
828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
829
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: SliderComponent, isStandalone: true, selector: "tw-slider", inputs: { 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 }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, marks: { classPropertyName: "marks", publicName: "marks", isSignal: true, isRequired: false, transformFunction: null }, showMarkLabels: { classPropertyName: "showMarkLabels", publicName: "showMarkLabels", isSignal: true, isRequired: false, transformFunction: null }, showMinMax: { classPropertyName: "showMinMax", publicName: "showMinMax", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, valueFormatter: { classPropertyName: "valueFormatter", publicName: "valueFormatter", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelStart: { classPropertyName: "ariaLabelStart", publicName: "ariaLabelStart", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelEnd: { classPropertyName: "ariaLabelEnd", publicName: "ariaLabelEnd", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", valueInput: "input", change: "change" }, host: { properties: { "class": "rootClasses()", "attr.data-disabled": "isDisabled() || null" } }, viewQueries: [{ propertyName: "region", first: true, predicate: ["region"], descendants: true, isSignal: true }, { propertyName: "startThumb", first: true, predicate: ["startThumb"], descendants: true, isSignal: true }, { propertyName: "endThumb", first: true, predicate: ["endThumb"], descendants: true, isSignal: true }, { propertyName: "singleThumb", first: true, predicate: ["singleThumb"], descendants: true, isSignal: true }], ngImport: i0, template: `
830
+ @if (hasHeader()) {
831
+ <div [class]="headerClasses()">
832
+ @if (label(); as lbl) {
833
+ <span [id]="labelId" [class]="labelClasses()">{{ lbl }}</span>
834
+ } @else {
835
+ <span></span>
836
+ }
837
+ @if (showValue()) {
838
+ <span [class]="valueTextClasses()">{{ headerValueText() }}</span>
839
+ }
840
+ </div>
841
+ }
842
+ @if (description(); as desc) {
843
+ <span [id]="descriptionId" [class]="descriptionClasses()">{{ desc }}</span>
844
+ }
845
+
846
+ <div
847
+ #region
848
+ [class]="regionClasses()"
849
+ (pointerdown)="onTrackPointerDown($event)"
850
+ >
851
+ <div [class]="railClasses()">
852
+ <div
853
+ [class]="fillClasses()"
854
+ [style.left.%]="fillLeftPct()"
855
+ [style.width.%]="fillWidthPct()"
856
+ ></div>
857
+
858
+ @if (hasVisibleMarks()) {
859
+ <div [class]="marksRowClasses()">
860
+ @for (m of resolvedMarks(); track m.value) {
861
+ <span
862
+ [class]="markClassFor(m.value)"
863
+ [style.left.%]="valuePct(m.value)"
864
+ ></span>
865
+ }
866
+ </div>
867
+ }
868
+
869
+ @if (range()) {
870
+ <button
871
+ type="button"
872
+ #startThumb
873
+ [class]="thumbClasses()"
874
+ [style.left.%]="startPct()"
875
+ role="slider"
876
+ [id]="startThumbId"
877
+ [attr.aria-label]="resolvedStartAriaLabel()"
878
+ [attr.aria-labelledby]="resolvedStartAriaLabelledby()"
879
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
880
+ [attr.aria-valuemin]="min()"
881
+ [attr.aria-valuemax]="effectiveStartMax()"
882
+ [attr.aria-valuenow]="startValue()"
883
+ [attr.aria-valuetext]="format(startValue())"
884
+ [attr.aria-orientation]="'horizontal'"
885
+ [attr.aria-disabled]="isDisabled() || null"
886
+ [attr.aria-required]="required() || null"
887
+ [attr.aria-invalid]="errorState() || null"
888
+ [attr.data-dragging]="activeThumb() === 'start'"
889
+ [attr.tabindex]="isDisabled() ? -1 : 0"
890
+ (pointerdown)="onThumbPointerDown($event, 'start')"
891
+ (keydown)="onThumbKeyDown($event, 'start')"
892
+ (blur)="onThumbBlur()"
893
+ >
894
+ <span
895
+ aria-hidden="true"
896
+ [class]="bubbleClassFor('start')"
897
+ >{{ format(startValue()) }}</span>
898
+ </button>
899
+ <button
900
+ type="button"
901
+ #endThumb
902
+ [class]="thumbClasses()"
903
+ [style.left.%]="endPct()"
904
+ role="slider"
905
+ [id]="endThumbId"
906
+ [attr.aria-label]="resolvedEndAriaLabel()"
907
+ [attr.aria-labelledby]="resolvedEndAriaLabelledby()"
908
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
909
+ [attr.aria-valuemin]="effectiveEndMin()"
910
+ [attr.aria-valuemax]="max()"
911
+ [attr.aria-valuenow]="endValue()"
912
+ [attr.aria-valuetext]="format(endValue())"
913
+ [attr.aria-orientation]="'horizontal'"
914
+ [attr.aria-disabled]="isDisabled() || null"
915
+ [attr.aria-required]="required() || null"
916
+ [attr.aria-invalid]="errorState() || null"
917
+ [attr.data-dragging]="activeThumb() === 'end'"
918
+ [attr.tabindex]="isDisabled() ? -1 : 0"
919
+ (pointerdown)="onThumbPointerDown($event, 'end')"
920
+ (keydown)="onThumbKeyDown($event, 'end')"
921
+ (blur)="onThumbBlur()"
922
+ >
923
+ <span
924
+ aria-hidden="true"
925
+ [class]="bubbleClassFor('end')"
926
+ >{{ format(endValue()) }}</span>
927
+ </button>
928
+ } @else {
929
+ <button
930
+ type="button"
931
+ #singleThumb
932
+ [class]="thumbClasses()"
933
+ [style.left.%]="singlePct()"
934
+ role="slider"
935
+ [id]="singleThumbId"
936
+ [attr.aria-label]="resolvedSingleAriaLabel()"
937
+ [attr.aria-labelledby]="resolvedSingleAriaLabelledby()"
938
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
939
+ [attr.aria-valuemin]="min()"
940
+ [attr.aria-valuemax]="max()"
941
+ [attr.aria-valuenow]="singleValue()"
942
+ [attr.aria-valuetext]="format(singleValue())"
943
+ [attr.aria-orientation]="'horizontal'"
944
+ [attr.aria-disabled]="isDisabled() || null"
945
+ [attr.aria-required]="required() || null"
946
+ [attr.aria-invalid]="errorState() || null"
947
+ [attr.data-dragging]="activeThumb() === 'single'"
948
+ [attr.tabindex]="isDisabled() ? -1 : 0"
949
+ (pointerdown)="onThumbPointerDown($event, 'single')"
950
+ (keydown)="onThumbKeyDown($event, 'single')"
951
+ (blur)="onThumbBlur()"
952
+ >
953
+ <span
954
+ aria-hidden="true"
955
+ [class]="bubbleClassFor('single')"
956
+ >{{ format(singleValue()) }}</span>
957
+ </button>
958
+ }
959
+ </div>
960
+ </div>
961
+
962
+ @if (hasMarkLabels()) {
963
+ <div [class]="markLabelsRowClasses()" aria-hidden="true">
964
+ @for (m of resolvedMarks(); track m.value) {
965
+ <span [class]="markLabelClasses()" [style.left.%]="valuePct(m.value)">
966
+ {{ m.label ?? format(m.value) }}
967
+ </span>
968
+ }
969
+ </div>
970
+ }
971
+
972
+ @if (showMinMax()) {
973
+ <div [class]="minMaxRowClasses()" aria-hidden="true">
974
+ <span>{{ format(min()) }}</span>
975
+ <span>{{ format(max()) }}</span>
976
+ </div>
977
+ }
978
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
979
+ }
980
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SliderComponent, decorators: [{
981
+ type: Component,
982
+ args: [{
983
+ selector: 'tw-slider',
984
+ changeDetection: ChangeDetectionStrategy.OnPush,
985
+ template: `
986
+ @if (hasHeader()) {
987
+ <div [class]="headerClasses()">
988
+ @if (label(); as lbl) {
989
+ <span [id]="labelId" [class]="labelClasses()">{{ lbl }}</span>
990
+ } @else {
991
+ <span></span>
992
+ }
993
+ @if (showValue()) {
994
+ <span [class]="valueTextClasses()">{{ headerValueText() }}</span>
995
+ }
996
+ </div>
997
+ }
998
+ @if (description(); as desc) {
999
+ <span [id]="descriptionId" [class]="descriptionClasses()">{{ desc }}</span>
1000
+ }
1001
+
1002
+ <div
1003
+ #region
1004
+ [class]="regionClasses()"
1005
+ (pointerdown)="onTrackPointerDown($event)"
1006
+ >
1007
+ <div [class]="railClasses()">
1008
+ <div
1009
+ [class]="fillClasses()"
1010
+ [style.left.%]="fillLeftPct()"
1011
+ [style.width.%]="fillWidthPct()"
1012
+ ></div>
1013
+
1014
+ @if (hasVisibleMarks()) {
1015
+ <div [class]="marksRowClasses()">
1016
+ @for (m of resolvedMarks(); track m.value) {
1017
+ <span
1018
+ [class]="markClassFor(m.value)"
1019
+ [style.left.%]="valuePct(m.value)"
1020
+ ></span>
1021
+ }
1022
+ </div>
1023
+ }
1024
+
1025
+ @if (range()) {
1026
+ <button
1027
+ type="button"
1028
+ #startThumb
1029
+ [class]="thumbClasses()"
1030
+ [style.left.%]="startPct()"
1031
+ role="slider"
1032
+ [id]="startThumbId"
1033
+ [attr.aria-label]="resolvedStartAriaLabel()"
1034
+ [attr.aria-labelledby]="resolvedStartAriaLabelledby()"
1035
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
1036
+ [attr.aria-valuemin]="min()"
1037
+ [attr.aria-valuemax]="effectiveStartMax()"
1038
+ [attr.aria-valuenow]="startValue()"
1039
+ [attr.aria-valuetext]="format(startValue())"
1040
+ [attr.aria-orientation]="'horizontal'"
1041
+ [attr.aria-disabled]="isDisabled() || null"
1042
+ [attr.aria-required]="required() || null"
1043
+ [attr.aria-invalid]="errorState() || null"
1044
+ [attr.data-dragging]="activeThumb() === 'start'"
1045
+ [attr.tabindex]="isDisabled() ? -1 : 0"
1046
+ (pointerdown)="onThumbPointerDown($event, 'start')"
1047
+ (keydown)="onThumbKeyDown($event, 'start')"
1048
+ (blur)="onThumbBlur()"
1049
+ >
1050
+ <span
1051
+ aria-hidden="true"
1052
+ [class]="bubbleClassFor('start')"
1053
+ >{{ format(startValue()) }}</span>
1054
+ </button>
1055
+ <button
1056
+ type="button"
1057
+ #endThumb
1058
+ [class]="thumbClasses()"
1059
+ [style.left.%]="endPct()"
1060
+ role="slider"
1061
+ [id]="endThumbId"
1062
+ [attr.aria-label]="resolvedEndAriaLabel()"
1063
+ [attr.aria-labelledby]="resolvedEndAriaLabelledby()"
1064
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
1065
+ [attr.aria-valuemin]="effectiveEndMin()"
1066
+ [attr.aria-valuemax]="max()"
1067
+ [attr.aria-valuenow]="endValue()"
1068
+ [attr.aria-valuetext]="format(endValue())"
1069
+ [attr.aria-orientation]="'horizontal'"
1070
+ [attr.aria-disabled]="isDisabled() || null"
1071
+ [attr.aria-required]="required() || null"
1072
+ [attr.aria-invalid]="errorState() || null"
1073
+ [attr.data-dragging]="activeThumb() === 'end'"
1074
+ [attr.tabindex]="isDisabled() ? -1 : 0"
1075
+ (pointerdown)="onThumbPointerDown($event, 'end')"
1076
+ (keydown)="onThumbKeyDown($event, 'end')"
1077
+ (blur)="onThumbBlur()"
1078
+ >
1079
+ <span
1080
+ aria-hidden="true"
1081
+ [class]="bubbleClassFor('end')"
1082
+ >{{ format(endValue()) }}</span>
1083
+ </button>
1084
+ } @else {
1085
+ <button
1086
+ type="button"
1087
+ #singleThumb
1088
+ [class]="thumbClasses()"
1089
+ [style.left.%]="singlePct()"
1090
+ role="slider"
1091
+ [id]="singleThumbId"
1092
+ [attr.aria-label]="resolvedSingleAriaLabel()"
1093
+ [attr.aria-labelledby]="resolvedSingleAriaLabelledby()"
1094
+ [attr.aria-describedby]="resolvedAriaDescribedby()"
1095
+ [attr.aria-valuemin]="min()"
1096
+ [attr.aria-valuemax]="max()"
1097
+ [attr.aria-valuenow]="singleValue()"
1098
+ [attr.aria-valuetext]="format(singleValue())"
1099
+ [attr.aria-orientation]="'horizontal'"
1100
+ [attr.aria-disabled]="isDisabled() || null"
1101
+ [attr.aria-required]="required() || null"
1102
+ [attr.aria-invalid]="errorState() || null"
1103
+ [attr.data-dragging]="activeThumb() === 'single'"
1104
+ [attr.tabindex]="isDisabled() ? -1 : 0"
1105
+ (pointerdown)="onThumbPointerDown($event, 'single')"
1106
+ (keydown)="onThumbKeyDown($event, 'single')"
1107
+ (blur)="onThumbBlur()"
1108
+ >
1109
+ <span
1110
+ aria-hidden="true"
1111
+ [class]="bubbleClassFor('single')"
1112
+ >{{ format(singleValue()) }}</span>
1113
+ </button>
1114
+ }
1115
+ </div>
1116
+ </div>
1117
+
1118
+ @if (hasMarkLabels()) {
1119
+ <div [class]="markLabelsRowClasses()" aria-hidden="true">
1120
+ @for (m of resolvedMarks(); track m.value) {
1121
+ <span [class]="markLabelClasses()" [style.left.%]="valuePct(m.value)">
1122
+ {{ m.label ?? format(m.value) }}
1123
+ </span>
1124
+ }
1125
+ </div>
1126
+ }
1127
+
1128
+ @if (showMinMax()) {
1129
+ <div [class]="minMaxRowClasses()" aria-hidden="true">
1130
+ <span>{{ format(min()) }}</span>
1131
+ <span>{{ format(max()) }}</span>
1132
+ </div>
1133
+ }
1134
+ `,
1135
+ host: {
1136
+ '[class]': 'rootClasses()',
1137
+ '[attr.data-disabled]': 'isDisabled() || null',
1138
+ },
1139
+ }]
1140
+ }], ctorParameters: () => [], propDecorators: { 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 }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], marks: [{ type: i0.Input, args: [{ isSignal: true, alias: "marks", required: false }] }], showMarkLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMarkLabels", required: false }] }], showMinMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMinMax", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], valueFormatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormatter", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], ariaDescribedby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], ariaLabelStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelStart", required: false }] }], ariaLabelEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelEnd", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], valueInput: [{ type: i0.Output, args: ["input"] }], change: [{ type: i0.Output, args: ["change"] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], region: [{ type: i0.ViewChild, args: ['region', { isSignal: true }] }], startThumb: [{ type: i0.ViewChild, args: ['startThumb', { isSignal: true }] }], endThumb: [{ type: i0.ViewChild, args: ['endThumb', { isSignal: true }] }], singleThumb: [{ type: i0.ViewChild, args: ['singleThumb', { isSignal: true }] }] } });
1141
+
1142
+ /**
1143
+ * Generated bundle index. Do not edit.
1144
+ */
1145
+
1146
+ export { SliderComponent };
1147
+ //# sourceMappingURL=cdevhub-ngx-tw-slider.mjs.map