ngx-opalbytes-components 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +370 -1
- package/fesm2022/ngx-opalbytes-components.mjs +562 -3
- package/fesm2022/ngx-opalbytes-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/shared/components/accordion/accordion.component.css +30 -0
- package/src/lib/shared/components/accordion/accordion.component.html +1 -0
- package/src/lib/shared/components/accordion/accordion.component.spec.ts +117 -0
- package/src/lib/shared/components/accordion/accordion.component.ts +195 -0
- package/src/lib/shared/components/accordion/accordion.token.ts +40 -0
- package/src/lib/shared/components/accordion-item/accordion-item.component.css +137 -0
- package/src/lib/shared/components/accordion-item/accordion-item.component.html +52 -0
- package/src/lib/shared/components/accordion-item/accordion-item.component.ts +157 -0
- package/src/lib/shared/components/accordion-item/accordion-item.spec.ts +22 -0
- package/src/lib/shared/components/checkbox/checkbox.component.css +131 -0
- package/src/lib/shared/components/checkbox/checkbox.component.html +84 -0
- package/src/lib/shared/components/checkbox/checkbox.component.spec.ts +344 -0
- package/src/lib/shared/components/checkbox/checkbox.component.ts +258 -0
- package/src/lib/shared/components/checkbox/checkbox.stories.ts +240 -0
- package/src/lib/shared/components/index.ts +4 -0
- package/types/ngx-opalbytes-components.d.ts +341 -126
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { Component, Input } from "@angular/core";
|
|
2
|
+
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
3
|
+
import { FormControl, FormsModule, Validators } from "@angular/forms";
|
|
4
|
+
import { LucideCheck, LucideMinus, LucideX, provideLucideIcons } from "@lucide/angular";
|
|
5
|
+
|
|
6
|
+
import { CaoCheckboxComponent } from "./checkbox.component";
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [FormsModule, CaoCheckboxComponent],
|
|
11
|
+
template: `
|
|
12
|
+
<cao-checkbox
|
|
13
|
+
[(ngModel)]="checked"
|
|
14
|
+
[isDisabled]="isDisabled"
|
|
15
|
+
label="Aceito os termos"
|
|
16
|
+
name="terms"
|
|
17
|
+
dataCy="checkbox-terms"></cao-checkbox>
|
|
18
|
+
`,
|
|
19
|
+
})
|
|
20
|
+
class CheckboxHostComponent {
|
|
21
|
+
@Input() checked = false;
|
|
22
|
+
@Input() isDisabled = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("CaoCheckboxComponent", () => {
|
|
26
|
+
let component: CaoCheckboxComponent;
|
|
27
|
+
let fixture: ComponentFixture<CaoCheckboxComponent>;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
await TestBed.configureTestingModule({
|
|
31
|
+
imports: [CaoCheckboxComponent],
|
|
32
|
+
providers: [provideLucideIcons(LucideCheck, LucideMinus, LucideX)],
|
|
33
|
+
}).compileComponents();
|
|
34
|
+
|
|
35
|
+
fixture = TestBed.createComponent(CaoCheckboxComponent);
|
|
36
|
+
component = fixture.componentInstance;
|
|
37
|
+
fixture.detectChanges();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function getInput(): HTMLInputElement {
|
|
41
|
+
return fixture.nativeElement.querySelector("input");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toggleInput(checked = true): void {
|
|
45
|
+
const input = getInput();
|
|
46
|
+
input.checked = checked;
|
|
47
|
+
input.dispatchEvent(new Event("change"));
|
|
48
|
+
fixture.detectChanges();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
it("should create", () => {
|
|
52
|
+
expect(component).toBeTruthy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should toggle and emit changes", () => {
|
|
56
|
+
const checkedChange = vi.fn();
|
|
57
|
+
const change = vi.fn();
|
|
58
|
+
|
|
59
|
+
component.checkedChange.subscribe(checkedChange);
|
|
60
|
+
component.checkboxChange.subscribe(change);
|
|
61
|
+
|
|
62
|
+
toggleInput(true);
|
|
63
|
+
|
|
64
|
+
expect(component.checked).toBe(true);
|
|
65
|
+
expect(checkedChange).toHaveBeenCalledWith(true);
|
|
66
|
+
expect(change).toHaveBeenCalledWith({
|
|
67
|
+
checked: true,
|
|
68
|
+
source: component,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should not toggle when disabled", () => {
|
|
73
|
+
component.isDisabled = true;
|
|
74
|
+
fixture.detectChanges();
|
|
75
|
+
|
|
76
|
+
toggleInput(true);
|
|
77
|
+
|
|
78
|
+
expect(component.checked).toBe(false);
|
|
79
|
+
expect(getInput().disabled).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should support ControlValueAccessor", () => {
|
|
83
|
+
const onChange = vi.fn();
|
|
84
|
+
const onTouched = vi.fn();
|
|
85
|
+
|
|
86
|
+
component.registerOnChange(onChange);
|
|
87
|
+
component.registerOnTouched(onTouched);
|
|
88
|
+
component.writeValue(true);
|
|
89
|
+
|
|
90
|
+
expect(component.checked).toBe(true);
|
|
91
|
+
|
|
92
|
+
component.onBlur();
|
|
93
|
+
expect(onTouched).toHaveBeenCalled();
|
|
94
|
+
|
|
95
|
+
component.toggle();
|
|
96
|
+
expect(onChange).toHaveBeenCalledWith(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should write null values as unchecked", () => {
|
|
100
|
+
component.writeValue(null);
|
|
101
|
+
|
|
102
|
+
expect(component.checked).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should enable and disable programmatically", () => {
|
|
106
|
+
component.disable();
|
|
107
|
+
expect(component.isDisabled).toBe(true);
|
|
108
|
+
|
|
109
|
+
component.enable();
|
|
110
|
+
expect(component.isDisabled).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should disable through setDisabledState", () => {
|
|
114
|
+
component.setDisabledState(true);
|
|
115
|
+
expect(component.isDisabled).toBe(true);
|
|
116
|
+
|
|
117
|
+
component.setDisabledState(false);
|
|
118
|
+
expect(component.isDisabled).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should clear indeterminate and check when clicked", () => {
|
|
122
|
+
const indeterminateChange = vi.fn();
|
|
123
|
+
|
|
124
|
+
component.indeterminate = true;
|
|
125
|
+
component.indeterminateChange.subscribe(indeterminateChange);
|
|
126
|
+
fixture.detectChanges();
|
|
127
|
+
|
|
128
|
+
toggleInput(true);
|
|
129
|
+
|
|
130
|
+
expect(component.checked).toBe(true);
|
|
131
|
+
expect(component.indeterminate).toBe(false);
|
|
132
|
+
expect(indeterminateChange).toHaveBeenCalledWith(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should set indeterminate programmatically", () => {
|
|
136
|
+
const indeterminateChange = vi.fn();
|
|
137
|
+
|
|
138
|
+
component.indeterminateChange.subscribe(indeterminateChange);
|
|
139
|
+
component.setIndeterminate(true);
|
|
140
|
+
|
|
141
|
+
expect(component.indeterminate).toBe(true);
|
|
142
|
+
expect(indeterminateChange).toHaveBeenCalledWith(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should render the default checked icon", () => {
|
|
146
|
+
fixture.componentRef.setInput("checked", true);
|
|
147
|
+
fixture.detectChanges();
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
fixture.nativeElement.querySelector(".cao-checkbox__icon--default"),
|
|
151
|
+
).toBeTruthy();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should render the default indeterminate icon", () => {
|
|
155
|
+
fixture.componentRef.setInput("indeterminate", true);
|
|
156
|
+
fixture.detectChanges();
|
|
157
|
+
|
|
158
|
+
const icon = fixture.nativeElement.querySelector(
|
|
159
|
+
".cao-checkbox__icon--default line",
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(icon).toBeTruthy();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should render Lucide icons for every state", () => {
|
|
166
|
+
fixture.componentRef.setInput("checkedIcon", "check");
|
|
167
|
+
fixture.componentRef.setInput("uncheckedIcon", "x");
|
|
168
|
+
fixture.componentRef.setInput("indeterminateIcon", "minus");
|
|
169
|
+
fixture.componentRef.setInput("isLucideIcon", true);
|
|
170
|
+
|
|
171
|
+
fixture.componentRef.setInput("checked", true);
|
|
172
|
+
fixture.detectChanges();
|
|
173
|
+
|
|
174
|
+
expect(component.checkedIcon).toBe("check");
|
|
175
|
+
expect(
|
|
176
|
+
fixture.nativeElement.querySelector("svg.cao-checkbox__icon"),
|
|
177
|
+
).toBeTruthy();
|
|
178
|
+
expect(
|
|
179
|
+
fixture.nativeElement.querySelector(".cao-checkbox__icon--default"),
|
|
180
|
+
).toBeNull();
|
|
181
|
+
|
|
182
|
+
fixture.componentRef.setInput("checked", false);
|
|
183
|
+
fixture.detectChanges();
|
|
184
|
+
|
|
185
|
+
expect(
|
|
186
|
+
fixture.nativeElement.querySelector("svg.cao-checkbox__icon"),
|
|
187
|
+
).toBeTruthy();
|
|
188
|
+
|
|
189
|
+
fixture.componentRef.setInput("indeterminate", true);
|
|
190
|
+
fixture.detectChanges();
|
|
191
|
+
|
|
192
|
+
expect(
|
|
193
|
+
fixture.nativeElement.querySelector("svg.cao-checkbox__icon"),
|
|
194
|
+
).toBeTruthy();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should render an image when isLucideIcon is false", () => {
|
|
198
|
+
fixture.componentRef.setInput("checkedIcon", "icon.svg");
|
|
199
|
+
fixture.componentRef.setInput("checked", true);
|
|
200
|
+
fixture.detectChanges();
|
|
201
|
+
|
|
202
|
+
const image: HTMLImageElement = fixture.nativeElement.querySelector(
|
|
203
|
+
"img.cao-checkbox__icon",
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(image).toBeTruthy();
|
|
207
|
+
expect(image.getAttribute("src")).toBe("icon.svg");
|
|
208
|
+
expect(
|
|
209
|
+
fixture.nativeElement.querySelector("svg.cao-checkbox__icon"),
|
|
210
|
+
).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should not render an icon when unchecked and no icon is provided", () => {
|
|
214
|
+
fixture.detectChanges();
|
|
215
|
+
|
|
216
|
+
expect(
|
|
217
|
+
fixture.nativeElement.querySelector(
|
|
218
|
+
".cao-checkbox__control svg, .cao-checkbox__control img",
|
|
219
|
+
),
|
|
220
|
+
).toBeNull();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should render the label and respect labelPosition", () => {
|
|
224
|
+
fixture.componentRef.setInput("label", "Aceito os termos");
|
|
225
|
+
fixture.detectChanges();
|
|
226
|
+
|
|
227
|
+
expect(
|
|
228
|
+
fixture.nativeElement
|
|
229
|
+
.querySelector(".cao-checkbox__label")
|
|
230
|
+
.textContent.trim(),
|
|
231
|
+
).toBe("Aceito os termos");
|
|
232
|
+
|
|
233
|
+
fixture.componentRef.setInput("labelPosition", "before");
|
|
234
|
+
fixture.detectChanges();
|
|
235
|
+
|
|
236
|
+
expect(
|
|
237
|
+
fixture.nativeElement
|
|
238
|
+
.querySelector(".cao-checkbox")
|
|
239
|
+
.classList.contains("cao-checkbox--label-before"),
|
|
240
|
+
).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should expose accessibility attributes", () => {
|
|
244
|
+
fixture.componentRef.setInput("ariaLabel", "Aceitar termos");
|
|
245
|
+
fixture.componentRef.setInput("required", true);
|
|
246
|
+
fixture.componentRef.setInput("name", "terms");
|
|
247
|
+
fixture.componentRef.setInput("dataCy", "checkbox-terms");
|
|
248
|
+
fixture.detectChanges();
|
|
249
|
+
|
|
250
|
+
const input = getInput();
|
|
251
|
+
|
|
252
|
+
expect(input.getAttribute("aria-label")).toBe("Aceitar termos");
|
|
253
|
+
expect(input.required).toBe(true);
|
|
254
|
+
expect(input.getAttribute("name")).toBe("terms");
|
|
255
|
+
expect(input.getAttribute("data-cy")).toBe("checkbox-terms");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should apply validation classes based on the form control", () => {
|
|
259
|
+
const control = new FormControl(false, Validators.requiredTrue);
|
|
260
|
+
|
|
261
|
+
fixture.componentRef.setInput("control", control);
|
|
262
|
+
control.markAsTouched();
|
|
263
|
+
fixture.detectChanges();
|
|
264
|
+
|
|
265
|
+
const label = fixture.nativeElement.querySelector(".cao-checkbox");
|
|
266
|
+
|
|
267
|
+
expect(label.classList.contains("cao-checkbox--invalid")).toBe(true);
|
|
268
|
+
|
|
269
|
+
control.setValue(true);
|
|
270
|
+
fixture.componentRef.changeDetectorRef.markForCheck();
|
|
271
|
+
fixture.detectChanges();
|
|
272
|
+
|
|
273
|
+
expect(label.classList.contains("cao-checkbox--valid")).toBe(true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should expose custom colors, sizes and icon colors as CSS variables", () => {
|
|
277
|
+
fixture.componentRef.setInput("color", "#16a34a");
|
|
278
|
+
fixture.componentRef.setInput("uncheckedColor", "#94a3b8");
|
|
279
|
+
fixture.componentRef.setInput("labelColor", "#166534");
|
|
280
|
+
fixture.componentRef.setInput("focusRingColor", "rgba(22, 163, 74, 0.35)");
|
|
281
|
+
fixture.componentRef.setInput("size", "24px");
|
|
282
|
+
fixture.componentRef.setInput("borderRadius", "6px");
|
|
283
|
+
fixture.componentRef.setInput("animationDuration", "300ms");
|
|
284
|
+
fixture.componentRef.setInput("checkedIconColor", "#ffffff");
|
|
285
|
+
fixture.componentRef.setInput("iconSize", 16);
|
|
286
|
+
fixture.componentRef.setInput("checked", true);
|
|
287
|
+
fixture.detectChanges();
|
|
288
|
+
|
|
289
|
+
const host = fixture.nativeElement as HTMLElement;
|
|
290
|
+
const styles = host.style;
|
|
291
|
+
|
|
292
|
+
expect(styles.getPropertyValue("--cao-checkbox-color")).toBe("#16a34a");
|
|
293
|
+
expect(styles.getPropertyValue("--cao-checkbox-unselected")).toBe("#94a3b8");
|
|
294
|
+
expect(styles.getPropertyValue("--cao-checkbox-label")).toBe("#166534");
|
|
295
|
+
expect(styles.getPropertyValue("--cao-checkbox-size")).toBe("24px");
|
|
296
|
+
expect(styles.getPropertyValue("--cao-checkbox-radius")).toBe("6px");
|
|
297
|
+
expect(styles.getPropertyValue("--cao-checkbox-duration")).toBe("300ms");
|
|
298
|
+
expect(styles.getPropertyValue("--cao-checkbox-icon-size")).toBe("16px");
|
|
299
|
+
expect(styles.getPropertyValue("--cao-checkbox-icon-color")).toBe("#ffffff");
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe("CaoCheckboxComponent with ngModel", () => {
|
|
304
|
+
let fixture: ComponentFixture<CheckboxHostComponent>;
|
|
305
|
+
|
|
306
|
+
beforeEach(async () => {
|
|
307
|
+
await TestBed.configureTestingModule({
|
|
308
|
+
imports: [CheckboxHostComponent],
|
|
309
|
+
}).compileComponents();
|
|
310
|
+
|
|
311
|
+
fixture = TestBed.createComponent(CheckboxHostComponent);
|
|
312
|
+
fixture.detectChanges();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
function getInput(): HTMLInputElement {
|
|
316
|
+
return fixture.nativeElement.querySelector("input");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
it("should render the initial form value", async () => {
|
|
320
|
+
fixture.componentRef.setInput("checked", true);
|
|
321
|
+
fixture.detectChanges();
|
|
322
|
+
await fixture.whenStable();
|
|
323
|
+
fixture.detectChanges();
|
|
324
|
+
|
|
325
|
+
expect(getInput().checked).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("should update the model when the input changes", () => {
|
|
329
|
+
const input = getInput();
|
|
330
|
+
input.checked = true;
|
|
331
|
+
input.dispatchEvent(new Event("change"));
|
|
332
|
+
fixture.detectChanges();
|
|
333
|
+
|
|
334
|
+
expect(fixture.componentInstance.checked).toBe(true);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should respect the disabled state", () => {
|
|
338
|
+
fixture.componentRef.setInput("isDisabled", true);
|
|
339
|
+
fixture.detectChanges();
|
|
340
|
+
|
|
341
|
+
expect(getInput().disabled).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { CommonModule } from "@angular/common";
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
ChangeDetectorRef,
|
|
5
|
+
Component,
|
|
6
|
+
EventEmitter,
|
|
7
|
+
forwardRef,
|
|
8
|
+
HostBinding,
|
|
9
|
+
inject,
|
|
10
|
+
Input,
|
|
11
|
+
OnChanges,
|
|
12
|
+
OnDestroy,
|
|
13
|
+
Output,
|
|
14
|
+
SimpleChanges,
|
|
15
|
+
} from "@angular/core";
|
|
16
|
+
import { AbstractControl, ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
|
|
17
|
+
|
|
18
|
+
import { LucideDynamicIcon } from "@lucide/angular";
|
|
19
|
+
import { Subscription } from "rxjs";
|
|
20
|
+
|
|
21
|
+
let nextCheckboxId = 0;
|
|
22
|
+
|
|
23
|
+
export interface ICaoCheckboxChangeEvent {
|
|
24
|
+
checked: boolean;
|
|
25
|
+
source: CaoCheckboxComponent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Component({
|
|
29
|
+
selector: "cao-checkbox",
|
|
30
|
+
standalone: true,
|
|
31
|
+
imports: [CommonModule, LucideDynamicIcon],
|
|
32
|
+
templateUrl: "./checkbox.component.html",
|
|
33
|
+
styleUrl: "./checkbox.component.css",
|
|
34
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
35
|
+
providers: [
|
|
36
|
+
{
|
|
37
|
+
provide: NG_VALUE_ACCESSOR,
|
|
38
|
+
useExisting: forwardRef(() => CaoCheckboxComponent),
|
|
39
|
+
multi: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
export class CaoCheckboxComponent implements ControlValueAccessor, OnChanges, OnDestroy {
|
|
44
|
+
@Input() checked = false;
|
|
45
|
+
@Input() indeterminate = false;
|
|
46
|
+
@Input() isDisabled = false;
|
|
47
|
+
@Input() required = false;
|
|
48
|
+
@Input() label = "";
|
|
49
|
+
@Input() labelPosition: "before" | "after" = "after";
|
|
50
|
+
@Input() name = "";
|
|
51
|
+
@Input() id = "";
|
|
52
|
+
@Input() ariaLabel = "";
|
|
53
|
+
@Input() ariaLabelledBy = "";
|
|
54
|
+
@Input() dataCy = "";
|
|
55
|
+
@Input() control?: AbstractControl | null;
|
|
56
|
+
@Input() errorMessage = "";
|
|
57
|
+
|
|
58
|
+
@Input() color?: string;
|
|
59
|
+
@Input() uncheckedColor?: string;
|
|
60
|
+
@Input() disabledColor?: string;
|
|
61
|
+
@Input() labelColor?: string;
|
|
62
|
+
@Input() disabledLabelColor?: string;
|
|
63
|
+
@Input() focusRingColor?: string;
|
|
64
|
+
|
|
65
|
+
@Input() size?: string;
|
|
66
|
+
@Input() borderRadius?: string;
|
|
67
|
+
@Input() animationDuration?: string;
|
|
68
|
+
|
|
69
|
+
@Input() checkedIcon = "";
|
|
70
|
+
@Input() uncheckedIcon = "";
|
|
71
|
+
@Input() indeterminateIcon = "";
|
|
72
|
+
@Input() isLucideIcon = false;
|
|
73
|
+
@Input() iconColor = "";
|
|
74
|
+
@Input() checkedIconColor = "";
|
|
75
|
+
@Input() uncheckedIconColor = "";
|
|
76
|
+
@Input() iconSize: string | number = 14;
|
|
77
|
+
@Input() iconStrokeWidth: string | number = 2.5;
|
|
78
|
+
|
|
79
|
+
@Input() set disabled(value: boolean) {
|
|
80
|
+
this.isDisabled = value;
|
|
81
|
+
this.cdr.markForCheck();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get disabled(): boolean {
|
|
85
|
+
return this.isDisabled;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Output() readonly checkedChange = new EventEmitter<boolean>();
|
|
89
|
+
@Output() readonly indeterminateChange = new EventEmitter<boolean>();
|
|
90
|
+
@Output() readonly checkboxChange = new EventEmitter<ICaoCheckboxChangeEvent>();
|
|
91
|
+
|
|
92
|
+
private readonly cdr = inject(ChangeDetectorRef);
|
|
93
|
+
private readonly uid = `cao-checkbox-${nextCheckboxId++}`;
|
|
94
|
+
private controlSub?: Subscription;
|
|
95
|
+
private onChange: (value: boolean) => void = () => {};
|
|
96
|
+
private onTouched: () => void = () => {};
|
|
97
|
+
|
|
98
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
99
|
+
if (changes["control"]) {
|
|
100
|
+
this.controlSub?.unsubscribe();
|
|
101
|
+
this.controlSub = this.control?.statusChanges.subscribe(() =>
|
|
102
|
+
this.cdr.markForCheck(),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
ngOnDestroy(): void {
|
|
108
|
+
this.controlSub?.unsubscribe();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get inputId(): string {
|
|
112
|
+
return this.id || this.uid;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get hasError(): boolean {
|
|
116
|
+
return !!this.errorMessage;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get hasInvalidState(): boolean {
|
|
120
|
+
return !!(this.control?.touched && this.control?.invalid);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get hasValidState(): boolean {
|
|
124
|
+
return !!(this.control?.touched && this.control?.valid);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get resolvedIconColor(): string {
|
|
128
|
+
if (this.indeterminate) {
|
|
129
|
+
return this.iconColor || this.checkedIconColor;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return this.checked
|
|
133
|
+
? this.checkedIconColor || this.iconColor
|
|
134
|
+
: this.uncheckedIconColor || this.iconColor;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@HostBinding("style")
|
|
138
|
+
get hostStyles(): Record<string, string> {
|
|
139
|
+
const styles: Record<string, string> = {};
|
|
140
|
+
|
|
141
|
+
if (this.color) {
|
|
142
|
+
styles["--cao-checkbox-color"] = this.color;
|
|
143
|
+
}
|
|
144
|
+
if (this.uncheckedColor) {
|
|
145
|
+
styles["--cao-checkbox-unselected"] = this.uncheckedColor;
|
|
146
|
+
}
|
|
147
|
+
if (this.disabledColor) {
|
|
148
|
+
styles["--cao-checkbox-disabled"] = this.disabledColor;
|
|
149
|
+
}
|
|
150
|
+
if (this.labelColor) {
|
|
151
|
+
styles["--cao-checkbox-label"] = this.labelColor;
|
|
152
|
+
}
|
|
153
|
+
if (this.disabledLabelColor) {
|
|
154
|
+
styles["--cao-checkbox-label-disabled"] = this.disabledLabelColor;
|
|
155
|
+
}
|
|
156
|
+
if (this.focusRingColor) {
|
|
157
|
+
styles["--cao-checkbox-focus-ring"] = this.focusRingColor;
|
|
158
|
+
}
|
|
159
|
+
if (this.size) {
|
|
160
|
+
styles["--cao-checkbox-size"] = this.size;
|
|
161
|
+
}
|
|
162
|
+
if (this.borderRadius) {
|
|
163
|
+
styles["--cao-checkbox-radius"] = this.borderRadius;
|
|
164
|
+
}
|
|
165
|
+
if (this.animationDuration) {
|
|
166
|
+
styles["--cao-checkbox-duration"] = this.animationDuration;
|
|
167
|
+
}
|
|
168
|
+
if (this.resolvedIconColor) {
|
|
169
|
+
styles["--cao-checkbox-icon-color"] = this.resolvedIconColor;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
styles["--cao-checkbox-icon-size"] = this.toCssLength(this.iconSize);
|
|
173
|
+
|
|
174
|
+
return styles;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
toggle(): void {
|
|
178
|
+
if (this.isDisabled) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.setChecked(!this.checked, true);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setChecked(value: boolean, emit = true): void {
|
|
186
|
+
if (this.isDisabled) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const wasIndeterminate = this.indeterminate;
|
|
191
|
+
|
|
192
|
+
this.checked = value;
|
|
193
|
+
this.indeterminate = false;
|
|
194
|
+
this.onChange(value);
|
|
195
|
+
|
|
196
|
+
if (emit) {
|
|
197
|
+
this.checkedChange.emit(value);
|
|
198
|
+
this.checkboxChange.emit({ checked: value, source: this });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (wasIndeterminate) {
|
|
202
|
+
this.indeterminateChange.emit(false);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.cdr.markForCheck();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setIndeterminate(value: boolean): void {
|
|
209
|
+
if (this.isDisabled || this.indeterminate === value) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.indeterminate = value;
|
|
214
|
+
this.indeterminateChange.emit(value);
|
|
215
|
+
this.cdr.markForCheck();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
enable(): void {
|
|
219
|
+
this.isDisabled = false;
|
|
220
|
+
this.cdr.markForCheck();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
disable(): void {
|
|
224
|
+
this.isDisabled = true;
|
|
225
|
+
this.cdr.markForCheck();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onInputChange(event: Event): void {
|
|
229
|
+
const target = event.target as HTMLInputElement;
|
|
230
|
+
this.setChecked(target.checked, true);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
onBlur(): void {
|
|
234
|
+
this.onTouched();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
writeValue(value: boolean | null): void {
|
|
238
|
+
this.checked = !!value;
|
|
239
|
+
this.cdr.markForCheck();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
registerOnChange(fn: (value: boolean) => void): void {
|
|
243
|
+
this.onChange = fn;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
registerOnTouched(fn: () => void): void {
|
|
247
|
+
this.onTouched = fn;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
setDisabledState(isDisabled: boolean): void {
|
|
251
|
+
this.isDisabled = isDisabled;
|
|
252
|
+
this.cdr.markForCheck();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private toCssLength(value: string | number): string {
|
|
256
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
257
|
+
}
|
|
258
|
+
}
|