('change', {
detail: {
checked: this.checked,
value: this.value,
name: this.name,
indeterminate: this.indeterminate,
},
bubbles: true,
composed: true,
});
// Dual-dispatch: DOM event first
this.dispatchEvent(changeEvent);
// Then invoke callback if provided
if (this.onChange) {
this.onChange(changeEvent);
}
}
/**
* Render helper text
*/
private _renderHelper() {
if (!this.helpText) return nothing;
return html`
${this.helpText}
`;
}
/**
* Render error text
*/
private _renderError() {
if (!this.invalid || !this.errorMessage) return nothing;
return html`
${this.errorMessage}
`;
}
/**
* Build ARIA describedby attribute
*/
private _getAriaDescribedBy(): string | undefined {
return buildAriaDescribedBy({
helperId: this._ids.helperId,
errorId: this._ids.errorId,
hasHelper: !!this.helpText,
hasError: this.invalid && !!this.errorMessage,
});
}
override render() {
const wrapperClasses = `
checkbox-wrapper
${this.labelPosition === 'start' ? 'checkbox-wrapper--label-start' : ''}
`;
const labelClasses = `
checkbox-label
checkbox-label--${this.size}
checkbox-label--${this.theme}
`;
const labelCopyClasses = `
checkbox-label-copy
checkbox-label-copy--${this.size}
`;
// Build aria-describedby
const describedBy = this._getAriaDescribedBy();
// Main structure wraps everything
return html`
${this._renderHelper()}
${this._renderError()}
`;
}
override firstUpdated() {
this.inputRef = this.shadowRoot?.querySelector(
'.checkbox-input'
) as HTMLInputElement;
if (this.inputRef && this.indeterminate) {
this.inputRef.indeterminate = this.indeterminate;
}
// FACE: set initial form value and sync validity after first render
this._internals.setFormValue(this.checked ? (this.value || 'on') : null);
this._syncValidity();
this._syncStates();
}
}