import { html, unsafeCSS, nothing } from 'lit'; import { PieElement } from '@justeattakeaway/pie-webc-core/src/internals/PieElement'; import { classMap } from 'lit/directives/class-map.js'; import { property, query, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { consume } from '@lit/context'; import { wrapNativeEvent, FormControlMixin, DelegatesFocusMixin, validPropertyValues, safeCustomElement, ariaContext, type ContextualAria, } from '@justeattakeaway/pie-webc-core'; import '@justeattakeaway/pie-assistive-text'; import styles from './checkbox.scss?inline'; import { type CheckboxProps, defaultProps, statusTypes, labelPositions, labelFits, } from './defs'; // Valid values available to consumers export * from './defs'; const componentSelector = 'pie-checkbox'; const assistiveTextId = 'assistive-text'; /** * @tagname pie-checkbox * @slot - Default slot * @event {CustomEvent} change - when checked state is changed. */ @safeCustomElement('pie-checkbox') export class PieCheckbox extends DelegatesFocusMixin(FormControlMixin(PieElement)) implements CheckboxProps { @state() private _disabledByParent = false; @state() private _visuallyHiddenError = false; @state() private _isAnimationAllowed = false; // Optional ARIA supplied by an ancestor (for example a `pie-list-item`), applied to the // internal input (the element carrying the checkbox semantics). Undefined when standalone, so // it has no effect there. @consume({ context: ariaContext, subscribe: true }) @state() private _contextAria?: ContextualAria; @property({ type: String }) public value = defaultProps.value; @property({ type: String, reflect: true }) public name: CheckboxProps['name']; @property({ type: Boolean, reflect: true }) public checked = defaultProps.checked; @property({ type: Boolean, reflect: true }) public defaultChecked = defaultProps.defaultChecked; @property({ type: Boolean, reflect: true }) public disabled = defaultProps.disabled; @property({ type: Boolean, reflect: true }) public required = defaultProps.required; @property({ type: Boolean, reflect: true }) public indeterminate = defaultProps.indeterminate; @query('input[type="checkbox"]') private _checkbox!: HTMLInputElement; @property({ type: String }) public assistiveText: CheckboxProps['assistiveText']; @property({ type: String }) @validPropertyValues(componentSelector, statusTypes, defaultProps.status) public status = defaultProps.status; @property({ type: String, reflect: true }) @validPropertyValues(componentSelector, labelPositions, defaultProps.labelPosition) public labelPosition = defaultProps.labelPosition; @property({ type: String, reflect: true }) @validPropertyValues(componentSelector, labelFits, defaultProps.labelFit) public labelFit = defaultProps.labelFit; private _abortController!: AbortController; connectedCallback () : void { super.connectedCallback(); this._abortController = new AbortController(); const { signal } = this._abortController; this.addEventListener('pie-checkbox-group-disabled', (e: CustomEventInit) => { this._disabledByParent = e.detail.disabled; }, { signal }); this.addEventListener('pie-checkbox-group-error', (e: CustomEventInit) => { this._visuallyHiddenError = e.detail.error; }, { signal }); // Allows a click dispatched on the host (for example forwarded from a `pie-list-item` // row) to toggle the checkbox. this.addEventListener('click', this._handleClick, { signal }); } /** * Forwards a click dispatched directly on the host (for example from a `pie-list-item` row) * to the internal input. Clicks on the internal label already toggle the input natively via * its `for` attribute, so only host-level clicks are forwarded, to avoid a double toggle. * @param {MouseEvent} event */ private _handleClick (event: MouseEvent): void { if (event.composedPath()[0] !== this) return; if (this.disabled || this._disabledByParent) return; this._checkbox.click(); } disconnectedCallback () : void { super.disconnectedCallback(); this._abortController.abort(); } /** * (Read-only) returns a ValidityState with the validity states that this element is in. * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity */ public get validity (): ValidityState { return (this._checkbox as HTMLInputElement).validity; } /** * Ensures that the form value is in sync with the component. */ private _handleFormAssociation () : void { const isFormAssociated = !!this.form && !!this.name; if (isFormAssociated) { this._internals.setFormValue(this.checked ? this.value : null); } } /** * Called after the disabled state of the element changes, * either because the disabled attribute of this element was added or removed; * or because the disabled state changed on a