checkbox-selection-input 15.0.4 → 15.0.5

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,1256 @@
1
- # CheckboxSelectionInput
1
+ # Checkbox Selection Input Component
2
2
 
3
- This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.0.
3
+ ## Overview
4
4
 
5
- ## Code scaffolding
5
+ The `checkbox-selection-input` library provides a comprehensive Material Design checkbox component that works seamlessly with Angular forms. It supports multiple selection with validation limits (min/max), disabled states, and both string and object data formats. The component implements Angular's `ControlValueAccessor` and `NG_VALIDATORS` interfaces for seamless form integration and validation.
6
6
 
7
- Run `ng generate component component-name --project checkbox-selection-input` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project checkbox-selection-input`.
8
- > Note: Don't forget to add `--project checkbox-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
+ #### ☑️ Advanced Checkbox Selection Interface
11
10
 
12
- Run `ng build checkbox-selection-input` to build the project. The build artifacts will be stored in the `dist/` directory.
11
+ - **Multiple Selection**: Support for selecting multiple checkboxes simultaneously
12
+ - **Form Validation**: Built-in min/max selection validation with error handling
13
+ - **Flexible Data Support**: Accepts both string arrays and complex object arrays
14
+ - **State Management**: Disabled states, pre-selected items, and dynamic enabling/disabling
15
+ - **Material Design**: Built on Angular Material checkbox foundation
16
+ - **ControlValueAccessor Integration**: Full Angular form control support
17
+ - **Validation Integration**: Native Angular validation system compatibility
18
+ - **Dynamic Control**: Runtime data updates and validation constraint changes
13
19
 
14
- ## Publishing
20
+ #### 🔧 Features
15
21
 
16
- After building your library with `ng build checkbox-selection-input`, go to the dist folder `cd dist/checkbox-selection-input` and run `npm publish`.
22
+ **ControlValueAccessor Implementation** - Works with Angular forms
23
+ ✅ **NG_VALIDATORS Integration** - Native validation support
24
+ ✅ **Material Design Integration** - Uses Angular Material components
25
+ ✅ **Multiple Selection** - Select multiple items with checkboxes
26
+ ✅ **Min/Max Validation selection limits
27
+ ✅** - Configurable **Disable Max Behavior** - Auto-disable checkboxes when max reached
28
+ ✅ **Flexible Data Types** - Support strings and objects
29
+ ✅ **Pre-selection** - Initialize with selected items
30
+ ✅ **Disabled Items** - Mark individual items as non-selectable
31
+ ✅ **Change Detection** - Console logging for debugging
32
+ ✅ **Form Integration** - Reactive and template-driven forms
17
33
 
18
- ## Running unit tests
34
+ ### Key Benefits
19
35
 
