button-toggle-input 15.0.3 → 15.0.4
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 +1045 -72
- package/button-toggle-input-15.0.4.tgz +0 -0
- package/fesm2022/button-toggle-input.mjs +23 -23
- package/fesm2022/button-toggle-input.mjs.map +1 -1
- package/package.json +12 -5
- package/types/button-toggle-input.d.ts +167 -0
- package/button-toggle-input-15.0.3.tgz +0 -0
- package/esm2022/button-toggle-input.mjs +0 -5
- package/esm2022/lib/button-toggle-input-demo/button-toggle-input-demo.component.mjs +0 -94
- package/esm2022/lib/button-toggle-input.component.mjs +0 -155
- package/esm2022/lib/button-toggle-input.module.mjs +0 -60
- package/esm2022/lib/models/list-item.model.mjs +0 -14
- package/esm2022/public-api.mjs +0 -8
- package/index.d.ts +0 -5
- package/lib/button-toggle-input-demo/button-toggle-input-demo.component.d.ts +0 -80
- package/lib/button-toggle-input.component.d.ts +0 -53
- package/lib/button-toggle-input.module.d.ts +0 -16
- package/lib/models/list-item.model.d.ts +0 -18
- package/public-api.d.ts +0 -4
package/README.md
CHANGED
|
@@ -1,105 +1,1078 @@
|
|
|
1
|
-
# Button Toggle
|
|
1
|
+
# Button Toggle Input Component
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The `button-toggle-input` library provides a comprehensive Material Design toggle button component that works seamlessly with Angular forms. It supports both single and multiple selection modes, customizable styling, tooltips, and various display options including icons, labels, and borders. The component implements Angular's `ControlValueAccessor` interface for seamless form integration.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Core Capabilities
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
#### 🎯 Advanced Toggle Button Interface
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
11
|
+
- **Single/Multiple Selection**: Support for both single selection and multi-select modes
|
|
12
|
+
- **Rich Display Options**: Icons, labels, tooltips, and custom styling
|
|
13
|
+
- **Form Control Integration**: Native Angular form control support with ControlValueAccessor
|
|
14
|
+
- **Material Design**: Built on Angular Material button toggle foundation
|
|
15
|
+
- **Customizable Styling**: Colors, borders, width, and icon positioning
|
|
16
|
+
- **Tooltip Support**: Contextual tooltips with configurable positioning
|
|
17
|
+
- **Accessibility Ready**: ARIA labels and keyboard navigation support
|
|
18
|
+
|
|
19
|
+
#### 🔧 Features
|
|
20
|
+
|
|
21
|
+
✅ **ControlValueAccessor Implementation** - Works with Angular forms
|
|
22
|
+
✅ **Material Design Integration** - Uses Angular Material components
|
|
23
|
+
✅ **Single & Multiple Selection** - Flexible selection modes
|
|
24
|
+
✅ **Icon Support** - Material icons with prefix/suffix positioning
|
|
25
|
+
✅ **Tooltip Integration** - Contextual help with positioning options
|
|
26
|
+
✅ **Custom Color Schemes** - Light/dark colors and border control
|
|
27
|
+
✅ **Full Width Option** - Responsive width control
|
|
28
|
+
✅ **Change Detection Optimized** - OnPush change detection strategy
|
|
29
|
+
✅ **Form Validation Support** - Integrates with Angular validation
|
|
30
|
+
|
|
31
|
+
### Key Benefits
|
|
32
|
+
|
|
33
|
+
| Feature | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| **Flexible Selection** | Support for single or multiple item selection |
|
|
36
|
+
| **Rich UI Options** | Icons, labels, tooltips, and styling customization |
|
|
37
|
+
| **Form Integration** | Seamless Angular form control integration |
|
|
38
|
+
| **Material Design** | Consistent Material Design styling and behavior |
|
|
39
|
+
| **Performance Optimized** | OnPush change detection for better performance |
|
|
40
|
+
| **Accessibility** | Full ARIA support and keyboard navigation |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Demo Component (`ButtonToggleDemoComponent`)
|
|
45
|
+
|
|
46
|
+
The demo component showcases various toggle button configurations and demonstrates different use cases including icons-only, labels-only, and combined icon+label modes.
|
|
47
|
+
|
|
48
|
+
### Usage
|
|
49
|
+
|
|
50
|
+
To use the demo component in your application:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<app-button-toggle-demo></app-button-toggle-demo>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The demo includes:
|
|
57
|
+
- **Icon-only mode**: Toggle buttons with Material icons
|
|
58
|
+
- **Label-only mode**: Toggle buttons with text labels
|
|
59
|
+
- **Combined mode**: Toggle buttons with both icons and labels
|
|
60
|
+
- **Form controls**: Reactive form integration examples
|
|
61
|
+
- **Dynamic data switching**: Runtime data type changes
|
|
62
|
+
- **Enable/disable controls**: Form control state management
|
|
63
|
+
- **Change detection logging**: Console output for debugging
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Summary
|
|
68
|
+
|
|
69
|
+
The `button-toggle-input` library provides a flexible, Material Design-compliant toggle button component with comprehensive form integration, customizable styling, and rich display options for Angular applications.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Quick Start Guide
|
|
74
|
+
|
|
75
|
+
### Installation & Setup (2 minutes)
|
|
76
|
+
|
|
77
|
+
#### 1. Import Module
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// app.module.ts
|
|
81
|
+
import { ButtonToggleInputModule } from 'button-toggle-input';
|
|
82
|
+
|
|
83
|
+
@NgModule({
|
|
84
|
+
imports: [
|
|
85
|
+
ButtonToggleInputModule
|
|
86
|
+
]
|
|
87
|
+
})
|
|
88
|
+
export class AppModule { }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### 2. No Module Configuration Required
|
|
92
|
+
|
|
93
|
+
The `ButtonToggleInputModule` does not require global configuration. Components can be used immediately after module import.
|
|
94
|
+
|
|
95
|
+
### Quick Examples
|
|
96
|
+
|
|
97
|
+
#### Example 1: Basic Single Selection
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
43
100
|
import { Component } from '@angular/core';
|
|
101
|
+
import { ListItem } from 'button-toggle-input';
|
|
44
102
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
103
|
+
@Component({
|
|
104
|
+
selector: 'app-basic-toggle',
|
|
105
|
+
template: `
|
|
106
|
+
<app-button-toggle-input
|
|
107
|
+
[formControl]="selectionControl"
|
|
108
|
+
[data]="toggleOptions">
|
|
109
|
+
</app-button-toggle-input>
|
|
110
|
+
|
|
111
|
+
<div>Selected: {{ selectionControl.value }}</div>
|
|
112
|
+
`
|
|
113
|
+
})
|
|
114
|
+
export class BasicToggleComponent {
|
|
115
|
+
selectionControl = new FormControl();
|
|
116
|
+
|
|
117
|
+
toggleOptions: ListItem[] = [
|
|
118
|
+
{ value: 'option1', label: 'Option 1', icon: 'home' },
|
|
119
|
+
{ value: 'option2', label: 'Option 2', icon: 'settings' },
|
|
120
|
+
{ value: 'option3', label: 'Option 3', icon: 'star' }
|
|
121
|
+
];
|
|
49
122
|
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Example 2: Multiple Selection with Tooltips
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Component } from '@angular/core';
|
|
129
|
+
import { ListItem } from 'button-toggle-input';
|
|
50
130
|
|
|
51
131
|
@Component({
|
|
52
|
-
selector: 'app-
|
|
132
|
+
selector: 'app-multi-toggle',
|
|
53
133
|
template: `
|
|
54
134
|
<app-button-toggle-input
|
|
55
|
-
[
|
|
56
|
-
[
|
|
135
|
+
[formControl]="multiControl"
|
|
136
|
+
[data]="multiOptions"
|
|
57
137
|
[multiple]="true"
|
|
58
138
|
[toolTips]="true"
|
|
59
139
|
toolTipPosition="below"
|
|
60
|
-
color="#007bff"
|
|
61
|
-
lightColor="#f0f8ff"
|
|
62
|
-
darkColor="white"
|
|
63
140
|
[noBorder]="true"
|
|
141
|
+
[fullWidth]="true">
|
|
142
|
+
</app-button-toggle-input>
|
|
143
|
+
|
|
144
|
+
<div>Selected Items: {{ multiControl.value | json }}</div>
|
|
145
|
+
`
|
|
146
|
+
})
|
|
147
|
+
export class MultiToggleComponent {
|
|
148
|
+
multiControl = new FormControl();
|
|
149
|
+
|
|
150
|
+
multiOptions: ListItem[] = [
|
|
151
|
+
{
|
|
152
|
+
value: 'read',
|
|
153
|
+
label: 'Read',
|
|
154
|
+
icon: 'visibility',
|
|
155
|
+
tootTip: 'Read access to documents'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
value: 'write',
|
|
159
|
+
label: 'Write',
|
|
160
|
+
icon: 'edit',
|
|
161
|
+
tootTip: 'Write access to documents'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
value: 'delete',
|
|
165
|
+
label: 'Delete',
|
|
166
|
+
icon: 'delete',
|
|
167
|
+
tootTip: 'Delete access to documents'
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### Example 3: Icon-Only Mode
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { Component } from '@angular/core';
|
|
177
|
+
import { ListItem } from 'button-toggle-input';
|
|
178
|
+
|
|
179
|
+
@Component({
|
|
180
|
+
selector: 'app-icon-toggle',
|
|
181
|
+
template: `
|
|
182
|
+
<app-button-toggle-input
|
|
183
|
+
[formControl]="iconControl"
|
|
184
|
+
[data]="iconOptions"
|
|
64
185
|
[iconPrefix]="true"
|
|
65
|
-
[
|
|
66
|
-
>
|
|
186
|
+
[noBorder]="false">
|
|
67
187
|
</app-button-toggle-input>
|
|
68
|
-
|
|
188
|
+
|
|
189
|
+
<div>Selected: {{ iconControl.value }}</div>
|
|
69
190
|
`
|
|
70
191
|
})
|
|
71
|
-
export class
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{ value: '
|
|
192
|
+
export class IconToggleComponent {
|
|
193
|
+
iconControl = new FormControl();
|
|
194
|
+
|
|
195
|
+
iconOptions: ListItem[] = [
|
|
196
|
+
{ value: 'grid', icon: 'grid_on', tootTip: 'Grid View' },
|
|
197
|
+
{ value: 'list', icon: 'list', tootTip: 'List View' },
|
|
198
|
+
{ value: 'chart', icon: 'bar_chart', tootTip: 'Chart View' }
|
|
76
199
|
];
|
|
77
|
-
selectedItems: string[] = [];
|
|
78
200
|
}
|
|
79
201
|
```
|
|
80
202
|
|
|
203
|
+
#### Example 4: Custom Styled Toggle
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { Component } from '@angular/core';
|
|
207
|
+
import { ListItem } from 'button-toggle-input';
|
|
208
|
+
|
|
209
|
+
@Component({
|
|
210
|
+
selector: 'app-styled-toggle',
|
|
211
|
+
template: `
|
|
212
|
+
<div class="toggle-container">
|
|
213
|
+
<h3>View Options</h3>
|
|
214
|
+
|
|
215
|
+
<app-button-toggle-input
|
|
216
|
+
[formControl]="viewControl"
|
|
217
|
+
[data]="viewOptions"
|
|
218
|
+
color="#007bff"
|
|
219
|
+
lightColor="#f0f8ff"
|
|
220
|
+
darkColor="white"
|
|
221
|
+
[fullWidth]="true">
|
|
222
|
+
</app-button-toggle-input>
|
|
223
|
+
|
|
224
|
+
<div class="result">
|
|
225
|
+
Current view: {{ viewControl.value }}
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
`,
|
|
229
|
+
styles: [`
|
|
230
|
+
.toggle-container {
|
|
231
|
+
max-width: 400px;
|
|
232
|
+
margin: 2rem auto;
|
|
233
|
+
padding: 1rem;
|
|
234
|
+
border: 1px solid #ddd;
|
|
235
|
+
border-radius: 8px;
|
|
236
|
+
}
|
|
237
|
+
h3 {
|
|
238
|
+
text-align: center;
|
|
239
|
+
margin-bottom: 1rem;
|
|
240
|
+
color: #333;
|
|
241
|
+
}
|
|
242
|
+
.result {
|
|
243
|
+
margin-top: 1rem;
|
|
244
|
+
text-align: center;
|
|
245
|
+
font-weight: bold;
|
|
246
|
+
color: #007bff;
|
|
247
|
+
}
|
|
248
|
+
`]
|
|
249
|
+
})
|
|
250
|
+
export class StyledToggleComponent {
|
|
251
|
+
viewControl = new FormControl();
|
|
252
|
+
|
|
253
|
+
viewOptions: ListItem[] = [
|
|
254
|
+
{ value: 'compact', label: 'Compact', icon: 'compress' },
|
|
255
|
+
{ value: 'comfortable', label: 'Comfortable', icon: 'aspect_ratio' },
|
|
256
|
+
{ value: 'spacious', label: 'Spacious', icon: 'open_in_full' }
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Example 5: Dynamic Data with Validation
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { Component } from '@angular/core';
|
|
265
|
+
import { FormBuilder, Validators } from '@angular/forms';
|
|
266
|
+
import { ListItem } from 'button-toggle-input';
|
|
267
|
+
|
|
268
|
+
@Component({
|
|
269
|
+
selector: 'app-dynamic-toggle',
|
|
270
|
+
template: `
|
|
271
|
+
<form [formGroup]="toggleForm">
|
|
272
|
+
<app-button-toggle-input
|
|
273
|
+
formControlName="category"
|
|
274
|
+
[data]="categoryOptions"
|
|
275
|
+
[multiple]="false"
|
|
276
|
+
[toolTips]="true">
|
|
277
|
+
</app-button-toggle-input>
|
|
278
|
+
|
|
279
|
+
<app-button-toggle-input
|
|
280
|
+
formControlName="features"
|
|
281
|
+
[data]="featureOptions"
|
|
282
|
+
[multiple]="true"
|
|
283
|
+
[toolTips]="true">
|
|
284
|
+
</app-button-toggle-input>
|
|
285
|
+
</form>
|
|
286
|
+
|
|
287
|
+
<div class="form-status">
|
|
288
|
+
<div>Category Valid: {{ toggleForm.get('category')?.valid }}</div>
|
|
289
|
+
<div>Features Valid: {{ toggleForm.get('features')?.valid }}</div>
|
|
290
|
+
<div>Form Valid: {{ toggleForm.valid }}</div>
|
|
291
|
+
</div>
|
|
292
|
+
`,
|
|
293
|
+
styles: [`
|
|
294
|
+
form {
|
|
295
|
+
display: flex;
|
|
296
|
+
flex-direction: column;
|
|
297
|
+
gap: 2rem;
|
|
298
|
+
max-width: 500px;
|
|
299
|
+
margin: 2rem auto;
|
|
300
|
+
}
|
|
301
|
+
.form-status {
|
|
302
|
+
background: #f5f5f5;
|
|
303
|
+
padding: 1rem;
|
|
304
|
+
border-radius: 4px;
|
|
305
|
+
font-family: monospace;
|
|
306
|
+
}
|
|
307
|
+
`]
|
|
308
|
+
})
|
|
309
|
+
export class DynamicToggleComponent {
|
|
310
|
+
constructor(private fb: FormBuilder) {}
|
|
311
|
+
|
|
312
|
+
toggleForm = this.fb.group({
|
|
313
|
+
category: ['', Validators.required],
|
|
314
|
+
features: [[], Validators.minLength(1)]
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
categoryOptions: ListItem[] = [
|
|
318
|
+
{ value: 'web', label: 'Web Application', icon: 'web' },
|
|
319
|
+
{ value: 'mobile', label: 'Mobile App', icon: 'phone_android' },
|
|
320
|
+
{ value: 'desktop', label: 'Desktop App', icon: 'computer' }
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
featureOptions: ListItem[] = [
|
|
324
|
+
{ value: 'auth', label: 'Authentication', icon: 'security' },
|
|
325
|
+
{ value: 'notifications', label: 'Notifications', icon: 'notifications' },
|
|
326
|
+
{ value: 'offline', label: 'Offline Support', icon: 'wifi_off' },
|
|
327
|
+
{ value: 'analytics', label: 'Analytics', icon: 'analytics' }
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Component API
|
|
335
|
+
|
|
336
|
+
### Inputs
|
|
337
|
+
|
|
338
|
+
| Input | Type | Description | Default |
|
|
339
|
+
| :--- | :--- | :--- | :--- |
|
|
340
|
+
| `data` | `ListItem[] \| string[]` | Array of ListItem objects or strings defining toggle options | (Required) |
|
|
341
|
+
| `multiple` | `boolean` | If true, allows multiple selections | `false` |
|
|
342
|
+
| `toolTips` | `boolean` | If true, displays tooltips on each toggle item | `false` |
|
|
343
|
+
| `toolTipPosition` | `TooltipPosition` | Position of tooltips ('above', 'below', 'left', 'right') | `"above"` |
|
|
344
|
+
| `toolTipShowDelay` | `number` | Delay in milliseconds before tooltip is displayed | `1` |
|
|
345
|
+
| `color` | `string` | Base color of toggle buttons (hex code or CSS color) | `"#333333"` |
|
|
346
|
+
| `lightColor` | `string` | Color for light areas (background when not selected) | `"white"` |
|
|
347
|
+
| `darkColor` | `string` | Color for dark areas (text when selected) | `"black"` |
|
|
348
|
+
| `noBorder` | `boolean` | If true, removes border from toggle buttons | `false` |
|
|
349
|
+
| `iconPrefix` | `boolean` | If true, displays icon before label (ignored if iconSuffix is true) | `true` |
|
|
350
|
+
| `iconSuffix` | `boolean` | If true, displays icon after label (sets iconPrefix to false) | `false` |
|
|
351
|
+
| `fullWidth` | `boolean` | If true, makes toggle buttons take full container width | `false` |
|
|
352
|
+
|
|
353
|
+
### Outputs
|
|
354
|
+
|
|
355
|
+
| Output | Type | Description |
|
|
356
|
+
| :--- | :--- | :--- |
|
|
357
|
+
| (Inherited from ControlValueAccessor) | `Function` | Value changes emitted through Angular's form control |
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Model Structures
|
|
362
|
+
|
|
363
|
+
### ListItem Interface
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
export interface ListItemInterface {
|
|
367
|
+
value: any; // The value returned when this item is selected
|
|
368
|
+
label?: string; // Optional display label text
|
|
369
|
+
icon?: string; // Optional Material icon name
|
|
370
|
+
tootTip?: string; // Optional tooltip text (note: typo in original)
|
|
371
|
+
selected?: string; // Internal: marks item as selected
|
|
372
|
+
disabled?: string; // Internal: marks item as disabled
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### ListItem Class
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
export class ListItem implements ListItemInterface {
|
|
380
|
+
constructor(
|
|
381
|
+
public value = '',
|
|
382
|
+
public label?: string,
|
|
383
|
+
public icon?: string,
|
|
384
|
+
public tootTip?: string,
|
|
385
|
+
public selected?: string,
|
|
386
|
+
public disabled?: string,
|
|
387
|
+
) {}
|
|
388
|
+
|
|
389
|
+
static adapt(item?: any): ListItem {
|
|
390
|
+
return new ListItem(
|
|
391
|
+
item?.value,
|
|
392
|
+
// Label: use provided label, or empty string if icon exists, or fallback to value
|
|
393
|
+
(item?.label) ? item.label : (item?.icon) ? '' : item?.value,
|
|
394
|
+
item?.icon,
|
|
395
|
+
// Tooltip: use provided tooltip, or label if exists, or fallback to value
|
|
396
|
+
(item?.tootTip) ? item.tootTip : (item?.label) ? item.label : item?.value,
|
|
397
|
+
item?.selected,
|
|
398
|
+
item?.disabled,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Usage Examples
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// Basic list item
|
|
408
|
+
const basicItem = new ListItem('value1', 'Label 1');
|
|
409
|
+
|
|
410
|
+
// With icon
|
|
411
|
+
const iconItem = new ListItem('value2', 'Settings', 'settings');
|
|
412
|
+
|
|
413
|
+
// Using adapt method for flexible data
|
|
414
|
+
const adaptedItem = ListItem.adapt({
|
|
415
|
+
value: 'option1',
|
|
416
|
+
label: 'Option 1',
|
|
417
|
+
icon: 'radio_button_checked',
|
|
418
|
+
tootTip: 'Select this option'
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Array configurations
|
|
422
|
+
const toggleOptions: ListItem[] = [
|
|
423
|
+
// String-only data (automatically converted)
|
|
424
|
+
'option1',
|
|
425
|
+
'option2',
|
|
426
|
+
'option3',
|
|
427
|
+
|
|
428
|
+
// Object configuration
|
|
429
|
+
{ value: 'custom1', label: 'Custom Option', icon: 'star' },
|
|
430
|
+
{ value: 'custom2', label: 'Another Option', icon: 'heart' },
|
|
431
|
+
|
|
432
|
+
// Icon-only (no label)
|
|
433
|
+
{ value: 'icon1', icon: 'home', tootTip: 'Home View' },
|
|
434
|
+
{ value: 'icon2', icon: 'work', tootTip: 'Work View' }
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
// Complex configuration with all properties
|
|
438
|
+
const complexItem: ListItem = ListItem.adapt({
|
|
439
|
+
value: 'complex-option',
|
|
440
|
+
label: 'Complex Option',
|
|
441
|
+
icon: 'settings',
|
|
442
|
+
tootTip: 'This is a complex option with all features',
|
|
443
|
+
selected: 'true', // Pre-selected
|
|
444
|
+
disabled: 'false' // Not disabled
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
81
450
|
## Form Integration (ControlValueAccessor)
|
|
82
451
|
|
|
83
|
-
The component implements
|
|
452
|
+
The component implements Angular's `ControlValueAccessor` interface, making it fully compatible with both template-driven and reactive forms.
|
|
84
453
|
|
|
85
|
-
|
|
86
|
-
registerOnChange(fn: any): void: Registers a callback function to be called when the control's value changes.
|
|
87
|
-
registerOnTouched(fn: any): void: Registers a callback function to be called when the control is touched.
|
|
88
|
-
setDisabledState(isDisabled: boolean): void: Disables or enables the control.
|
|
89
|
-
The component uses a FormControl internally to manage the selected values. The selected values are emitted through the onChange callback.
|
|
454
|
+
### ControlValueAccessor Implementation
|
|
90
455
|
|
|
91
|
-
|
|
456
|
+
#### Methods Implemented
|
|
92
457
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
458
|
+
```typescript
|
|
459
|
+
// writeValue(value: any): void
|
|
460
|
+
// Sets the value of the control
|
|
461
|
+
writeValue(value: any): void {
|
|
462
|
+
// Handle incoming form control value
|
|
463
|
+
// Update component state accordingly
|
|
464
|
+
}
|
|
100
465
|
|
|
101
|
-
|
|
466
|
+
// registerOnChange(fn: any): void
|
|
467
|
+
// Registers a callback for value changes
|
|
468
|
+
registerOnChange(fn: any): void {
|
|
469
|
+
this.onChange = fn;
|
|
470
|
+
}
|
|
102
471
|
|
|
103
|
-
|
|
472
|
+
// registerOnTouched(fn: any): void
|
|
473
|
+
// Registers a callback for touch events
|
|
474
|
+
registerOnTouched(fn: any): void {
|
|
475
|
+
this.onTouch = fn;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// setDisabledState(isDisabled: boolean): void
|
|
479
|
+
// Sets disabled state
|
|
480
|
+
setDisabledState?(isDisabled: boolean): void {
|
|
481
|
+
this.disabled = isDisabled;
|
|
482
|
+
}
|
|
104
483
|
```
|
|
105
484
|
|
|
485
|
+
### Form Integration Examples
|
|
486
|
+
|
|
487
|
+
#### Reactive Forms
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import { Component } from '@angular/core';
|
|
491
|
+
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
|
492
|
+
|
|
493
|
+
@Component({
|
|
494
|
+
selector: 'app-reactive-form-toggle',
|
|
495
|
+
template: `
|
|
496
|
+
<form [formGroup]="toggleForm">
|
|
497
|
+
<app-button-toggle-input
|
|
498
|
+
formControlName="selection"
|
|
499
|
+
[data]="options"
|
|
500
|
+
[multiple]="true">
|
|
501
|
+
</app-button-toggle-input>
|
|
502
|
+
|
|
503
|
+
<div class="form-errors" *ngIf="toggleForm.get('selection')?.errors">
|
|
504
|
+
<div *ngIf="toggleForm.get('selection')?.hasError('required')">
|
|
505
|
+
Selection is required
|
|
506
|
+
</div>
|
|
507
|
+
<div *ngIf="toggleForm.get('selection')?.hasError('minLength')">
|
|
508
|
+
At least one option must be selected
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</form>
|
|
512
|
+
`
|
|
513
|
+
})
|
|
514
|
+
export class ReactiveFormToggleComponent {
|
|
515
|
+
toggleForm = new FormGroup({
|
|
516
|
+
selection: new FormControl([], Validators.required)
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
options: ListItem[] = [
|
|
520
|
+
{ value: 'opt1', label: 'Option 1' },
|
|
521
|
+
{ value: 'opt2', label: 'Option 2' },
|
|
522
|
+
{ value: 'opt3', label: 'Option 3' }
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Template-Driven Forms
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { Component } from '@angular/core';
|
|
531
|
+
|
|
532
|
+
@Component({
|
|
533
|
+
selector: 'app-template-toggle',
|
|
534
|
+
template: `
|
|
535
|
+
<form #toggleForm="ngForm">
|
|
536
|
+
<app-button-toggle-input
|
|
537
|
+
[(ngModel)]="selectedValue"
|
|
538
|
+
name="toggleSelection"
|
|
539
|
+
[data]="options"
|
|
540
|
+
[multiple]="false"
|
|
541
|
+
required>
|
|
542
|
+
</app-button-toggle-input>
|
|
543
|
+
|
|
544
|
+
<div *ngIf="toggleForm.controls.toggleSelection?.invalid && toggleForm.controls.toggleSelection?.touched">
|
|
545
|
+
Selection is required
|
|
546
|
+
</div>
|
|
547
|
+
</form>
|
|
548
|
+
`
|
|
549
|
+
})
|
|
550
|
+
export class TemplateToggleComponent {
|
|
551
|
+
selectedValue: any = null;
|
|
552
|
+
|
|
553
|
+
options: ListItem[] = [
|
|
554
|
+
{ value: 'single1', label: 'Single Option 1' },
|
|
555
|
+
{ value: 'single2', label: 'Single Option 2' },
|
|
556
|
+
{ value: 'single3', label: 'Single Option 3' }
|
|
557
|
+
];
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### Form Control Methods
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
// Setting values programmatically
|
|
565
|
+
this.formControl.setValue(['option1', 'option2']); // For multiple mode
|
|
566
|
+
this.formControl.setValue('option1'); // For single mode
|
|
567
|
+
|
|
568
|
+
// Patching values
|
|
569
|
+
this.formControl.patchValue(['option1']); // Partial update
|
|
570
|
+
|
|
571
|
+
// Resetting
|
|
572
|
+
this.formControl.reset(); // Clear selection
|
|
573
|
+
|
|
574
|
+
// Getting current value
|
|
575
|
+
const currentValue = this.formControl.value;
|
|
576
|
+
|
|
577
|
+
// Checking validity
|
|
578
|
+
const isValid = this.formControl.valid;
|
|
579
|
+
const errors = this.formControl.errors;
|
|
580
|
+
|
|
581
|
+
// Setting custom errors
|
|
582
|
+
this.formControl.setErrors({ customError: 'Custom error message' });
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Module Configuration
|
|
588
|
+
|
|
589
|
+
### ButtonToggleInputModule
|
|
590
|
+
|
|
591
|
+
**No Global Configuration Required**
|
|
592
|
+
|
|
593
|
+
The `ButtonToggleInputModule` does not provide a `forRoot()` method or global configuration options. All configuration is done at the component level through input properties.
|
|
594
|
+
|
|
595
|
+
#### Module Structure
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
@NgModule({
|
|
599
|
+
imports: [
|
|
600
|
+
CommonModule,
|
|
601
|
+
FormsModule,
|
|
602
|
+
MatIconModule,
|
|
603
|
+
MatButtonModule,
|
|
604
|
+
MatTooltipModule,
|
|
605
|
+
ReactiveFormsModule,
|
|
606
|
+
MatButtonToggleModule,
|
|
607
|
+
MatSlideToggleModule,
|
|
608
|
+
MatDividerModule,
|
|
609
|
+
],
|
|
610
|
+
declarations: [
|
|
611
|
+
ButtonToggleInputComponent,
|
|
612
|
+
ButtonToggleDemoComponent
|
|
613
|
+
],
|
|
614
|
+
exports: [
|
|
615
|
+
ButtonToggleInputComponent,
|
|
616
|
+
ButtonToggleDemoComponent
|
|
617
|
+
]
|
|
618
|
+
})
|
|
619
|
+
export class ButtonToggleInputModule { }
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Dependencies
|
|
623
|
+
|
|
624
|
+
- **@angular/common**: Core Angular functionality
|
|
625
|
+
- **@angular/forms**: Form control integration (FormsModule, ReactiveFormsModule)
|
|
626
|
+
- **@angular/material**: Material Design components
|
|
627
|
+
- MatIconModule: Icon display
|
|
628
|
+
- MatButtonModule: Button base components
|
|
629
|
+
- MatTooltipModule: Tooltip functionality
|
|
630
|
+
- MatButtonToggleModule: Button toggle foundation
|
|
631
|
+
- MatSlideToggleModule: Slide toggle components
|
|
632
|
+
- MatDividerModule: Visual divider components
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Styling and Customization
|
|
637
|
+
|
|
638
|
+
### CSS Classes and Styling
|
|
639
|
+
|
|
640
|
+
The component uses Material Design styling and can be customized using:
|
|
641
|
+
|
|
642
|
+
1. **Global Material Theme**: Configure colors in your Angular Material theme
|
|
643
|
+
2. **Component-specific Styles**: Add custom CSS classes
|
|
644
|
+
3. **Input Properties**: Use color, lightColor, darkColor, and noBorder properties
|
|
645
|
+
4. **Container Styling**: Style the parent container for layout control
|
|
646
|
+
|
|
647
|
+
### Color Customization
|
|
648
|
+
|
|
649
|
+
```scss
|
|
650
|
+
// Custom color schemes
|
|
651
|
+
.light-theme {
|
|
652
|
+
app-button-toggle-input {
|
|
653
|
+
--toggle-color: #2196f3;
|
|
654
|
+
--toggle-light-color: #e3f2fd;
|
|
655
|
+
--toggle-dark-color: #ffffff;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.dark-theme {
|
|
660
|
+
app-button-toggle-input {
|
|
661
|
+
--toggle-color: #64b5f6;
|
|
662
|
+
--toggle-light-color: #1565c0;
|
|
663
|
+
--toggle-dark-color: #ffffff;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Layout Customization
|
|
669
|
+
|
|
670
|
+
```scss
|
|
671
|
+
// Full width toggle
|
|
672
|
+
.full-width-toggle {
|
|
673
|
+
app-button-toggle-input {
|
|
674
|
+
width: 100%;
|
|
675
|
+
|
|
676
|
+
.mat-button-toggle-group {
|
|
677
|
+
width: 100%;
|
|
678
|
+
|
|
679
|
+
.mat-button-toggle {
|
|
680
|
+
flex: 1;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Compact toggle
|
|
687
|
+
.compact-toggle {
|
|
688
|
+
app-button-toggle-input {
|
|
689
|
+
.mat-button-toggle {
|
|
690
|
+
padding: 4px 8px;
|
|
691
|
+
min-width: auto;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Border Customization
|
|
698
|
+
|
|
699
|
+
```scss
|
|
700
|
+
// Remove borders
|
|
701
|
+
.no-borders {
|
|
702
|
+
app-button-toggle-input {
|
|
703
|
+
.mat-button-toggle {
|
|
704
|
+
border: none;
|
|
705
|
+
|
|
706
|
+
&.mat-button-toggle-checked {
|
|
707
|
+
border: none;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Custom border styles
|
|
714
|
+
.custom-borders {
|
|
715
|
+
app-button-toggle-input {
|
|
716
|
+
.mat-button-toggle {
|
|
717
|
+
border: 2px solid #e0e0e0;
|
|
718
|
+
border-radius: 8px;
|
|
719
|
+
margin: 2px;
|
|
720
|
+
|
|
721
|
+
&.mat-button-toggle-checked {
|
|
722
|
+
border-color: #2196f3;
|
|
723
|
+
background-color: #e3f2fd;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## Accessibility
|
|
733
|
+
|
|
734
|
+
### ARIA Support
|
|
735
|
+
|
|
736
|
+
- Buttons include proper ARIA labels and roles
|
|
737
|
+
- Keyboard navigation is fully supported (Tab, Enter, Space, Arrow keys)
|
|
738
|
+
- Screen reader friendly with appropriate labels and descriptions
|
|
739
|
+
- Tooltip content is accessible to assistive technologies
|
|
740
|
+
- Selection state is properly communicated
|
|
741
|
+
|
|
742
|
+
### Best Practices
|
|
743
|
+
|
|
744
|
+
1. **Provide meaningful labels** for all toggle items
|
|
745
|
+
2. **Use descriptive icons** that match the toggle purpose
|
|
746
|
+
3. **Set appropriate tooltips** for additional context
|
|
747
|
+
4. **Consider color contrast** when using custom colors
|
|
748
|
+
5. **Test with screen readers** to ensure accessibility
|
|
749
|
+
6. **Use logical tab order** for keyboard navigation
|
|
750
|
+
|
|
751
|
+
### Keyboard Navigation
|
|
752
|
+
|
|
753
|
+
| Key | Action |
|
|
754
|
+
|-----|--------|
|
|
755
|
+
| `Tab` | Navigate to next toggle item |
|
|
756
|
+
| `Shift+Tab` | Navigate to previous toggle item |
|
|
757
|
+
| `Space` | Toggle selection (single mode) |
|
|
758
|
+
| `Enter` | Toggle selection (single mode) |
|
|
759
|
+
| `Arrow Keys` | Navigate between toggle items |
|
|
760
|
+
| `Ctrl+Click` | Toggle selection (multiple mode) |
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## Integration Examples
|
|
765
|
+
|
|
766
|
+
### With Other UI Components
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
// Integration with display-card
|
|
770
|
+
@Component({
|
|
771
|
+
template: `
|
|
772
|
+
<app-display-card title="Filter Options">
|
|
773
|
+
<app-button-toggle-input
|
|
774
|
+
[data]="filterOptions"
|
|
775
|
+
[multiple]="true"
|
|
776
|
+
[toolTips]="true"
|
|
777
|
+
[fullWidth]="true">
|
|
778
|
+
</app-button-toggle-input>
|
|
779
|
+
</app-display-card>
|
|
780
|
+
`
|
|
781
|
+
})
|
|
782
|
+
export class CardWithToggleComponent {
|
|
783
|
+
filterOptions: ListItem[] = [
|
|
784
|
+
{ value: 'active', label: 'Active', icon: 'check_circle' },
|
|
785
|
+
{ value: 'pending', label: 'Pending', icon: 'schedule' },
|
|
786
|
+
{ value: 'archived', label: 'Archived', icon: 'archive' }
|
|
787
|
+
];
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### With State Management
|
|
792
|
+
|
|
793
|
+
```typescript
|
|
794
|
+
// Integration with HTTP Request Manager
|
|
795
|
+
@Component({
|
|
796
|
+
template: `
|
|
797
|
+
<app-button-toggle-input
|
|
798
|
+
[data]="viewOptions$ | async"
|
|
799
|
+
[multiple]="false"
|
|
800
|
+
(selectionChange)="handleViewChange($event)">
|
|
801
|
+
</app-button-toggle-input>
|
|
802
|
+
`
|
|
803
|
+
})
|
|
804
|
+
export class StateManagedToggleComponent {
|
|
805
|
+
viewOptions$ = this.viewStore.options$;
|
|
806
|
+
|
|
807
|
+
constructor(private viewStore: ViewStore) {}
|
|
808
|
+
|
|
809
|
+
handleViewChange(value: any) {
|
|
810
|
+
this.viewStore.setView(value);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### With Dynamic Forms
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
@Component({
|
|
819
|
+
template: `
|
|
820
|
+
<div formArrayName="toggles">
|
|
821
|
+
<div *ngFor="let toggle of toggleArray.controls; let i = index">
|
|
822
|
+
<app-button-toggle-input
|
|
823
|
+
[formControlName]="i"
|
|
824
|
+
[data]="dynamicOptions[i]">
|
|
825
|
+
</app-button-toggle-input>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
`
|
|
829
|
+
})
|
|
830
|
+
export class DynamicFormComponent {
|
|
831
|
+
toggleForm = this.fb.group({
|
|
832
|
+
toggles: this.fb.array([
|
|
833
|
+
this.fb.control('option1'),
|
|
834
|
+
this.fb.control(['option1', 'option2']),
|
|
835
|
+
this.fb.control('option3')
|
|
836
|
+
])
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
get toggleArray() {
|
|
840
|
+
return this.toggleForm.get('toggles') as FormArray;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
dynamicOptions: ListItem[][] = [
|
|
844
|
+
// Options for first toggle
|
|
845
|
+
[{ value: 'opt1', label: 'Option 1' }, { value: 'opt2', label: 'Option 2' }],
|
|
846
|
+
// Options for second toggle
|
|
847
|
+
[{ value: 'opt3', label: 'Option 3' }, { value: 'opt4', label: 'Option 4' }],
|
|
848
|
+
// Options for third toggle
|
|
849
|
+
[{ value: 'opt5', label: 'Option 5' }, { value: 'opt6', label: 'Option 6' }]
|
|
850
|
+
];
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## Performance Optimization
|
|
857
|
+
|
|
858
|
+
### Change Detection Strategy
|
|
859
|
+
|
|
860
|
+
The demo component uses `ChangeDetectionStrategy.OnPush` for optimal performance:
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
@Component({
|
|
864
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
865
|
+
// ... other config
|
|
866
|
+
})
|
|
867
|
+
export class ButtonToggleDemoComponent {
|
|
868
|
+
// Component logic
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Performance Tips
|
|
873
|
+
|
|
874
|
+
1. **Use OnPush change detection** for better performance with large toggle arrays
|
|
875
|
+
2. **Implement trackBy** for dynamic toggle lists
|
|
876
|
+
3. **Avoid frequent data object recreation** to prevent unnecessary re-renders
|
|
877
|
+
4. **Use immutable data patterns** for toggle option updates
|
|
878
|
+
5. **Consider virtual scrolling** for very large toggle lists
|
|
879
|
+
6. **Optimize tooltip content** to avoid performance issues
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## Testing
|
|
884
|
+
|
|
885
|
+
### Unit Testing Example
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
889
|
+
import { ButtonToggleInputComponent } from './button-toggle-input.component';
|
|
890
|
+
import { ListItem } from './models/list-item.model';
|
|
891
|
+
import { ReactiveFormsModule } from '@angular/forms';
|
|
892
|
+
|
|
893
|
+
describe('ButtonToggleInputComponent', () => {
|
|
894
|
+
let component: ButtonToggleInputComponent;
|
|
895
|
+
let fixture: ComponentFixture<ButtonToggleInputComponent>;
|
|
896
|
+
|
|
897
|
+
beforeEach(async () => {
|
|
898
|
+
await TestBed.configureTestingModule({
|
|
899
|
+
declarations: [ ButtonToggleInputComponent ],
|
|
900
|
+
imports: [ ReactiveFormsModule ]
|
|
901
|
+
}).compileComponents();
|
|
902
|
+
|
|
903
|
+
fixture = TestBed.createComponent(ButtonToggleInputComponent);
|
|
904
|
+
component = fixture.componentInstance;
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it('should create', () => {
|
|
908
|
+
expect(component).toBeTruthy();
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
it('should display toggle items from data input', () => {
|
|
912
|
+
component.data = [
|
|
913
|
+
{ value: 'opt1', label: 'Option 1' },
|
|
914
|
+
{ value: 'opt2', label: 'Option 2' }
|
|
915
|
+
];
|
|
916
|
+
fixture.detectChanges();
|
|
917
|
+
|
|
918
|
+
const compiled = fixture.nativeElement;
|
|
919
|
+
const buttons = compiled.querySelectorAll('.mat-button-toggle');
|
|
920
|
+
expect(buttons.length).toBe(2);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it('should emit value changes through form control', () => {
|
|
924
|
+
const formControl = new FormControl();
|
|
925
|
+
component.formControl = formControl;
|
|
926
|
+
|
|
927
|
+
spyOn(formControl, 'setValue');
|
|
928
|
+
component.writeValue('test-value');
|
|
929
|
+
|
|
930
|
+
expect(formControl.setValue).toHaveBeenCalledWith('test-value');
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
it('should support multiple selection mode', () => {
|
|
934
|
+
component.multiple = true;
|
|
935
|
+
component.data = [
|
|
936
|
+
{ value: 'opt1', label: 'Option 1' },
|
|
937
|
+
{ value: 'opt2', label: 'Option 2' }
|
|
938
|
+
];
|
|
939
|
+
fixture.detectChanges();
|
|
940
|
+
|
|
941
|
+
// Test multiple selection logic
|
|
942
|
+
expect(component.multiple).toBe(true);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it('should handle tooltips correctly', () => {
|
|
946
|
+
component.toolTips = true;
|
|
947
|
+
component.data = [
|
|
948
|
+
{ value: 'opt1', label: 'Option 1', tootTip: 'Tooltip 1' }
|
|
949
|
+
];
|
|
950
|
+
fixture.detectChanges();
|
|
951
|
+
|
|
952
|
+
const compiled = fixture.nativeElement;
|
|
953
|
+
const tooltip = compiled.querySelector('[mattooltip]');
|
|
954
|
+
expect(tooltip).toBeTruthy();
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Integration Testing
|
|
960
|
+
|
|
961
|
+
```typescript
|
|
962
|
+
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
963
|
+
import { ButtonToggleInputModule } from './button-toggle-input.module';
|
|
964
|
+
|
|
965
|
+
describe('ButtonToggleInput Integration', () => {
|
|
966
|
+
let component: TestHostComponent;
|
|
967
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
968
|
+
|
|
969
|
+
@Component({
|
|
970
|
+
template: `
|
|
971
|
+
<app-button-toggle-input
|
|
972
|
+
[formControl]="testControl"
|
|
973
|
+
[data]="testData"
|
|
974
|
+
[multiple]="true">
|
|
975
|
+
</app-button-toggle-input>
|
|
976
|
+
`
|
|
977
|
+
})
|
|
978
|
+
class TestHostComponent {
|
|
979
|
+
testControl = new FormControl();
|
|
980
|
+
testData: ListItem[] = [
|
|
981
|
+
{ value: 'test1', label: 'Test 1' },
|
|
982
|
+
{ value: 'test2', label: 'Test 2' }
|
|
983
|
+
];
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
beforeEach(async () => {
|
|
987
|
+
await TestBed.configureTestingModule({
|
|
988
|
+
declarations: [ TestHostComponent ],
|
|
989
|
+
imports: [ ButtonToggleInputModule ]
|
|
990
|
+
}).compileComponents();
|
|
991
|
+
|
|
992
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
993
|
+
component = fixture.componentInstance;
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('should integrate with form controls', () => {
|
|
997
|
+
expect(component.testControl).toBeDefined();
|
|
998
|
+
expect(component.testData.length).toBe(2);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it('should update form control value when selection changes', () => {
|
|
1002
|
+
fixture.detectChanges();
|
|
1003
|
+
|
|
1004
|
+
// Simulate user interaction
|
|
1005
|
+
const buttons = fixture.nativeElement.querySelectorAll('.mat-button-toggle');
|
|
1006
|
+
buttons[0].click();
|
|
1007
|
+
|
|
1008
|
+
expect(component.testControl.value).toEqual(['test1']);
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
---
|
|
1014
|
+
|
|
1015
|
+
## Troubleshooting
|
|
1016
|
+
|
|
1017
|
+
### Common Issues
|
|
1018
|
+
|
|
1019
|
+
1. **Form control not working**: Ensure ReactiveFormsModule is imported
|
|
1020
|
+
2. **Icons not displaying**: Verify MatIconModule is imported and Material Icons are loaded
|
|
1021
|
+
3. **Tooltips not showing**: Check that MatTooltipModule is imported
|
|
1022
|
+
4. **Styling issues**: Ensure Material theme is properly configured
|
|
1023
|
+
5. **Change detection warnings**: Consider using OnPush change detection strategy
|
|
1024
|
+
|
|
1025
|
+
### Debug Mode
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
// Add debugging to track form control changes
|
|
1029
|
+
@Component({
|
|
1030
|
+
template: `
|
|
1031
|
+
<div class="debug-info">
|
|
1032
|
+
Form Control Value: {{ formControl.value | json }}<br>
|
|
1033
|
+
Form Control Valid: {{ formControl.valid }}<br>
|
|
1034
|
+
Data Length: {{ data?.length || 0 }}<br>
|
|
1035
|
+
Multiple Mode: {{ multiple }}
|
|
1036
|
+
</div>
|
|
1037
|
+
|
|
1038
|
+
<app-button-toggle-input
|
|
1039
|
+
[formControl]="formControl"
|
|
1040
|
+
[data]="data"
|
|
1041
|
+
[multiple]="multiple">
|
|
1042
|
+
</app-button-toggle-input>
|
|
1043
|
+
`
|
|
1044
|
+
})
|
|
1045
|
+
export class DebugToggleComponent {
|
|
1046
|
+
formControl = new FormControl();
|
|
1047
|
+
data: ListItem[] = [];
|
|
1048
|
+
multiple = false;
|
|
1049
|
+
|
|
1050
|
+
constructor() {
|
|
1051
|
+
this.formControl.valueChanges.subscribe(value => {
|
|
1052
|
+
console.log('Toggle value changed:', value);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### Performance Issues
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
// Optimize for large datasets
|
|
1062
|
+
@Component({
|
|
1063
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1064
|
+
template: `
|
|
1065
|
+
<app-button-toggle-input
|
|
1066
|
+
[formControl]="formControl"
|
|
1067
|
+
[data]="data"
|
|
1068
|
+
[multiple]="multiple">
|
|
1069
|
+
</app-button-toggle-input>
|
|
1070
|
+
`
|
|
1071
|
+
})
|
|
1072
|
+
export class OptimizedToggleComponent {
|
|
1073
|
+
// Use immutable data updates
|
|
1074
|
+
updateData(newData: ListItem[]) {
|
|
1075
|
+
this.data = [...newData]; // Create new array reference
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
```
|