import { booleanAttribute, Component, HostBinding, input, Optional, Self } from "@angular/core"; import { NgControl, Validators } from "@angular/forms"; import { BitFormControlAbstraction } from "../form-control"; @Component({ selector: "input[type=checkbox][bitCheckbox]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: CheckboxComponent }], host: { "[disabled]": "disabled", }, }) export class CheckboxComponent implements BitFormControlAbstraction { @HostBinding("class") protected inputClasses = [ "tw-appearance-none", "tw-outline-none", "tw-box-border", "tw-relative", "tw-transition", "tw-cursor-pointer", "tw-inline-block", "tw-align-sub", "tw-flex-none", // Flexbox fix for bit-form-control "!tw-p-1", // Give checkbox explicit height and width to fix iOS rendering bug "tw-h-[calc(1.12rem_+_theme(spacing.2))]", "tw-w-[calc(1.12rem_+_theme(spacing.2))]", "after:tw-inset-1", // negative margin to negate the positioning added by the padding "!-tw-mt-1", "!-tw-mb-1", "!-tw-ms-1", "before:tw-content-['']", "before:tw-block", "before:tw-inset-0", "before:tw-h-[1.12rem]", "before:tw-w-[1.12rem]", "before:tw-rounded", "before:tw-border", "before:tw-border-solid", "before:tw-border-secondary-500", "before:tw-box-border", "after:tw-content-['']", "after:tw-block", "after:tw-absolute", "after:tw-inset-0", "after:tw-h-[1.12rem]", "after:tw-w-[1.12rem]", "after:tw-box-border", "hover:before:tw-border-2", "[&>label]:before:tw-border-2", // if it exists, the parent form control handles focus "[&:not(bit-form-control_*)]:focus-visible:before:tw-ring-2", "[&:not(bit-form-control_*)]:focus-visible:before:tw-ring-offset-2", "[&:not(bit-form-control_*)]:focus-visible:before:tw-ring-primary-600", "disabled:before:tw-cursor-auto", "disabled:before:tw-border", "disabled:before:hover:tw-border", "disabled:before:tw-bg-secondary-100", "disabled:hover:before:tw-bg-secondary-100", "checked:before:tw-bg-primary-600", "checked:before:tw-border-primary-600", "checked:before:hover:tw-bg-primary-700", "checked:before:hover:tw-border-primary-700", "[&>label:hover]:checked:before:tw-bg-primary-700", "[&>label:hover]:checked:before:tw-border-primary-700", "checked:after:tw-bg-text-contrast", "checked:after:tw-mask-position-[center]", "checked:after:tw-mask-repeat-[no-repeat]", "checked:disabled:before:tw-border-secondary-100", "checked:disabled:hover:before:tw-border-secondary-100", "checked:disabled:before:tw-bg-secondary-100", "checked:disabled:after:tw-bg-text-muted", "[&:not(:indeterminate)]:checked:after:tw-mask-image-[var(--mask-image)]", "indeterminate:after:tw-mask-image-[var(--indeterminate-mask-image)]", "indeterminate:before:tw-bg-primary-600", "indeterminate:before:tw-border-primary-600", "indeterminate:hover:before:tw-bg-primary-700", "indeterminate:hover:before:tw-border-primary-700", "[&>label:hover]:indeterminate:before:tw-bg-primary-700", "[&>label:hover]:indeterminate:before:tw-border-primary-700", "indeterminate:after:tw-bg-text-contrast", "indeterminate:after:tw-mask-position-[center]", "indeterminate:after:tw-mask-repeat-[no-repeat]", "indeterminate:after:tw-mask-image-[var(--indeterminate-mask-image)]", "indeterminate:disabled:tw-border-secondary-100", "indeterminate:disabled:tw-bg-secondary-100", "indeterminate:disabled:after:tw-bg-text-muted", ]; constructor(@Optional() @Self() private ngControl?: NgControl) {} @HostBinding("style.--mask-image") protected maskImage = `url('data:image/svg+xml,%3Csvg class="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10" viewBox="0 0 10 10"%3E%3Cpath d="M0.5 6.2L2.9 8.6L9.5 1.4" fill="none" stroke="white" stroke-width="2"%3E%3C/path%3E%3C/svg%3E')`; @HostBinding("style.--indeterminate-mask-image") protected indeterminateImage = `url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`; readonly disabledInput = input(false, { transform: booleanAttribute, alias: "disabled" }); // TODO migrate to computed signal when Angular adds signal support to reactive forms // https://bitwarden.atlassian.net/browse/CL-819 get disabled() { return this.disabledInput() || this.ngControl?.disabled || false; } get required() { return this.ngControl?.control?.hasValidator(Validators.requiredTrue) ?? false; } get hasError() { return !!(this.ngControl?.status === "INVALID" && this.ngControl?.touched); } get error(): [string, any] { const errors = this.ngControl?.errors ?? {}; const key = Object.keys(errors)[0]; return [key, errors[key]]; } }