20
- Run `ng test checkbox-selection-input` to execute the unit tests via [Karma](https://karma-runner.github.io).
36
+ | Feature | Description |
37
+ |---------|-------------|
38
+ | **Flexible Selection** | Support for multiple item selection with validation |
39
+ | **Rich Validation** | Built-in min/max selection limits with error states |
40
+ | **Data Format Support** | Works with simple strings or complex objects |
41
+ | **State Management** | Disabled states and pre-selection capabilities |
42
+ | **Form Integration** | Seamless Angular form control and validation integration |
43
+ | **Material Design** | Consistent Material Design styling and behavior |
21
44
 
22
- ## Further help
45
+ ---
23
46
 
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.
47
+ ## Demo Component (`CheckboxSelectionDemoComponent`)
48
+
49
+ The demo component showcases 6 different checkbox configurations demonstrating various use cases and validation scenarios.
50
+
51
+ ### Usage
52
+
53
+ To use the demo component in your application:
54
+
55
+ ```html
56
+ <app-checkbox-selection-demo></app-checkbox-selection-demo>
57
+ ```
58
+
59
+ ### Demo Configurations
60
+
61
+ The demo includes 6 different checkbox setups:
62
+
63
+ #### Demo 1: Basic Configuration
64
+ - **No label/placeholder**
65
+ - **Not required**
66
+ - **Basic form control integration**
67
+
68
+ #### Demo 2: Labeled Configuration
69
+ - **With label and placeholder**
70
+ - **Required validation**
71
+ - **Error display toggle**
72
+
73
+ #### Demo 3: Minimum Selection
74
+ - **Label: "Providers"**
75
+ - **Minimum 2 selections required**
76
+ - **Error handling for insufficient selections**
77
+
78
+ #### Demo 4: Maximum Selection
79
+ - **Label: "Providers"**
80
+ - **Maximum 2 selections allowed**
81
+ - **Error handling for exceeding limit**
82
+
83
+ #### Demo 5: Auto-disable at Max
84
+ - **Label: "Providers"**
85
+ - **Maximum 2 selections**
86
+ - **Auto-disable remaining checkboxes when max reached**
87
+
88
+ #### Demo 6: Combined Min/Max
89
+ - **Label: "Providers"**
90
+ - **Minimum 1, Maximum 2 selections**
91
+ - **Auto-disable at max**
92
+ - **Complex validation scenarios**
93
+
94
+ ### Demo Features
95
+
96
+ - **Data Type Toggle**: Switch between string arrays and object arrays
97
+ - **Patch Testing**: Programmatically set values for testing
98
+ - **Enable/Disable Controls**: Test form control state changes
99
+ - **Change Detection**: Console logging of value changes
100
+ - **Error Display**: Toggle error message visibility
101
+ - **Reset Functionality**: Clear all selections
102
+
103
+ ---
104
+
105
+ ## Summary
106
+
107
+ The `checkbox-selection-input` library provides a flexible, Material Design-compliant checkbox component with comprehensive form integration, validation support, and multiple selection capabilities for Angular applications.
108
+
109
+ ---
110
+
111
+ ## Quick Start Guide
112
+
113
+ ### Installation & Setup (2 minutes)
114
+
115
+ #### 1. Import Module
116
+
117
+ ```typescript
118
+ // app.module.ts
119
+ import { CheckboxSelectionInputModule } from 'checkbox-selection-input';
120
+
121
+ @NgModule({
122
+ imports: [
123
+ CheckboxSelectionInputModule
124
+ ]
125
+ })
126
+ export class AppModule { }
127
+ ```
128
+
129
+ #### 2. No Module Configuration Required
130
+
131
+ The `CheckboxSelectionInputModule` does not require global configuration. Components can be used immediately after module import.
132
+
133
+ ### Quick Examples
134
+
135
+ #### Example 1: Basic Multiple Selection
136
+
137
+ ```typescript
138
+ import { Component } from '@angular/core';
139
+ import { FormControl } from '@angular/forms';
140
+
141
+ @Component({
142
+ selector: 'app-basic-checkbox',
143
+ template: `
144
+ <app-checkbox-selection-input
145
+ [formControl]="selectionControl"
146
+ [data]="options">
147
+ </app-checkbox-selection-input>
148
+
149
+ <div>Selected: {{ selectionControl.value | json }}</div>
150
+ `
151
+ })
152
+ export class BasicCheckboxComponent {
153
+ selectionControl = new FormControl();
154
+
155
+ options = ['Option 1', 'Option 2', 'Option 3', 'Option 4'];
156
+ }
157
+ ```
158
+
159
+ #### Example 2: Required Selection with Validation
160
+
161
+ ```typescript
162
+ import { Component } from '@angular/core';
163
+ import { FormControl, Validators } from '@angular/forms';
164
+
165
+ @Component({
166
+ selector: 'app-required-checkbox',
167
+ template: `
168
+ <app-checkbox-selection-input
169
+ [formControl]="requiredControl"
170
+ [data]="providers"
171
+ label="Select Providers"
172
+ placeholder="Choose your providers"
173
+ [minSelection]="2"
174
+ [maxSelection]="3">
175
+ </app-checkbox-selection-input>
176
+
177
+ <div class="errors" *ngIf="requiredControl.errors">
178
+ <div *ngIf="requiredControl.hasError('minRequired')">
179
+ Please select at least 2 providers
180
+ </div>
181
+ <div *ngIf="requiredControl.hasError('maxExceeded')">
182
+ You can select maximum 3 providers
183
+ </div>
184
+ <div *ngIf="requiredControl.hasError('required')">
185
+ Provider selection is required
186
+ </div>
187
+ </div>
188
+ `
189
+ })
190
+ export class RequiredCheckboxComponent {
191
+ requiredControl = new FormControl([], [
192
+ Validators.required,
193
+ Validators.minLength(2),
194
+ Validators.maxLength(3)
195
+ ]);
196
+
197
+ providers = ['Telus', 'AT&T', 'Bell', 'Rogers', 'Verizon'];
198
+ }
199
+ ```
200
+
201
+ #### Example 3: Object Data with Disabled Items
202
+
203
+ ```typescript
204
+ import { Component } from '@angular/core';
205
+ import { FormControl } from '@angular/forms';
206
+ import { SelectionItem } from 'checkbox-selection-input';
207
+
208
+ @Component({
209
+ selector: 'app-object-checkbox',
210
+ template: `
211
+ <app-checkbox-selection-input
212
+ [formControl]="objectControl"
213
+ [data]="userOptions"
214
+ [disableMax]="true">
215
+ </app-checkbox-selection-input>
216
+
217
+ <div>Selected Users: {{ objectControl.value | json }}</div>
218
+ `
219
+ })
220
+ export class ObjectCheckboxComponent {
221
+ objectControl = new FormControl();
222
+
223
+ userOptions = [
224
+ { id: 1, value: 'John Doe', selected: true },
225
+ { id: 2, value: 'Jane Smith', disabled: true },
226
+ { id: 3, value: 'Bob Johnson', selected: true },
227
+ { id: 4, value: 'Alice Brown' }
228
+ ];
229
+ }
230
+ ```
231
+
232
+ #### Example 4: Auto-disable at Maximum
233
+
234
+ ```typescript
235
+ import { Component } from '@angular/core';
236
+ import { FormControl } from '@angular/forms';
237
+
238
+ @Component({
239
+ selector: 'app-auto-disable-checkbox',
240
+ template: `
241
+ <app-checkbox-selection-input
242
+ [formControl]="featuresControl"
243
+ [data]="features"
244
+ label="Select Features"
245
+ [maxSelection]="2"
246
+ [disableMax]="true">
247
+ </app-checkbox-selection-input>
248
+
249
+ <div class="info">
250
+ Selected: {{ featuresControl.value?.length || 0 }} / 2 maximum
251
+ </div>
252
+ `
253
+ })
254
+ export class AutoDisableCheckboxComponent {
255
+ featuresControl = new FormControl();
256
+
257
+ features = [
258
+ 'Dark Mode',
259
+ 'Notifications',
260
+ 'Analytics',
261
+ 'Export Data',
262
+ 'API Access',
263
+ 'Advanced Filters'
264
+ ];
265
+ }
266
+ ```
267
+
268
+ #### Example 5: Dynamic Data with Form Validation
269
+
270
+ ```typescript
271
+ import { Component } from '@angular/core';
272
+ import { FormBuilder, Validators } from '@angular/forms';
273
+ import { SelectionItem } from 'checkbox-selection-input';
274
+
275
+ @Component({
276
+ selector: 'app-dynamic-checkbox',
277
+ template: `
278
+ <form [formGroup]="dynamicForm">
279
+ <app-checkbox-selection-input
280
+ formControlName="permissions"
281
+ [data]="permissionOptions"
282
+ label="User Permissions"
283
+ [minSelection]="1"
284
+ [maxSelection]="4"
285
+ [disableMax]="true">
286
+ </app-checkbox-selection-input>
287
+ </form>
288
+
289
+ <div class="form-status">
290
+ <div>Valid: {{ dynamicForm.get('permissions')?.valid }}</div>
291
+ <div>Touched: {{ dynamicForm.get('permissions')?.touched }}</div>
292
+ <div>Selected Count: {{ dynamicForm.get('permissions')?.value?.length || 0 }}</div>
293
+ </div>
294
+
295
+ <div class="controls">
296
+ <button (click)="addPermission()">Add Permission</button>
297
+ <button (click)="removeLastPermission()">Remove Last</button>
298
+ <button (click)="resetForm()">Reset</button>
299
+ </div>
300
+ `,
301
+ styles: [`
302
+ .controls { margin-top: 1rem; display: flex; gap: 0.5rem; }
303
+ .form-status { margin-top: 1rem; padding: 0.5rem; background: #f5f5f5; }
304
+ `]
305
+ })
306
+ export class DynamicCheckboxComponent {
307
+ constructor(private fb: FormBuilder) {}
308
+
309
+ dynamicForm = this.fb.group({
310
+ permissions: [[], [Validators.required, Validators.minLength(1), Validators.maxLength(4)]]
311
+ });
312
+
313
+ permissionOptions = [
314
+ { id: 1, value: 'Read Files' },
315
+ { id: 2, value: 'Write Files' },
316
+ { id: 3, value: 'Delete Files' },
317
+ { id: 4, value: 'Share Files' },
318
+ { id: 5, value: 'Admin Access' }
319
+ ];
320
+
321
+ addPermission() {
322
+ const availablePermissions = this.permissionOptions.filter(
323
+ p => !this.dynamicForm.get('permissions')?.value?.includes(p.value)
324
+ );
325
+ if (availablePermissions.length > 0) {
326
+ const current = this.dynamicForm.get('permissions')?.value || [];
327
+ this.dynamicForm.get('permissions')?.setValue([...current, availablePermissions[0].value]);
328
+ }
329
+ }
330
+
331
+ removeLastPermission() {
332
+ const current = this.dynamicForm.get('permissions')?.value || [];
333
+ if (current.length > 0) {
334
+ this.dynamicForm.get('permissions')?.setValue(current.slice(0, -1));
335
+ }
336
+ }
337
+
338
+ resetForm() {
339
+ this.dynamicForm.get('permissions')?.reset();
340
+ }
341
+ }
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Component API
347
+
348
+ ### Inputs
349
+
350
+ | Input | Type | Description | Default |
351
+ | :--- | :--- | :--- | :--- |
352
+ | `data` | `any[] \| string[]` | Array of items or strings defining checkbox options | (Required) |
353
+ | `label` | `string` | Optional label text for the checkbox group | `undefined` |
354
+ | `placeholder` | `string` | Optional placeholder text | `undefined` |
355
+ | `error` | `string` | Optional error message to display | `undefined` |
356
+ | `disableMax` | `boolean` | If true, disables checkboxes when max selection is reached | `false` |
357
+ | `useDefaultReset` | `boolean` | Use default reset behavior | `false` |
358
+ | `minSelection` | `number` | Minimum number of selections required | `0` |
359
+ | `maxSelection` | `number` | Maximum number of selections allowed | `0` |
360
+
361
+ ### Outputs
362
+
363
+ | Output | Type | Description |
364
+ | :--- | :--- | :--- |
365
+ | `selectionChange` | `EventEmitter<string[]>` | Emits array of selected values when selection changes |
366
+
367
+ ### Form Control Integration
368
+
369
+ The component works with Angular form controls and emits:
370
+
371
+ - **Single array value**: Array of selected values (strings or objects based on input data)
372
+ - **Validation state**: Integrates with Angular's validation system
373
+ - **Touch/dirty states**: Properly tracks form control states
374
+
375
+ ---
376
+
377
+ ## Model Structures
378
+
379
+ ### SelectionItem Interface
380
+
381
+ ```typescript
382
+ export interface SelectionItemInterface {
383
+ id: number | string; // Unique identifier for the item
384
+ value: string; // The value to be selected/returned
385
+ disabled?: boolean; // Whether this item is disabled
386
+ selected?: boolean; // Whether this item is pre-selected
387
+ }
388
+ ```
389
+
390
+ ### SelectionItem Class
391
+
392
+ ```typescript
393
+ export class SelectionItem implements SelectionItemInterface {
394
+ constructor(
395
+ public id = crypto.randomUUID(), // Auto-generates UUID if not provided
396
+ public value = '',
397
+ public disabled?: boolean = false,
398
+ public selected?: boolean = false,
399
+ ) {}
400
+
401
+ static adapt(item?: any): SelectionItem {
402
+ return new SelectionItem(
403
+ item?.id, // Use provided ID or undefined
404
+ (item?.value) ? item.value : item, // Use value or fallback to item itself
405
+ (item?.disabled) ? true : false, // Convert to boolean
406
+ (item?.selected) ? true : false, // Convert to boolean
407
+ );
408
+ }
409
+ }
410
+ ```
411
+
412
+ ### Usage Examples
413
+
414
+ ```typescript
415
+ // String array data (automatically converted to SelectionItem)
416
+ const stringData = ['Option 1', 'Option 2', 'Option 3'];
417
+
418
+ // Object array data
419
+ const objectData = [
420
+ { id: 1, value: 'Telus', selected: true },
421
+ { id: 2, value: 'AT&T', disabled: true },
422
+ { id: 3, value: 'Bell' }
423
+ ];
424
+
425
+ // Manual SelectionItem creation
426
+ const manualItems = [
427
+ new SelectionItem('1', 'Option 1', false, true),
428
+ new SelectionItem('2', 'Option 2', true, false),
429
+ new SelectionItem('3', 'Option 3', false, false)
430
+ ];
431
+
432
+ // Using adapt method for flexible data
433
+ const adaptedItems = [
434
+ SelectionItem.adapt({ value: 'Item 1', selected: true }),
435
+ SelectionItem.adapt({ id: 'custom-id', value: 'Item 2', disabled: true }),
436
+ SelectionItem.adapt('Item 3') // String fallback
437
+ ];
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Form Integration
443
+
444
+ ### ControlValueAccessor Implementation
445
+
446
+ The component implements Angular's `ControlValueAccessor` interface:
447
+
448
+ ```typescript
449
+ // writeValue(value: string[]): void
450
+ // Sets the value of the control (array of selected values)
451
+ writeValue(value: string[]): void {
452
+ // Handle incoming form control value
453
+ // Update component state and checkbox states
454
+ }
455
+
456
+ // registerOnChange(fn: any): void
457
+ // Registers a callback for value changes
458
+ registerOnChange(fn: any): void {
459
+ this.onChange = fn;
460
+ }
461
+
462
+ // registerOnTouched(fn: any): void
463
+ // Registers a callback for touch events
464
+ registerOnTouched(fn: any): void {
465
+ this.onTouch = fn;
466
+ }
467
+
468
+ // setDisabledState(isDisabled: boolean): void
469
+ // Sets disabled state for entire component
470
+ setDisabledState(isDisabled: boolean): void {
471
+ this.disabled = isDisabled;
472
+ if (this.disabled) {
473
+ this.selectionControl.disable();
474
+ } else {
475
+ this.selectionControl.enable();
476
+ }
477
+ }
478
+ ```
479
+
480
+ ### Validation Integration
481
+
482
+ The component also implements `NG_VALIDATORS`:
483
+
484
+ ```typescript
485
+ // validate(control: AbstractControl): ValidationErrors | null
486
+ // Performs validation and returns errors if any
487
+ validate(control: AbstractControl): ValidationErrors | null {
488
+ // Check min/max selection requirements
489
+ // Return validation errors or null
490
+ return { minRequired: true, maxExceeded: false };
491
+ }
492
+ ```
493
+
494
+ ### Form Integration Examples
495
+
496
+ #### Reactive Forms
497
+
498
+ ```typescript
499
+ import { Component } from '@angular/core';
500
+ import { FormControl, FormGroup, Validators } from '@angular/forms';
501
+
502
+ @Component({
503
+ selector: 'app-reactive-checkbox',
504
+ template: `
505
+ <form [formGroup]="checkboxForm">
506
+ <app-checkbox-selection-input
507
+ formControlName="selections"
508
+ [data]="options"
509
+ label="Select Options"
510
+ [minSelection]="2"
511
+ [maxSelection]="4"
512
+ [disableMax]="true">
513
+ </app-checkbox-selection-input>
514
+
515
+ <div class="errors" *ngIf="checkboxForm.get('selections')?.errors">
516
+ <div *ngIf="checkboxForm.get('selections')?.hasError('minRequired')">
517
+ Please select at least 2 options
518
+ </div>
519
+ <div *ngIf="checkboxForm.get('selections')?.hasError('maxExceeded')">
520
+ Maximum selections exceeded
521
+ </div>
522
+ </div>
523
+ </form>
524
+ `
525
+ })
526
+ export class ReactiveCheckboxComponent {
527
+ checkboxForm = new FormGroup({
528
+ selections: new FormControl([], [
529
+ Validators.required,
530
+ Validators.minLength(2),
531
+ Validators.maxLength(4)
532
+ ])
533
+ });
534
+
535
+ options = ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5'];
536
+ }
537
+ ```
538
+
539
+ #### Template-Driven Forms
540
+
541
+ ```typescript
542
+ import { Component } from '@angular/core';
543
+
544
+ @Component({
545
+ selector: 'app-template-checkbox',
546
+ template: `
547
+ <form #checkboxForm="ngForm">
548
+ <app-checkbox-selection-input
549
+ [(ngModel)]="selectedValues"
550
+ name="checkboxSelections"
551
+ [data]="options"
552
+ label="Template Driven Selection"
553
+ [minSelection]="1"
554
+ [maxSelection]="3"
555
+ required>
556
+ </app-checkbox-selection-input>
557
+
558
+ <div *ngIf="checkboxForm.controls.checkboxSelections?.invalid &&
559
+ checkboxForm.controls.checkboxSelections?.touched">
560
+ Please make a selection
561
+ </div>
562
+ </form>
563
+ `
564
+ })
565
+ export class TemplateCheckboxComponent {
566
+ selectedValues: string[] = [];
567
+
568
+ options = ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4'];
569
+ }
570
+ ```
571
+
572
+ #### Programmatic Control
573
+
574
+ ```typescript
575
+ // Setting values
576
+ this.formControl.setValue(['Option 1', 'Option 2']);
577
+ this.formControl.patchValue(['Option 1']); // Partial update
578
+
579
+ // Resetting
580
+ this.formControl.reset();
581
+
582
+ // Getting current value
583
+ const currentValue = this.formControl.value;
584
+
585
+ // Validation
586
+ const isValid = this.formControl.valid;
587
+ const errors = this.formControl.errors;
588
+
589
+ // Setting custom errors
590
+ this.formControl.setErrors({ customError: 'Custom error message' });
591
+ ```
592
+
593
+ ---
594
+
595
+ ## Validation System
596
+
597
+ ### Built-in Validation
598
+
599
+ The component provides built-in validation for:
600
+
601
+ #### Min Selection Validation
602
+ ```typescript
603
+ // Requires at least X selections
604
+ [minSelection]="2" // Must select at least 2 items
605
+ ```
606
+
607
+ #### Max Selection Validation
608
+ ```typescript
609
+ // Allows maximum X selections
610
+ [maxSelection]="3" // Cannot select more than 3 items
611
+ ```
612
+
613
+ #### Disable Max Behavior
614
+ ```typescript
615
+ // Auto-disable remaining checkboxes when max reached
616
+ [disableMax]="true" // Disables unchecked boxes at max
617
+ ```
618
+
619
+ ### Validation Error Types
620
+
621
+ | Error Type | Condition | Description |
622
+ |------------|-----------|-------------|
623
+ | `minRequired` | `selectedCount < minSelection` | Not enough selections made |
624
+ | `maxExceeded` | `selectedCount >= maxSelection` | Too many selections made |
625
+ | `required` | Control has required validator and no selections | Control is required but empty |
626
+
627
+ ### Custom Validation Examples
628
+
629
+ ```typescript
630
+ // Complex validation scenarios
631
+ const complexForm = new FormGroup({
632
+ permissions: new FormControl([], [
633
+ Validators.required,
634
+ Validators.minLength(1), // At least 1 selection
635
+ Validators.maxLength(5), // Maximum 5 selections
636
+ customMaxValidator(3) // Custom: Cannot select admin + delete together
637
+ ])
638
+ });
639
+
640
+ // Custom validator example
641
+ function customMaxValidator(maxCount: number) {
642
+ return (control: AbstractControl): ValidationErrors | null => {
643
+ const value = control.value;
644
+ if (value && value.length > maxCount) {
645
+ return { customMaxExceeded: true };
646
+ }
647
+ return null;
648
+ };
649
+ }
650
+ ```
651
+
652
+ ---
653
+
654
+ ## Module Configuration
655
+
656
+ ### CheckboxSelectionInputModule
657
+
658
+ **No Global Configuration Required**
659
+
660
+ The `CheckboxSelectionInputModule` does not provide a `forRoot()` method or global configuration options. All configuration is done at the component level through input properties.
661
+
662
+ #### Module Structure
663
+
664
+ ```typescript
665
+ @NgModule({
666
+ imports: [
667
+ CommonModule,
668
+ FormsModule,
669
+ ReactiveFormsModule,
670
+ MatSliderModule,
671
+ MatButtonModule,
672
+ MatIconModule,
673
+ MatFormFieldModule,
674
+ MatToolbarModule,
675
+ MatCheckboxModule,
676
+ MatMenuModule,
677
+ MatButtonToggleModule,
678
+ MatDividerModule,
679
+ MatRadioModule,
680
+ MatInputModule,
681
+ MatAutocompleteModule,
682
+ RemoveUnderscorePipe,
683
+ MatSelectModule,
684
+ MatOptionModule,
685
+ MatSlideToggleModule,
686
+ ],
687
+ declarations: [
688
+ CheckboxSelectionInputComponent,
689
+ CheckboxSelectionDemoComponent,
690
+ ],
691
+ exports: [
692
+ CheckboxSelectionInputComponent,
693
+ CheckboxSelectionDemoComponent
694
+ ]
695
+ })
696
+ export class CheckboxSelectionInputModule { }
697
+ ```
698
+
699
+ #### Dependencies
700
+
701
+ - **@angular/common**: Core Angular functionality
702
+ - **@angular/forms**: Form control integration (FormsModule, ReactiveFormsModule)
703
+ - **@angular/material**: Material Design components
704
+ - MatCheckboxModule: Checkbox components
705
+ - MatFormFieldModule: Form field styling
706
+ - MatButtonModule: Button components
707
+ - MatIconModule: Icon display
708
+ - MatSlideToggleModule: Toggle switches for demo
709
+ - MatDividerModule: Visual dividers
710
+ - Additional Material modules for comprehensive UI support
711
+
712
+ ---
713
+
714
+ ## Styling and Customization
715
+
716
+ ### CSS Classes and Styling
717
+
718
+ The component uses Material Design styling and can be customized using:
719
+
720
+ 1. **Global Material Theme**: Configure colors in your Angular Material theme
721
+ 2. **Component-specific Styles**: Add custom CSS classes
722
+ 3. **Form Field Styling**: Style using Material form field classes
723
+ 4. **Checkbox Styling**: Customize individual checkbox appearance
724
+
725
+ ### Custom Styling Examples
726
+
727
+ ```scss
728
+ // Custom checkbox group styling
729
+ :host ::ng-deep .checkbox-selection-input {
730
+ .mat-form-field {
731
+ .mat-form-field-label {
732
+ color: #2196f3;
733
+ font-weight: 500;
734
+ }
735
+ }
736
+
737
+ .mat-checkbox {
738
+ margin-bottom: 8px;
739
+
740
+ &.mat-checkbox-disabled {
741
+ .mat-checkbox-label {
742
+ opacity: 0.6;
743
+ }
744
+ }
745
+ }
746
+ }
747
+
748
+ // Custom disabled state styling
749
+ :host ::ng-deep .checkbox-selection-input {
750
+ .mat-checkbox-disabled {
751
+ .mat-checkbox-frame {
752
+ border-color: #ccc;
753
+ }
754
+
755
+ .mat-checkbox-label {
756
+ color: #999;
757
+ }
758
+ }
759
+ }
760
+ ```
761
+
762
+ ### Layout Customization
763
+
764
+ ```scss
765
+ // Horizontal layout
766
+ .horizontal-checkboxes {
767
+ app-checkbox-selection-input {
768
+ .mat-checkbox {
769
+ display: inline-block;
770
+ margin-right: 16px;
771
+ margin-bottom: 0;
772
+ }
773
+ }
774
+ }
775
+
776
+ // Compact layout
777
+ .compact-checkboxes {
778
+ app-checkbox-selection-input {
779
+ .mat-checkbox {
780
+ margin-bottom: 4px;
781
+
782
+ .mat-checkbox-label {
783
+ font-size: 0.875rem;
784
+ }
785
+ }
786
+ }
787
+ }
788
+ ```
789
+
790
+ ---
791
+
792
+ ## Accessibility
793
+
794
+ ### ARIA Support
795
+
796
+ - Checkboxes include proper ARIA labels and roles
797
+ - Group labeling through label input property
798
+ - Keyboard navigation is fully supported (Tab, Space, Arrow keys)
799
+ - Screen reader friendly with appropriate descriptions
800
+ - Validation errors are announced to assistive technologies
801
+ - Disabled states are properly communicated
802
+
803
+ ### Best Practices
804
+
805
+ 1. **Provide meaningful labels** for the checkbox group
806
+ 2. **Use descriptive placeholders** for additional context
807
+ 3. **Set appropriate validation messages** for accessibility
808
+ 4. **Consider keyboard navigation** order
809
+ 5. **Test with screen readers** to ensure proper announcements
810
+ 6. **Use logical grouping** for related checkbox options
811
+
812
+ ### Keyboard Navigation
813
+
814
+ | Key | Action |
815
+ |-----|--------|
816
+ | `Tab` | Navigate to next checkbox |
817
+ | `Shift+Tab` | Navigate to previous checkbox |
818
+ | `Space` | Toggle checkbox selection |
819
+ | `Enter` | Toggle checkbox selection (in some contexts) |
820
+
821
+ ---
822
+
823
+ ## Integration Examples
824
+
825
+ ### With Other UI Components
826
+
827
+ ```typescript
828
+ // Integration with display-card
829
+ @Component({
830
+ template: `
831
+ <app-display-card title="User Permissions">
832
+ <app-checkbox-selection-input
833
+ [data]="permissionOptions"
834
+ [formControl]="permissionControl"
835
+ label="Select Permissions"
836
+ [minSelection]="1"
837
+ [maxSelection]="3"
838
+ [disableMax]="true">
839
+ </app-checkbox-selection-input>
840
+ </app-display-card>
841
+ `
842
+ })
843
+ export class CardWithCheckboxComponent {
844
+ permissionControl = new FormControl();
845
+
846
+ permissionOptions = [
847
+ { id: 1, value: 'Read Access' },
848
+ { id: 2, value: 'Write Access' },
849
+ { id: 3, value: 'Delete Access' },
850
+ { id: 4, value: 'Admin Access' }
851
+ ];
852
+ }
853
+ ```
854
+
855
+ ### With State Management
856
+
857
+ ```typescript
858
+ // Integration with HTTP Request Manager
859
+ @Component({
860
+ template: `
861
+ <app-checkbox-selection-input
862
+ [data]="categoryOptions$ | async"
863
+ [formControl]="categoryControl"
864
+ [multiple]="true"
865
+ (selectionChange)="handleSelectionChange($event)">
866
+ </app-checkbox-selection-input>
867
+ `
868
+ })
869
+ export class StateManagedCheckboxComponent {
870
+ categoryOptions$ = this.categoryStore.options$;
871
+ categoryControl = new FormControl();
872
+
873
+ constructor(private categoryStore: CategoryStore) {}
874
+
875
+ handleSelectionChange(selectedValues: string[]) {
876
+ this.categoryStore.updateSelection(selectedValues);
877
+ }
878
+ }
879
+ ```
880
+
881
+ ### With Dynamic Forms
882
+
883
+ ```typescript
884
+ @Component({
885
+ template: `
886
+ <div formArrayName="checkboxGroups">
887
+ <div *ngFor="let group of checkboxGroups.controls; let i = index">
888
+ <app-checkbox-selection-input
889
+ [formControlName]="i"
890
+ [data]="dynamicOptions[i]"
891
+ [label]="'Group ' + (i + 1)">
892
+ </app-checkbox-selection-input>
893
+ </div>
894
+ </div>
895
+ `
896
+ })
897
+ export class DynamicFormCheckboxComponent {
898
+ checkboxForm = this.fb.group({
899
+ checkboxGroups: this.fb.array([
900
+ this.fb.control(['option1']),
901
+ this.fb.control(['option2', 'option3']),
902
+ this.fb.control([])
903
+ ])
904
+ });
905
+
906
+ get checkboxGroups() {
907
+ return this.checkboxForm.get('checkboxGroups') as FormArray;
908
+ }
909
+
910
+ dynamicOptions = [
911
+ ['Option 1A', 'Option 1B', 'Option 1C'],
912
+ ['Option 2A', 'Option 2B'],
913
+ ['Option 3A', 'Option 3B', 'Option 3C', 'Option 3D']
914
+ ];
915
+ }
916
+ ```
917
+
918
+ ---
919
+
920
+ ## Performance Optimization
921
+
922
+ ### Performance Tips
923
+
924
+ 1. **Use OnPush change detection** for better performance with large checkbox arrays
925
+ 2. **Implement trackBy** for dynamic checkbox lists (if applicable)
926
+ 3. **Avoid frequent data object recreation** to prevent unnecessary re-renders
927
+ 4. **Use immutable data patterns** for checkbox option updates
928
+ 5. **Consider virtual scrolling** for very large checkbox lists
929
+ 6. **Optimize validation** to avoid expensive operations on every change
930
+
931
+ ### Memory Management
932
+
933
+ ```typescript
934
+ // Efficient data updates
935
+ updateOptions(newOptions: any[]) {
936
+ // Create new array reference to trigger change detection
937
+ this.options = [...newOptions];
938
+ }
939
+
940
+ // Cleanup in ngOnDestroy
941
+ ngOnDestroy() {
942
+ this.subscriptions.forEach(sub => sub.unsubscribe());
943
+ }
944
+ ```
945
+
946
+ ---
947
+
948
+ ## Testing
949
+
950
+ ### Unit Testing Example
951
+
952
+ ```typescript
953
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
954
+ import { CheckboxSelectionInputComponent } from './checkbox-selection-input.component';
955
+ import { ReactiveFormsModule } from '@angular/forms';
956
+
957
+ describe('CheckboxSelectionInputComponent', () => {
958
+ let component: CheckboxSelectionInputComponent;
959
+ let fixture: ComponentFixture<CheckboxSelectionInputComponent>;
960
+
961
+ beforeEach(async () => {
962
+ await TestBed.configureTestingModule({
963
+ declarations: [ CheckboxSelectionInputComponent ],
964
+ imports: [ ReactiveFormsModule ]
965
+ }).compileComponents();
966
+
967
+ fixture = TestBed.createComponent(CheckboxSelectionInputComponent);
968
+ component = fixture.componentInstance;
969
+ });
970
+
971
+ it('should create', () => {
972
+ expect(component).toBeTruthy();
973
+ });
974
+
975
+ it('should display checkboxes from data input', () => {
976
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
977
+ fixture.detectChanges();
978
+
979
+ const compiled = fixture.nativeElement;
980
+ const checkboxes = compiled.querySelectorAll('.mat-checkbox');
981
+ expect(checkboxes.length).toBe(3);
982
+ });
983
+
984
+ it('should emit selection changes', () => {
985
+ spyOn(component.selectionChange, 'emit');
986
+ component.data = ['Option 1', 'Option 2'];
987
+ component.writeValue(['Option 1']);
988
+ fixture.detectChanges();
989
+
990
+ expect(component.selectionChange.emit).toHaveBeenCalledWith(['Option 1']);
991
+ });
992
+
993
+ it('should validate minimum selections', () => {
994
+ component.minSelection = 2;
995
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
996
+
997
+ // Simulate selecting only 1 item
998
+ component.writeValue(['Option 1']);
999
+
1000
+ const validationResult = component.validate({} as AbstractControl);
1001
+ expect(validationResult?.minRequired).toBe(true);
1002
+ });
1003
+
1004
+ it('should validate maximum selections', () => {
1005
+ component.maxSelection = 2;
1006
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
1007
+
1008
+ // Simulate selecting 3 items when max is 2
1009
+ component.writeValue(['Option 1', 'Option 2', 'Option 3']);
1010
+
1011
+ const validationResult = component.validate({} as AbstractControl);
1012
+ expect(validationResult?.maxExceeded).toBe(true);
1013
+ });
1014
+ });
1015
+ ```
1016
+
1017
+ ### Integration Testing
1018
+
1019
+ ```typescript
1020
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
1021
+ import { CheckboxSelectionInputModule } from './checkbox-selection-input.module';
1022
+
1023
+ describe('CheckboxSelectionInput Integration', () => {
1024
+ let component: TestHostComponent;
1025
+ let fixture: ComponentFixture<TestHostComponent>;
1026
+
1027
+ @Component({
1028
+ template: `
1029
+ <app-checkbox-selection-input
1030
+ [formControl]="testControl"
1031
+ [data]="testData"
1032
+ [minSelection]="1"
1033
+ [maxSelection]="2">
1034
+ </app-checkbox-selection-input>
1035
+ `
1036
+ })
1037
+ class TestHostComponent {
1038
+ testControl = new FormControl();
1039
+ testData = ['Test 1', 'Test 2', 'Test 3'];
1040
+ }
1041
+
1042
+ beforeEach(async () => {
1043
+ await TestBed.configureTestingModule({
1044
+ declarations: [ TestHostComponent ],
1045
+ imports: [ CheckboxSelectionInputModule ]
1046
+ }).compileComponents();
1047
+
1048
+ fixture = TestBed.createComponent(TestHostComponent);
1049
+ component = fixture.componentInstance;
1050
+ });
1051
+
1052
+ it('should integrate with form controls', () => {
1053
+ expect(component.testControl).toBeDefined();
1054
+ expect(component.testData.length).toBe(3);
1055
+ });
1056
+
1057
+ it('should update form control value when selection changes', () => {
1058
+ fixture.detectChanges();
1059
+
1060
+ // Simulate user interaction
1061
+ const checkboxes = fixture.nativeElement.querySelectorAll('.mat-checkbox');
1062
+ checkboxes[0].click(); // Select first checkbox
1063
+
1064
+ expect(component.testControl.value).toEqual(['Test 1']);
1065
+ });
1066
+
1067
+ it('should enforce validation constraints', () => {
1068
+ fixture.detectChanges();
1069
+
1070
+ // Test minimum selection validation
1071
+ component.testControl.setValue([]);
1072
+ expect(component.testControl.valid).toBe(false);
1073
+
1074
+ // Test maximum selection validation
1075
+ component.testControl.setValue(['Test 1', 'Test 2', 'Test 3']);
1076
+ expect(component.testControl.valid).toBe(false);
1077
+
1078
+ // Test valid selection
1079
+ component.testControl.setValue(['Test 1', 'Test 2']);
1080
+ expect(component.testControl.valid).toBe(true);
1081
+ });
1082
+ });
1083
+ ```
1084
+
1085
+ ---
1086
+
1087
+ ## Troubleshooting
1088
+
1089
+ ### Common Issues
1090
+
1091
+ 1. **Form control not working**: Ensure ReactiveFormsModule is imported
1092
+ 2. **Validation not triggering**: Check that validators are properly configured
1093
+ 3. **Selection not updating**: Verify data format matches expected structure
1094
+ 4. **Styling issues**: Ensure Material theme is properly configured
1095
+ 5. **Auto-disable not working**: Check disableMax input property
1096
+ 6. **Performance issues**: Consider OnPush change detection for large datasets
1097
+
1098
+ ### Debug Mode
1099
+
1100
+ ```typescript
1101
+ // Add debugging to track form control changes and validation
1102
+ @Component({
1103
+ template: `
1104
+ <div class="debug-info">
1105
+ Form Control Value: {{ formControl.value | json }}<br>
1106
+ Form Control Valid: {{ formControl.valid }}<br>
1107
+ Form Control Errors: {{ formControl.errors | json }}<br>
1108
+ Data Length: {{ data?.length || 0 }}<br>
1109
+ Min Selection: {{ minSelection }}<br>
1110
+ Max Selection: {{ maxSelection }}<br>
1111
+ Disable Max: {{ disableMax }}
1112
+ </div>
1113
+
1114
+ <app-checkbox-selection-input
1115
+ [formControl]="formControl"
1116
+ [data]="data"
1117
+ [minSelection]="minSelection"
1118
+ [maxSelection]="maxSelection"
1119
+ [disableMax]="disableMax">
1120
+ </app-checkbox-selection-input>
1121
+ `
1122
+ })
1123
+ export class DebugCheckboxComponent {
1124
+ formControl = new FormControl();
1125
+ data: string[] = [];
1126
+ minSelection = 0;
1127
+ maxSelection = 0;
1128
+ disableMax = false;
1129
+
1130
+ constructor() {
1131
+ this.formControl.valueChanges.subscribe(value => {
1132
+ console.log('Checkbox value changed:', value);
1133
+ });
1134
+
1135
+ this.formControl.statusChanges.subscribe(status => {
1136
+ console.log('Form control status:', status);
1137
+ });
1138
+ }
1139
+ }
1140
+ ```
1141
+
1142
+ ### Validation Debugging
1143
+
1144
+ ```typescript
1145
+ // Debug validation logic
1146
+ validate(control: AbstractControl): ValidationErrors | null {
1147
+ console.log('Validating with:', {
1148
+ selectedCount: this.selectedCheckboxes(this.selectionControl.value).length,
1149
+ minSelection: this.minSelection,
1150
+ maxSelection: this.maxSelection,
1151
+ currentValue: this.selectionControl.value
1152
+ });
1153
+
1154
+ // ... validation logic
1155
+
1156
+ const errors = this.selectedValues(this.selectionControl.value, true).length > 0 ? errors : null;
1157
+ console.log('Validation result:', errors);
1158
+
1159
+ return errors;
1160
+ }
1161
+ ```
1162
+
1163
+ ### Performance Debugging
1164
+
1165
+ ```typescript
1166
+ // Monitor change detection performance
1167
+ ngAfterViewInit() {
1168
+ // Track rendering time
1169
+ const start = performance.now();
1170
+
1171
+ setTimeout(() => {
1172
+ const end = performance.now();
1173
+ console.log(`Checkbox rendering took ${end - start}ms`);
1174
+ });
1175
+ }
1176
+ ```
1177
+
1178
+ ---
1179
+
1180
+ ## Advanced Usage Patterns
1181
+
1182
+ ### Conditional Validation
1183
+
1184
+ ```typescript
1185
+ // Complex validation scenarios
1186
+ @Component({
1187
+ template: `
1188
+ <app-checkbox-selection-input
1189
+ [formControl]="conditionalControl"
1190
+ [data]="conditionalOptions"
1191
+ [minSelection]="getMinSelection()"
1192
+ [maxSelection]="getMaxSelection()"
1193
+ [disableMax]="shouldDisableMax()">
1194
+ </app-checkbox-selection-input>
1195
+ `
1196
+ })
1197
+ export class ConditionalCheckboxComponent {
1198
+ conditionalControl = new FormControl();
1199
+
1200
+ getMinSelection(): number {
1201
+ const userRole = this.getUserRole();
1202
+ return userRole === 'admin' ? 1 : 2;
1203
+ }
1204
+
1205
+ getMaxSelection(): number {
1206
+ const subscriptionLevel = this.getSubscriptionLevel();
1207
+ return subscriptionLevel === 'premium' ? 5 : 3;
1208
+ }
1209
+
1210
+ shouldDisableMax(): boolean {
1211
+ return true;
1212
+ }
1213
+ }
1214
+ ```
1215
+
1216
+ ### Data Transformation
1217
+
1218
+ ```typescript
1219
+ // Transform data before passing to component
1220
+ transformData(rawData: any[]): any[] {
1221
+ return rawData.map(item => {
1222
+ if (typeof item === 'string') {
1223
+ return {
1224
+ id: this.generateId(item),
1225
+ value: item,
1226
+ selected: this.isPreSelected(item),
1227
+ disabled: this.isDisabled(item)
1228
+ };
1229
+ }
1230
+ return SelectionItem.adapt(item);
1231
+ });
1232
+ }
1233
+ ```
1234
+
1235
+ ### Custom Validation Messages
1236
+
1237
+ ```typescript
1238
+ // Dynamic error messages
1239
+ getErrorMessage(control: AbstractControl): string {
1240
+ if (control.hasError('minRequired')) {
1241
+ const min = this.minSelection;
1242
+ return `Please select at least ${min} option${min > 1 ? 's' : ''}`;
1243
+ }
1244
+
1245
+ if (control.hasError('maxExceeded')) {
1246
+ const max = this.maxSelection;
1247
+ return `You can select maximum ${max} option${max > 1 ? 's' : ''}`;
1248
+ }
1249
+
1250
+ if (control.hasError('required')) {
1251
+ return 'Please make a selection';
1252
+ }
1253
+
1254
+ return 'Invalid selection';
1255
+ }
1256
+ ```