/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../focus/md-focus-ring.js';
import '../../ripple/ripple.js';
import {html, isServer, LitElement, nothing, PropertyValues} from 'lit';
import {property, query, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {mixinDelegatesAria} from '../../internal/aria/delegate.js';
import {
dispatchActivationClick,
isActivationClick,
} from '../../internal/events/form-label-activation.js';
import {redispatchEvent} from '../../internal/events/redispatch-event.js';
import {
createValidator,
getValidityAnchor,
mixinConstraintValidation,
} from '../../labs/behaviors/constraint-validation.js';
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
import {
getFormState,
getFormValue,
mixinFormAssociated,
} from '../../labs/behaviors/form-associated.js';
import {CheckboxValidator} from '../../labs/behaviors/validators/checkbox-validator.js';
// Separate variable needed for closure.
const checkboxBaseClass = mixinDelegatesAria(
mixinConstraintValidation(
mixinFormAssociated(mixinElementInternals(LitElement)),
),
);
/**
* A checkbox component.
*
*
* @fires change {Event} The native `change` event on
* [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event)
* --bubbles
* @fires input {InputEvent} The native `input` event on
* [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event)
* --bubbles --composed
*/
export class Checkbox extends checkboxBaseClass {
/** @nocollapse */
static override shadowRootOptions = {
...LitElement.shadowRootOptions,
delegatesFocus: true,
};
/**
* Whether or not the checkbox is selected.
*/
@property({type: Boolean}) checked = false;
/**
* Whether or not the checkbox is indeterminate.
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes
*/
@property({type: Boolean}) indeterminate = false;
/**
* When true, require the checkbox to be selected when participating in
* form submission.
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation
*/
@property({type: Boolean}) required = false;
/**
* The value of the checkbox that is submitted with a form when selected.
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#value
*/
@property() value = 'on';
@state() private prevChecked = false;
@state() private prevDisabled = false;
@state() private prevIndeterminate = false;
@query('input') private readonly input!: HTMLInputElement | null;
constructor() {
super();
if (!isServer) {
this.addEventListener('click', (event: MouseEvent) => {
if (!isActivationClick(event) || !this.input) {
return;
}
this.focus();
dispatchActivationClick(this.input);
});
}
}
protected override update(changed: PropertyValues) {
if (
changed.has('checked') ||
changed.has('disabled') ||
changed.has('indeterminate')
) {
this.prevChecked = changed.get('checked') ?? this.checked;
this.prevDisabled = changed.get('disabled') ?? this.disabled;
this.prevIndeterminate =
changed.get('indeterminate') ?? this.indeterminate;
}
super.update(changed);
}
protected override render() {
const prevNone = !this.prevChecked && !this.prevIndeterminate;
const prevChecked = this.prevChecked && !this.prevIndeterminate;
const prevIndeterminate = this.prevIndeterminate;
const isChecked = this.checked && !this.indeterminate;
const isIndeterminate = this.indeterminate;
const containerClasses = classMap({
'disabled': this.disabled,
'selected': isChecked || isIndeterminate,
'unselected': !isChecked && !isIndeterminate,
'checked': isChecked,
'indeterminate': isIndeterminate,
'prev-unselected': prevNone,
'prev-checked': prevChecked,
'prev-indeterminate': prevIndeterminate,
'prev-disabled': this.prevDisabled,
});
// Needed for closure conformance
const {ariaLabel, ariaInvalid} = this as ARIAMixinStrict;
// Note: needs to be rendered before the