UNPKG

@material/web

Version:
307 lines (306 loc) 10.8 kB
/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { __decorate } from "tslib"; import '../../focus/md-focus-ring.js'; import '../../ripple/ripple.js'; import { html, isServer, LitElement, nothing } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { requestUpdateOnAriaChange } from '../../internal/aria/delegate.js'; import { dispatchActivationClick, isActivationClick, redispatchEvent } from '../../internal/controller/events.js'; /** * A checkbox component. */ export class Checkbox extends LitElement { /** * The HTML name to use in form submission. */ get name() { return this.getAttribute('name') ?? ''; } set name(name) { this.setAttribute('name', name); } /** * The associated form element with which this element's value will submit. */ get form() { return this.internals.form; } /** * The labels this element is associated with. */ get labels() { return this.internals.labels; } /** * Returns a ValidityState object that represents the validity states of the * checkbox. * * Note that checkboxes will only set `valueMissing` if `required` and not * checked. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation */ get validity() { this.syncValidity(); return this.internals.validity; } /** * Returns the native validation error message. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process */ get validationMessage() { this.syncValidity(); return this.internals.validationMessage; } /** * Returns whether an element will successfully validate based on forms * validation rules and constraints. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process */ get willValidate() { this.syncValidity(); return this.internals.willValidate; } constructor() { super(); /** * Whether or not the checkbox is selected. */ this.checked = false; /** * Whether or not the checkbox is disabled. */ this.disabled = false; /** * Whether or not the checkbox is indeterminate. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes */ this.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 */ this.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 */ this.value = 'on'; this.prevChecked = false; this.prevDisabled = false; this.prevIndeterminate = false; // Needed for Safari, see https://bugs.webkit.org/show_bug.cgi?id=261432 // Replace with this.internals.validity.customError when resolved. this.hasCustomValidityError = false; this.internals = this /* needed for closure */.attachInternals(); if (!isServer) { this.addEventListener('click', (event) => { if (!isActivationClick(event)) { return; } this.focus(); dispatchActivationClick(this.input); }); } } /** * Checks the checkbox's native validation and returns whether or not the * element is valid. * * If invalid, this method will dispatch the `invalid` event. * * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/checkValidity * * @return true if the checkbox is valid, or false if not. */ checkValidity() { this.syncValidity(); return this.internals.checkValidity(); } /** * Checks the checkbox's native validation and returns whether or not the * element is valid. * * If invalid, this method will dispatch the `invalid` event. * * The `validationMessage` is reported to the user by the browser. Use * `setCustomValidity()` to customize the `validationMessage`. * * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity * * @return true if the checkbox is valid, or false if not. */ reportValidity() { this.syncValidity(); return this.internals.reportValidity(); } /** * Sets the checkbox's native validation error message. This is used to * customize `validationMessage`. * * When the error is not an empty string, the checkbox is considered invalid * and `validity.customError` will be true. * * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity * * @param error The error message to display. */ setCustomValidity(error) { this.hasCustomValidityError = !!error; this.internals.setValidity({ customError: !!error }, error, this.getInput()); } update(changed) { 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; } const shouldAddFormValue = this.checked && !this.indeterminate; const state = String(this.checked); this.internals.setFormValue(shouldAddFormValue ? this.value : null, state); super.update(changed); } 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; // Note: <input> needs to be rendered before the <svg> for // form.reportValidity() to work in Chrome. return html ` <div class="container ${containerClasses}"> <input type="checkbox" id="input" aria-checked=${isIndeterminate ? 'mixed' : nothing} aria-label=${ariaLabel || nothing} aria-invalid=${ariaInvalid || nothing} ?disabled=${this.disabled} ?required=${this.required} .indeterminate=${this.indeterminate} .checked=${this.checked} @change=${this.handleChange} > <div class="outline"></div> <div class="background"></div> <md-focus-ring part="focus-ring" for="input"></md-focus-ring> <md-ripple for="input" ?disabled=${this.disabled}></md-ripple> <svg class="icon" viewBox="0 0 18 18" aria-hidden="true"> <rect class="mark short" /> <rect class="mark long" /> </svg> </div> `; } updated() { // Sync validity when properties change, since validation properties may // have changed. this.syncValidity(); } handleChange(event) { const target = event.target; this.checked = target.checked; this.indeterminate = target.indeterminate; redispatchEvent(this, event); } syncValidity() { // Sync the internal <input>'s validity and the host's ElementInternals // validity. We do this to re-use native `<input>` validation messages. const input = this.getInput(); if (this.hasCustomValidityError) { input.setCustomValidity(this.internals.validationMessage); } else { input.setCustomValidity(''); } this.internals.setValidity(input.validity, input.validationMessage, this.getInput()); } getInput() { if (!this.input) { // If the input is not yet defined, synchronously render. this.connectedCallback(); this.performUpdate(); } if (this.isUpdatePending) { // If there are pending updates, synchronously perform them. This ensures // that constraint validation properties (like `required`) are synced // before interacting with input APIs that depend on them. this.scheduleUpdate(); } return this.input; } /** @private */ formResetCallback() { // The checked property does not reflect, so the original attribute set by // the user is used to determine the default value. this.checked = this.hasAttribute('checked'); } /** @private */ formStateRestoreCallback(state) { this.checked = state === 'true'; } } (() => { requestUpdateOnAriaChange(Checkbox); })(); /** @nocollapse */ Checkbox.shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }; /** @nocollapse */ Checkbox.formAssociated = true; __decorate([ property({ type: Boolean }) ], Checkbox.prototype, "checked", void 0); __decorate([ property({ type: Boolean, reflect: true }) ], Checkbox.prototype, "disabled", void 0); __decorate([ property({ type: Boolean }) ], Checkbox.prototype, "indeterminate", void 0); __decorate([ property({ type: Boolean }) ], Checkbox.prototype, "required", void 0); __decorate([ property() ], Checkbox.prototype, "value", void 0); __decorate([ state() ], Checkbox.prototype, "prevChecked", void 0); __decorate([ state() ], Checkbox.prototype, "prevDisabled", void 0); __decorate([ state() ], Checkbox.prototype, "prevIndeterminate", void 0); __decorate([ query('input') ], Checkbox.prototype, "input", void 0); //# sourceMappingURL=checkbox.js.map