range-selection-input 15.0.2 → 15.0.3

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.
package/README.md CHANGED
@@ -1,24 +1,1139 @@
1
- # RangeSelectionInput
1
+ # Range Selection Input Component
2
2
 
3
- This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.0.
3
+ ## Overview
4
4
 
5
- ## Code scaffolding
5
+ The `range-selection-input` library provides a Material Design slider component that allows users to select numeric values or ranges through an intuitive draggable interface. It supports both single value sliders and dual-thumb range selection, configurable min/max values, step divisions, tick marks, and display formatting. Built with Angular Material slider components and implementing `ControlValueAccessor` for seamless form integration.
6
6
 
7
- Run `ng generate component component-name --project range-selection-input` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project range-selection-input`.
8
- > Note: Don't forget to add `--project range-selection-input` or else it will be added to the default project in your `angular.json` file.
7
+ ### Core Capabilities
9
8
 
10
- ## Build
9
+ #### 🎚️ Numeric Range Selection Interface
11
10
 
12
- Run `ng build range-selection-input` to build the project. The build artifacts will be stored in the `dist/` directory.
11
+ - **Single Value Selection**: Select one numeric value using a single thumb slider
12
+ - **Dual Range Selection**: Select a range using dual thumbs with minimum and maximum values
13
+ - **Configurable Range**: Set minimum, maximum, and step values
14
+ - **Tick Mark Support**: Optional tick marks for discrete value selection
15
+ - **Display Formatting**: Customizable value display with formatting functions
16
+ - **Form Control Integration**: Implements `ControlValueAccessor` for reactive forms
17
+ - **Validation Support**: Native Angular validation system compatibility
18
+ - **Material Design**: Built on Angular Material slider foundation
13
19
 
14
- ## Publishing
20
+ #### 🔧 Features
15
21
 
16
- After building your library with `ng build range-selection-input`, go to the dist folder `cd dist/range-selection-input` and run `npm publish`.
22
+ **ControlValueAccessor Implementation** - Works with Angular forms
23
+ ✅ **Material Design Integration** - Uses Angular Material components
24
+ ✅ **Single & Range Selection** - Flexible selection modes
25
+ ✅ **Configurable Steps** - Set increment/decrement values
26
+ ✅ **Tick Marks** - Discrete value selection support
27
+ ✅ **Value Formatting** - Custom display formatting
28
+ ✅ **Form Validation** - Native validation integration
29
+ ✅ **Disabled State** - Disable slider interaction
30
+ ✅ **Display Functions** - Custom value presentation
17
31
 
18
- ## Running unit tests
32
+ ### Key Benefits
19
33
 
20
- Run `ng test range-selection-input` to execute the unit tests via [Karma](https://karma-runner.github.io).
34
+ | Feature | Description |
35
+ |---------|-------------|
36
+ | **Intuitive Selection** | Drag-based numeric value selection |
37
+ | **Range Support** | Select minimum and maximum values simultaneously |
38
+ | **Precise Control** | Configurable step values for precise selection |
39
+ | **Visual Feedback** | Tick marks and value display |
40
+ | **Form Integration** | Seamless Angular form control integration |
41
+ | **Flexible Formatting** | Custom value display formatting |
21
42
 
22
- ## Further help
43
+ ---
23
44
 
24
- To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
45
+ ## Summary
46
+
47
+ The `range-selection-input` library provides an intuitive numeric selection component with support for both single values and ranges, making it ideal for settings like price ranges, quantity selections, or any scenario requiring precise numeric input through a visual interface.
48
+
49
+ ---
50
+
51
+ ## Quick Start Guide
52
+
53
+ ### Installation & Setup (2 minutes)
54
+
55
+ #### 1. Import Module
56
+
57
+ ```typescript
58
+ // app.module.ts
59
+ import { RangeSelectionInputModule } from 'range-selection-input';
60
+
61
+ @NgModule({
62
+ imports: [
63
+ RangeSelectionInputModule
64
+ ]
65
+ })
66
+ export class AppModule { }
67
+ ```
68
+
69
+ #### 2. No Module Configuration Required
70
+
71
+ The `RangeSelectionInputModule` does not require global configuration. Components can be used immediately after module import.
72
+
73
+ ### Quick Examples
74
+
75
+ #### Example 1: Basic Single Value Slider
76
+
77
+ ```typescript
78
+ import { Component } from '@angular/core';
79
+ import { FormControl } from '@angular/forms';
80
+
81
+ @Component({
82
+ selector: 'app-basic-range',
83
+ template: `
84
+ <app-range-selection-input
85
+ [formControl]="volumeControl"
86
+ [min]="0"
87
+ [max]="100"
88
+ [step]="1"
89
+ label="Volume Level"
90
+ [showTicks]="true">
91
+ </app-range-selection-input>
92
+
93
+ <div>Volume: {{ volumeControl.value }}</div>
94
+ `
95
+ })
96
+ export class BasicRangeComponent {
97
+ volumeControl = new FormControl(50);
98
+ }
99
+ ```
100
+
101
+ #### Example 2: Dual Range Slider (Price Range)
102
+
103
+ ```typescript
104
+ import { Component } from '@angular/core';
105
+ import { FormControl } from '@angular/forms';
106
+
107
+ @Component({
108
+ selector: 'app-price-range',
109
+ template: `
110
+ <app-range-selection-input
111
+ [formControl]="priceControl"
112
+ [min]="0"
113
+ [max]="1000"
114
+ [step]="10"
115
+ [showTicks]="true"
116
+ label="Price Range ($)"
117
+ [displayFunction]="formatPrice">
118
+ </app-range-selection-input>
119
+
120
+ <div class="price-display">
121
+ <div *ngIf="priceControl.value">
122
+ <strong>Selected Range:</strong>
123
+ {{ formatPrice(priceControl.value.min) }} - {{ formatPrice(priceControl.value.max) }}
124
+ </div>
125
+ </div>
126
+ `,
127
+ styles: [`
128
+ .price-display {
129
+ margin-top: 1rem;
130
+ padding: 1rem;
131
+ background: #f5f5f5;
132
+ border-radius: 4px;
133
+ font-size: 1.1rem;
134
+ }
135
+ `]
136
+ })
137
+ export class PriceRangeComponent {
138
+ priceControl = new FormControl({ min: 100, max: 500 });
139
+
140
+ formatPrice(value: number): string {
141
+ return `$${value.toFixed(0)}`;
142
+ }
143
+ }
144
+ ```
145
+
146
+ #### Example 3: Quantity Selection with Validation
147
+
148
+ ```typescript
149
+ import { Component } from '@angular/core';
150
+ import { FormControl, Validators } from '@angular/forms';
151
+
152
+ @Component({
153
+ selector: 'app-quantity-selector',
154
+ template: `
155
+ <app-range-selection-input
156
+ [formControl]="quantityControl"
157
+ [min]="1"
158
+ [max]="10"
159
+ [step]="1"
160
+ [showTicks]="true"
161
+ label="Select Quantity"
162
+ [required]="true">
163
+ </app-range-selection-input>
164
+
165
+ <div class="errors" *ngIf="quantityControl.errors && quantityControl.touched">
166
+ <div *ngIf="quantityControl.hasError('required')">
167
+ Quantity selection is required
168
+ </div>
169
+ <div *ngIf="quantityControl.hasError('min')">
170
+ Minimum quantity is 1
171
+ </div>
172
+ <div *ngIf="quantityControl.hasError('max')">
173
+ Maximum quantity is 10
174
+ </div>
175
+ </div>
176
+
177
+ <div class="quantity-info">
178
+ Quantity: {{ quantityControl.value }}
179
+ <span *ngIf="quantityControl.value">
180
+ (Total: ${{ (quantityControl.value * unitPrice).toFixed(2) }})
181
+ </span>
182
+ </div>
183
+ `,
184
+ styles: [`
185
+ .errors {
186
+ color: #f44336;
187
+ font-size: 0.875rem;
188
+ margin-top: 0.5rem;
189
+ }
190
+ .quantity-info {
191
+ margin-top: 1rem;
192
+ padding: 0.75rem;
193
+ background: #e8f5e8;
194
+ border: 1px solid #4caf50;
195
+ border-radius: 4px;
196
+ color: #2e7d32;
197
+ }
198
+ `]
199
+ })
200
+ export class QuantitySelectorComponent {
201
+ quantityControl = new FormControl(1, [
202
+ Validators.required,
203
+ Validators.min(1),
204
+ Validators.max(10)
205
+ ]);
206
+
207
+ unitPrice = 29.99;
208
+ }
209
+ ```
210
+
211
+ #### Example 4: Percentage Selection
212
+
213
+ ```typescript
214
+ import { Component } from '@angular/core';
215
+ import { FormControl } from '@angular/forms';
216
+
217
+ @Component({
218
+ selector: 'app-percentage-selector',
219
+ template: `
220
+ <app-range-selection-input
221
+ [formControl]="opacityControl"
222
+ [min]="0"
223
+ [max]="100"
224
+ [step]="5"
225
+ [showTicks]="true"
226
+ label="Opacity Level"
227
+ [displayFunction]="formatPercentage">
228
+ </app-range-selection-input>
229
+
230
+ <div class="preview-container">
231
+ <div
232
+ class="preview-box"
233
+ [style.opacity]="(opacityControl.value || 50) / 100">
234
+ <p>Preview Box</p>
235
+ <p>Opacity: {{ formatPercentage(opacityControl.value || 50) }}</p>
236
+ </div>
237
+ </div>
238
+ `,
239
+ styles: [`
240
+ .preview-container {
241
+ margin-top: 2rem;
242
+ display: flex;
243
+ justify-content: center;
244
+ }
245
+ .preview-box {
246
+ width: 200px;
247
+ height: 100px;
248
+ background: linear-gradient(45deg, #2196f3, #21cbf3);
249
+ border-radius: 8px;
250
+ display: flex;
251
+ flex-direction: column;
252
+ align-items: center;
253
+ justify-content: center;
254
+ color: white;
255
+ font-weight: bold;
256
+ text-shadow: 0 1px 2px rgba(0,0,0,0.5);
257
+ transition: opacity 0.3s ease;
258
+ }
259
+ .preview-box p {
260
+ margin: 0.25rem 0;
261
+ }
262
+ `]
263
+ })
264
+ export class PercentageSelectorComponent {
265
+ opacityControl = new FormControl(75);
266
+
267
+ formatPercentage(value: number): string {
268
+ return `${value}%`;
269
+ }
270
+ }
271
+ ```
272
+
273
+ #### Example 5: Temperature Range Selection
274
+
275
+ ```typescript
276
+ import { Component } from '@angular/core';
277
+ import { FormControl } from '@angular/forms';
278
+
279
+ @Component({
280
+ selector: 'app-temperature-range',
281
+ template: `
282
+ <app-range-selection-input
283
+ [formControl]="temperatureControl"
284
+ [min]="-10"
285
+ [max]="40"
286
+ [step]="1"
287
+ [showTicks]="true"
288
+ label="Temperature Range (°C)"
289
+ [displayFunction]="formatTemperature">
290
+ </app-range-selection-input>
291
+
292
+ <div class="temperature-info" [attr.data-range]="getTemperatureRange()">
293
+ <h4>Temperature Range</h4>
294
+ <p>{{ formatTemperature(temperatureControl.value.min) }} to {{ formatTemperature(temperatureControl.value.max) }}</p>
295
+ <div class="range-description">
296
+ {{ getTemperatureDescription() }}
297
+ </div>
298
+ </div>
299
+ `,
300
+ styles: [`
301
+ .temperature-info {
302
+ margin-top: 1rem;
303
+ padding: 1rem;
304
+ border-radius: 8px;
305
+ text-align: center;
306
+ transition: all 0.3s ease;
307
+ }
308
+ .temperature-info[data-range="cold"] {
309
+ background: linear-gradient(135deg, #e3f2fd, #bbdefb);
310
+ border: 2px solid #2196f3;
311
+ color: #1565c0;
312
+ }
313
+ .temperature-info[data-range="moderate"] {
314
+ background: linear-gradient(135deg, #fff3e0, #ffcc02);
315
+ border: 2px solid #ff9800;
316
+ color: #ef6c00;
317
+ }
318
+ .temperature-info[data-range="warm"] {
319
+ background: linear-gradient(135deg, #ffebee, #ef5350);
320
+ border: 2px solid #f44336;
321
+ color: #c62828;
322
+ }
323
+ .range-description {
324
+ margin-top: 0.5rem;
325
+ font-style: italic;
326
+ opacity: 0.8;
327
+ }
328
+ `]
329
+ })
330
+ export class TemperatureRangeComponent {
331
+ temperatureControl = new FormControl({ min: 15, max: 25 });
332
+
333
+ formatTemperature(value: number): string {
334
+ if (value < 0) {
335
+ return `${Math.abs(value)}°C below freezing`;
336
+ }
337
+ return `${value}°C`;
338
+ }
339
+
340
+ getTemperatureRange(): string {
341
+ const avg = (this.temperatureControl.value.min + this.temperatureControl.value.max) / 2;
342
+ if (avg < 10) return 'cold';
343
+ if (avg < 25) return 'moderate';
344
+ return 'warm';
345
+ }
346
+
347
+ getTemperatureDescription(): string {
348
+ const range = this.getTemperatureRange();
349
+ const descriptions = {
350
+ cold: 'Cool weather - jacket recommended',
351
+ moderate: 'Pleasant temperature',
352
+ warm: 'Warm weather - stay hydrated'
353
+ };
354
+ return descriptions[range];
355
+ }
356
+ }
357
+ ```
358
+
359
+ #### Example 6: Form Integration with Dynamic Range
360
+
361
+ ```typescript
362
+ import { Component } from '@angular/core';
363
+ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
364
+
365
+ @Component({
366
+ selector: 'app-dynamic-range-form',
367
+ template: `
368
+ <form [formGroup]="dynamicForm">
369
+ <app-range-selection-input
370
+ formControlName="age"
371
+ [min]="18"
372
+ [max]="100"
373
+ [step]="1"
374
+ [showTicks]="true"
375
+ label="Age"
376
+ placeholder="Select age"
377
+ [required]="true">
378
+ </app-range-selection-input>
379
+
380
+ <app-range-selection-input
381
+ formControlName="budget"
382
+ [min]="budgetRange.min"
383
+ [max]="budgetRange.max"
384
+ [step]="budgetRange.step"
385
+ [showTicks]="true"
386
+ label="Budget Range ($)"
387
+ [displayFunction]="formatCurrency">
388
+ </app-range-selection-input>
389
+
390
+ <app-range-selection-input
391
+ formControlName="rating"
392
+ [min]="0"
393
+ [max]="5"
394
+ [step]="0.5"
395
+ [showTicks]="true"
396
+ label="Rating"
397
+ [displayFunction]="formatStars">
398
+ </app-range-selection-input>
399
+ </form>
400
+
401
+ <div class="form-status">
402
+ <div>Form Valid: {{ dynamicForm.valid }}</div>
403
+ <div>Age: {{ dynamicForm.get('age')?.value }}</div>
404
+ <div>Budget: {{ formatCurrency(dynamicForm.get('budget')?.value?.min) }} - {{ formatCurrency(dynamicForm.get('budget')?.value?.max) }}</div>
405
+ <div>Rating: {{ formatStars(dynamicForm.get('rating')?.value) }}</div>
406
+ </div>
407
+
408
+ <div class="controls">
409
+ <button (click)="setDefaults()">Set Defaults</button>
410
+ <button (click)="resetForm()">Reset</button>
411
+ <button (click)="submitForm()" [disabled]="dynamicForm.invalid">Submit</button>
412
+ </div>
413
+ `,
414
+ styles: [`
415
+ form {
416
+ display: flex;
417
+ flex-direction: column;
418
+ gap: 2rem;
419
+ max-width: 400px;
420
+ }
421
+ .form-status {
422
+ margin-top: 1rem;
423
+ padding: 1rem;
424
+ background: #f5f5f5;
425
+ border-radius: 4px;
426
+ font-family: monospace;
427
+ font-size: 0.9rem;
428
+ }
429
+ .controls {
430
+ margin-top: 1rem;
431
+ display: flex;
432
+ gap: 0.5rem;
433
+ }
434
+ button {
435
+ padding: 0.5rem 1rem;
436
+ border: 1px solid #ccc;
437
+ border-radius: 4px;
438
+ background: white;
439
+ cursor: pointer;
440
+ }
441
+ button:disabled {
442
+ opacity: 0.5;
443
+ cursor: not-allowed;
444
+ }
445
+ `]
446
+ })
447
+ export class DynamicRangeFormComponent {
448
+ dynamicForm: FormGroup;
449
+ budgetRange = { min: 1000, max: 10000, step: 100 };
450
+
451
+ constructor(private fb: FormBuilder) {
452
+ this.dynamicForm = this.fb.group({
453
+ age: [25, [Validators.required, Validators.min(18), Validators.max(100)]],
454
+ budget: [{ min: 3000, max: 7000 }, [Validators.required]],
455
+ rating: [3.5, [Validators.required, Validators.min(0), Validators.max(5)]]
456
+ });
457
+ }
458
+
459
+ formatCurrency(value: number): string {
460
+ if (typeof value === 'object' && value !== null) {
461
+ return `$${value.min?.toLocaleString()} - $${value.max?.toLocaleString()}`;
462
+ }
463
+ return `$${value.toLocaleString()}`;
464
+ }
465
+
466
+ formatStars(value: number): string {
467
+ const fullStars = Math.floor(value);
468
+ const hasHalfStar = value % 1 >= 0.5;
469
+ const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
470
+
471
+ return '★'.repeat(fullStars) +
472
+ (hasHalfStar ? '☆' : '') +
473
+ '☆'.repeat(emptyStars) +
474
+ ` (${value})`;
475
+ }
476
+
477
+ setDefaults() {
478
+ this.dynamicForm.patchValue({
479
+ age: 30,
480
+ budget: { min: 2500, max: 8000 },
481
+ rating: 4.0
482
+ });
483
+ }
484
+
485
+ resetForm() {
486
+ this.dynamicForm.reset();
487
+ }
488
+
489
+ submitForm() {
490
+ if (this.dynamicForm.valid) {
491
+ console.log('Form submitted:', this.dynamicForm.value);
492
+ alert('Form submitted successfully!');
493
+ }
494
+ }
495
+ }
496
+ ```
497
+
498
+ ---
499
+
500
+ ## Component API
501
+
502
+ ### Inputs
503
+
504
+ | Input | Type | Description | Default |
505
+ | :--- | :--- | :--- | :--- |
506
+ | `min` | `number` | Minimum value for the slider | `0` |
507
+ | `max` | `number` | Maximum value for the slider | `100` |
508
+ | `step` | `number` | Step increment/decrement value | `1` |
509
+ | `showTicks` | `boolean` | Whether to show tick marks | `false` |
510
+ | `label` | `string` | Label text for the slider | `undefined` |
511
+ | `placeholder` | `string` | Placeholder text for the slider | `undefined` |
512
+ | `displayFunction` | `(value: number) => string` | Function to format display values | `undefined` |
513
+ | `required` | `boolean` | Whether selection is required | `false` |
514
+ | `disabled` | `boolean` | Whether the slider is disabled | `false` |
515
+
516
+ ### Outputs
517
+
518
+ | Output | Type | Description |
519
+ |--------|------|-------------|
520
+ | `valueChange` | `EventEmitter<number \| { min: number; max: number }>` | Emits when slider value changes |
521
+
522
+ ---
523
+
524
+ ## Form Integration (ControlValueAccessor)
525
+
526
+ The component implements Angular's `ControlValueAccessor` interface for seamless form integration.
527
+
528
+ ### ControlValueAccessor Implementation
529
+
530
+ ```typescript
531
+ // writeValue(value: number | { min: number; max: number }): void
532
+ // Sets the slider value(s)
533
+ writeValue(value: number | { min: number; max: number }): void {
534
+ this.selectedValue = value;
535
+ }
536
+
537
+ // registerOnChange(fn: any): void
538
+ // Registers a callback for value changes
539
+ registerOnChange(fn: any): void {
540
+ this.onChange = fn;
541
+ }
542
+
543
+ // registerOnTouched(fn: any): void
544
+ // Registers a callback for touch events
545
+ registerOnTouched(fn: any): void {
546
+ this.onTouch = fn;
547
+ }
548
+
549
+ // setDisabledState(isDisabled: boolean): void
550
+ // Sets disabled state
551
+ setDisabledState?(isDisabled: boolean): void {
552
+ this.disabled = isDisabled;
553
+ }
554
+ ```
555
+
556
+ ### Form Integration Examples
557
+
558
+ #### Reactive Forms
559
+
560
+ ```typescript
561
+ import { Component } from '@angular/core';
562
+ import { FormControl, FormGroup, Validators } from '@angular/forms';
563
+
564
+ @Component({
565
+ selector: 'app-reactive-range-form',
566
+ template: `
567
+ <form [formGroup]="rangeForm">
568
+ <app-range-selection-input
569
+ formControlName="singleValue"
570
+ [min]="0"
571
+ [max]="100"
572
+ [step]="5"
573
+ [showTicks]="true"
574
+ label="Single Value"
575
+ [required]="true">
576
+ </app-range-selection-input>
577
+
578
+ <app-range-selection-input
579
+ formControlName="rangeValue"
580
+ [min]="0"
581
+ [max]="1000"
582
+ [step]="25"
583
+ [showTicks]="true"
584
+ label="Range Value"
585
+ [required]="true">
586
+ </app-range-selection-input>
587
+ </form>
588
+ `
589
+ })
590
+ export class ReactiveRangeFormComponent {
591
+ rangeForm = new FormGroup({
592
+ singleValue: new FormControl(50, [Validators.required, Validators.min(0), Validators.max(100)]),
593
+ rangeValue: new FormControl({ min: 100, max: 500 }, [Validators.required])
594
+ });
595
+ }
596
+ ```
597
+
598
+ #### Template-Driven Forms
599
+
600
+ ```typescript
601
+ import { Component } from '@angular/core';
602
+
603
+ @Component({
604
+ selector: 'app-template-range-form',
605
+ template: `
606
+ <app-range-selection-input
607
+ [(ngModel)]="selectedValue"
608
+ name="templateRange"
609
+ [min]="0"
610
+ [max]="100"
611
+ [step]="10"
612
+ [showTicks]="true"
613
+ label="Template Range Selection"
614
+ required>
615
+ </app-range-selection-input>
616
+
617
+ <div *ngIf="selectedValue">
618
+ Selected: {{ selectedValue }}
619
+ </div>
620
+ `
621
+ })
622
+ export class TemplateRangeFormComponent {
623
+ selectedValue = 25;
624
+ }
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Module Configuration
630
+
631
+ ### RangeSelectionInputModule
632
+
633
+ **No Global Configuration Required**
634
+
635
+ The `RangeSelectionInputModule` does not provide a `forRoot()` method or global configuration options. All configuration is done at the component level through input properties.
636
+
637
+ #### Dependencies
638
+
639
+ - **@angular/core**: Core Angular functionality
640
+ - **@angular/forms**: Form control integration (FormsModule, ReactiveFormsModule)
641
+ - **@angular/material**: Material Design components (MatSliderModule, MatFormFieldModule, etc.)
642
+
643
+ ---
644
+
645
+ ## Advanced Usage Patterns
646
+
647
+ ### Custom Display Formatting
648
+
649
+ ```typescript
650
+ import { Component } from '@angular/core';
651
+ import { FormControl } from '@angular/forms';
652
+
653
+ @Component({
654
+ selector: 'app-custom-formatting',
655
+ template: `
656
+ <app-range-selection-input
657
+ [formControl]="progressControl"
658
+ [min]="0"
659
+ [max]="100"
660
+ [step]="5"
661
+ [showTicks]="true"
662
+ label="Project Progress"
663
+ [displayFunction]="formatProgress">
664
+ </app-range-selection-input>
665
+
666
+ <div class="progress-visual">
667
+ <div class="progress-bar">
668
+ <div
669
+ class="progress-fill"
670
+ [style.width.%]="progressControl.value || 0">
671
+ </div>
672
+ </div>
673
+ <span class="progress-text">{{ formatProgress(progressControl.value || 0) }}</span>
674
+ </div>
675
+ `,
676
+ styles: [`
677
+ .progress-visual {
678
+ margin-top: 2rem;
679
+ }
680
+ .progress-bar {
681
+ width: 100%;
682
+ height: 20px;
683
+ background: #e0e0e0;
684
+ border-radius: 10px;
685
+ overflow: hidden;
686
+ margin-bottom: 0.5rem;
687
+ }
688
+ .progress-fill {
689
+ height: 100%;
690
+ background: linear-gradient(90deg, #4caf50, #8bc34a);
691
+ transition: width 0.3s ease;
692
+ }
693
+ .progress-text {
694
+ font-weight: bold;
695
+ color: #333;
696
+ }
697
+ `]
698
+ })
699
+ export class CustomFormattingComponent {
700
+ progressControl = new FormControl(25);
701
+
702
+ formatProgress(value: number): string {
703
+ const percentage = Math.round(value);
704
+ const status = percentage < 25 ? 'Just Started' :
705
+ percentage < 50 ? 'Early Progress' :
706
+ percentage < 75 ? 'Halfway There' :
707
+ percentage < 90 ? 'Almost Done' : 'Complete';
708
+ return `${percentage}% - ${status}`;
709
+ }
710
+ }
711
+ ```
712
+
713
+ ### Dynamic Range Updates
714
+
715
+ ```typescript
716
+ import { Component } from '@angular/core';
717
+ import { FormControl } from '@angular/forms';
718
+
719
+ @Component({
720
+ selector: 'app-dynamic-range',
721
+ template: `
722
+ <app-range-selection-input
723
+ [formControl]="difficultyControl"
724
+ [min]="difficultyRange.min"
725
+ [max]="difficultyRange.max"
726
+ [step]="difficultyRange.step"
727
+ [showTicks]="true"
728
+ label="Difficulty Level"
729
+ [displayFunction]="formatDifficulty">
730
+ </app-range-selection-input>
731
+
732
+ <div class="difficulty-info">
733
+ <h4>Difficulty: {{ formatDifficulty(difficultyControl.value || difficultyRange.min) }}</h4>
734
+ <p>{{ getDifficultyDescription() }}</p>
735
+ </div>
736
+
737
+ <div class="range-controls">
738
+ <label>
739
+ Quick Presets:
740
+ <select (change)="setPreset($event.target.value)">
741
+ <option value="">Choose preset</option>
742
+ <option value="easy">Easy (1-3)</option>
743
+ <option value="medium">Medium (4-6)</option>
744
+ <option value="hard">Hard (7-9)</option>
745
+ </select>
746
+ </label>
747
+ </div>
748
+ `,
749
+ styles: [`
750
+ .difficulty-info {
751
+ margin-top: 1rem;
752
+ padding: 1rem;
753
+ border-radius: 8px;
754
+ text-align: center;
755
+ }
756
+ .difficulty-info[data-level="easy"] {
757
+ background: #e8f5e8;
758
+ border: 2px solid #4caf50;
759
+ color: #2e7d32;
760
+ }
761
+ .difficulty-info[data-level="medium"] {
762
+ background: #fff3e0;
763
+ border: 2px solid #ff9800;
764
+ color: #ef6c00;
765
+ }
766
+ .difficulty-info[data-level="hard"] {
767
+ background: #ffebee;
768
+ border: 2px solid #f44336;
769
+ color: #c62828;
770
+ }
771
+ .range-controls {
772
+ margin-top: 1rem;
773
+ }
774
+ .range-controls select {
775
+ margin-left: 0.5rem;
776
+ padding: 0.25rem;
777
+ }
778
+ `]
779
+ })
780
+ export class DynamicRangeComponent {
781
+ difficultyControl = new FormControl(5);
782
+ difficultyRange = { min: 1, max: 10, step: 1 };
783
+
784
+ formatDifficulty(value: number): string {
785
+ if (value <= 3) return `Easy (${value})`;
786
+ if (value <= 6) return `Medium (${value})`;
787
+ return `Hard (${value})`;
788
+ }
789
+
790
+ getDifficultyDescription(): string {
791
+ const value = this.difficultyControl.value || this.difficultyRange.min;
792
+ if (value <= 3) return 'Simple and straightforward tasks';
793
+ if (value <= 6) return 'Moderate complexity with some challenges';
794
+ return 'Complex tasks requiring significant effort';
795
+ }
796
+
797
+ setPreset(preset: string) {
798
+ const presets = {
799
+ easy: { min: 1, max: 3 },
800
+ medium: { min: 4, max: 6 },
801
+ hard: { min: 7, max: 9 }
802
+ };
803
+
804
+ if (preset && presets[preset as keyof typeof presets]) {
805
+ const range = presets[preset as keyof typeof presets];
806
+ this.difficultyControl.setValue(range.min);
807
+ this.difficultyRange = { ...this.difficultyRange, ...range };
808
+ }
809
+ }
810
+ }
811
+ ```
812
+
813
+ ### Performance Optimization
814
+
815
+ ```typescript
816
+ import { Component, ChangeDetectionStrategy } from '@angular/core';
817
+ import { FormControl } from '@angular/forms';
818
+
819
+ @Component({
820
+ selector: 'app-optimized-range',
821
+ changeDetection: ChangeDetectionStrategy.OnPush,
822
+ template: `
823
+ <app-range-selection-input
824
+ [formControl]="optimizedControl"
825
+ [min]="0"
826
+ [max]="1000000"
827
+ [step]="1000"
828
+ [showTicks]="true"
829
+ label="Large Range Selection"
830
+ [displayFunction]="formatLargeNumber">
831
+ </app-range-selection-input>
832
+ `
833
+ })
834
+ export class OptimizedRangeComponent {
835
+ optimizedControl = new FormControl(500000);
836
+
837
+ formatLargeNumber(value: number): string {
838
+ if (value >= 1000000) {
839
+ return `${(value / 1000000).toFixed(1)}M`;
840
+ } else if (value >= 1000) {
841
+ return `${(value / 1000).toFixed(0)}K`;
842
+ }
843
+ return value.toString();
844
+ }
845
+ }
846
+ ```
847
+
848
+ ---
849
+
850
+ ## Integration Examples
851
+
852
+ ### With Other UI Components
853
+
854
+ ```typescript
855
+ import { Component } from '@angular/core';
856
+
857
+ @Component({
858
+ selector: 'app-integrated-range',
859
+ template: `
860
+ <app-display-card title="Price Configuration">
861
+ <app-range-selection-input
862
+ [formControl]="priceControl"
863
+ [min]="0"
864
+ [max]="1000"
865
+ [step]="10"
866
+ [showTicks]="true"
867
+ label="Price Range"
868
+ [displayFunction]="formatPrice">
869
+ </app-range-selection-input>
870
+
871
+ <div *ngIf="priceControl.value" class="price-summary">
872
+ <h4>Price Configuration</h4>
873
+ <p><strong>Selected Range:</strong> {{ formatPrice(priceControl.value.min) }} - {{ formatPrice(priceControl.value.max) }}</p>
874
+ <p><strong>Average Price:</strong> {{ formatPrice((priceControl.value.min + priceControl.value.max) / 2) }}</p>
875
+ <p><strong>Price Spread:</strong> {{ formatPrice(priceControl.value.max - priceControl.value.min) }}</p>
876
+ </div>
877
+ </app-display-card>
878
+ `
879
+ })
880
+ export class IntegratedRangeComponent {
881
+ priceControl = new FormControl({ min: 100, max: 500 });
882
+
883
+ formatPrice(value: number): string {
884
+ if (typeof value === 'object' && value !== null) {
885
+ return `$${value.min?.toFixed(0)} - $${value.max?.toFixed(0)}`;
886
+ }
887
+ return `$${value.toFixed(0)}`;
888
+ }
889
+ }
890
+ ```
891
+
892
+ ### With State Management
893
+
894
+ ```typescript
895
+ import { Component } from '@angular/core';
896
+ import { Store } from '@ngrx/store';
897
+
898
+ @Component({
899
+ selector: 'app-state-range',
900
+ template: `
901
+ <app-range-selection-input
902
+ [formControl]="volumeControl"
903
+ [data]="volumeOptions$ | async"
904
+ label="Audio Volume"
905
+ placeholder="Adjust volume"
906
+ (valueChange)="onVolumeChange($event)">
907
+ </app-range-selection-input>
908
+ `
909
+ })
910
+ export class StateRangeComponent {
911
+ volumeControl = new FormControl();
912
+ volumeOptions$ = this.store.select(state => state.volumeOptions);
913
+
914
+ constructor(private store: Store) {}
915
+
916
+ onVolumeChange(volume: number) {
917
+ this.store.dispatch(adjustVolume({ volume }));
918
+ }
919
+ }
920
+ ```
921
+
922
+ ---
923
+
924
+ ## Testing
925
+
926
+ ### Unit Testing Example
927
+
928
+ ```typescript
929
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
930
+ import { RangeSelectionInputComponent } from './range-selection-input.component';
931
+ import { ReactiveFormsModule } from '@angular/forms';
932
+
933
+ describe('RangeSelectionInputComponent', () => {
934
+ let component: RangeSelectionInputComponent;
935
+ let fixture: ComponentFixture<RangeSelectionInputComponent>;
936
+
937
+ beforeEach(async () => {
938
+ await TestBed.configureTestingModule({
939
+ declarations: [ RangeSelectionInputComponent ],
940
+ imports: [ ReactiveFormsModule ]
941
+ }).compileComponents();
942
+
943
+ fixture = TestBed.createComponent(RangeSelectionInputComponent);
944
+ component = fixture.componentInstance;
945
+ });
946
+
947
+ it('should create', () => {
948
+ expect(component).toBeTruthy();
949
+ });
950
+
951
+ it('should handle single value selection', () => {
952
+ component.min = 0;
953
+ component.max = 100;
954
+ component.step = 1;
955
+ fixture.detectChanges();
956
+
957
+ component.writeValue(50);
958
+ expect(component.selectedValue).toBe(50);
959
+ });
960
+
961
+ it('should handle range value selection', () => {
962
+ component.min = 0;
963
+ component.max = 100;
964
+ component.step = 1;
965
+ fixture.detectChanges();
966
+
967
+ component.writeValue({ min: 25, max: 75 });
968
+ expect(component.selectedValue).toEqual({ min: 25, max: 75 });
969
+ });
970
+
971
+ it('should emit value changes', () => {
972
+ spyOn(component.valueChange, 'emit');
973
+
974
+ component.onValueChange(50);
975
+
976
+ expect(component.valueChange.emit).toHaveBeenCalledWith(50);
977
+ });
978
+
979
+ it('should handle form control integration', () => {
980
+ const formControl = new FormControl();
981
+ component.formControl = formControl;
982
+
983
+ spyOn(formControl, 'setValue');
984
+ component.writeValue(75);
985
+
986
+ expect(formControl.setValue).toHaveBeenCalledWith(75);
987
+ });
988
+
989
+ it('should apply custom display formatting', () => {
990
+ component.displayFunction = (value: number) => `${value}%`;
991
+ fixture.detectChanges();
992
+
993
+ const formattedValue = component.displayFunction(50);
994
+ expect(formattedValue).toBe('50%');
995
+ });
996
+ });
997
+ ```
998
+
999
+ ---
1000
+
1001
+ ## Troubleshooting
1002
+
1003
+ ### Common Issues
1004
+
1005
+ 1. **Form control not working**: Ensure ReactiveFormsModule is imported
1006
+ 2. **Slider not responding**: Check min/max/step values are properly configured
1007
+ 3. **Display formatting not working**: Verify displayFunction is properly defined
1008
+ 4. **Range selection not working**: Ensure dual-thumb mode is properly configured
1009
+ 5. **Styling issues**: Verify Material theme is properly configured
1010
+
1011
+ ### Debug Mode
1012
+
1013
+ ```typescript
1014
+ @Component({
1015
+ template: `
1016
+ <div class="debug-info">
1017
+ Form Control Value: {{ formControl.value | json }}<br>
1018
+ Form Control Valid: {{ formControl.valid }}<br>
1019
+ Min: {{ min }}<br>
1020
+ Max: {{ max }}<br>
1021
+ Step: {{ step }}<br>
1022
+ Show Ticks: {{ showTicks }}<br>
1023
+ Selected Value: {{ selectedValue | json }}
1024
+ </div>
1025
+
1026
+ <app-range-selection-input
1027
+ [formControl]="formControl"
1028
+ [min]="min"
1029
+ [max]="max"
1030
+ [step]="step"
1031
+ [showTicks]="showTicks"
1032
+ [displayFunction]="displayFunction"
1033
+ (valueChange)="onValueChange($event)">
1034
+ </app-range-selection-input>
1035
+ `
1036
+ })
1037
+ export class DebugRangeComponent {
1038
+ formControl = new FormControl();
1039
+ min = 0;
1040
+ max = 100;
1041
+ step = 1;
1042
+ showTicks = false;
1043
+ selectedValue: any = null;
1044
+
1045
+ displayFunction = (value: number) => `Value: ${value}`;
1046
+
1047
+ constructor() {
1048
+ this.formControl.valueChanges.subscribe(value => {
1049
+ console.log('Range value changed:', value);
1050
+ });
1051
+
1052
+ this.formControl.statusChanges.subscribe(status => {
1053
+ console.log('Form control status:', status);
1054
+ });
1055
+ }
1056
+
1057
+ onValueChange(value: any) {
1058
+ this.selectedValue = value;
1059
+ console.log('Value changed:', value);
1060
+ }
1061
+ }
1062
+ ```
1063
+
1064
+ ### Performance Monitoring
1065
+
1066
+ ```typescript
1067
+ @Component({
1068
+ template: `
1069
+ <div class="performance-info">
1070
+ Range Size: {{ max - min }}<br>
1071
+ Step Count: {{ (max - min) / step }}<br>
1072
+ Update Time: {{ updateTime }}ms<br>
1073
+ Change Operations: {{ changeOperationCount }}
1074
+ </div>
1075
+
1076
+ <app-range-selection-input
1077
+ [formControl]="formControl"
1078
+ [min]="min"
1079
+ [max]="max"
1080
+ [step]="step"
1081
+ (valueChange)="onValueChange($event)">
1082
+ </app-range-selection-input>
1083
+ `
1084
+ })
1085
+ export class PerformanceDebugComponent {
1086
+ formControl = new FormControl();
1087
+ min = 0;
1088
+ max = 1000;
1089
+ step = 1;
1090
+ updateTime = 0;
1091
+ changeOperationCount = 0;
1092
+
1093
+ onValueChange(value: any) {
1094
+ const start = performance.now();
1095
+
1096
+ // Perform value update operation
1097
+ this.processValueUpdate(value);
1098
+
1099
+ const end = performance.now();
1100
+ this.updateTime = end - start;
1101
+ this.changeOperationCount++;
1102
+ }
1103
+
1104
+ private processValueUpdate(value: any) {
1105
+ // Your value processing logic
1106
+ }
1107
+ }
1108
+ ```
1109
+
1110
+ ---
1111
+
1112
+ ## Accessibility Features
1113
+
1114
+ ### ARIA Support
1115
+
1116
+ - Sliders include proper ARIA labels and roles
1117
+ - Keyboard navigation is fully supported (Arrow keys, Page Up/Down, Home/End)
1118
+ - Screen reader friendly with appropriate descriptions
1119
+ - Focus management for keyboard users
1120
+ - Value announcements for screen readers
1121
+
1122
+ ### Keyboard Navigation
1123
+
1124
+ | Key | Action |
1125
+ |-----|--------|
1126
+ | `Arrow Keys` | Adjust slider value by step |
1127
+ | `Page Up/Down` | Adjust by larger increments |
1128
+ | `Home` | Set to minimum value |
1129
+ | `End` | Set to maximum value |
1130
+ | `Space` | Toggle selection (if applicable) |
1131
+
1132
+ ### Best Practices
1133
+
1134
+ 1. **Provide meaningful labels** for slider accessibility
1135
+ 2. **Set appropriate min/max values** for context
1136
+ 3. **Use sensible step values** for precision
1137
+ 4. **Test with screen readers** to ensure proper announcements
1138
+ 5. **Consider keyboard users** for navigation
1139
+ 6. **Use consistent styling** for better visual accessibility