diff --git a/packages/checkbox/src/vaadin-checkbox.js b/packages/checkbox/src/vaadin-checkbox.js index a31a534f64..65567067ff 100644 --- a/packages/checkbox/src/vaadin-checkbox.js +++ b/packages/checkbox/src/vaadin-checkbox.js @@ -4,9 +4,12 @@ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; -import { GestureEventListeners } from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; -import { ControlStateMixin } from '@vaadin/vaadin-control-state-mixin/vaadin-control-state-mixin.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; +import { ActiveMixin } from '@vaadin/field-base/src/active-mixin.js'; +import { AriaLabelMixin } from '@vaadin/field-base/src/aria-label-mixin.js'; +import { CheckedMixin } from '@vaadin/field-base/src/checked-mixin.js'; +import { InputSlotMixin } from '@vaadin/field-base/src/input-slot-mixin.js'; +import { SlotLabelMixin } from '@vaadin/field-base/src/slot-label-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; /** @@ -46,12 +49,21 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix * @fires {CustomEvent} indeterminate-changed - Fired when the `indeterminate` property changes. * * @extends HTMLElement - * @mixes ElementMixin - * @mixes ControlStateMixin * @mixes ThemableMixin - * @mixes GestureEventListeners + * @mixes ElementMixin + * @mixes ActiveMixin + * @mixes AriaLabelMixin + * @mixes InputSlotMixin + * @mixes CheckedMixin + * @mixes SlotLabelMixin */ -class CheckboxElement extends ElementMixin(ControlStateMixin(ThemableMixin(GestureEventListeners(PolymerElement)))) { +class Checkbox extends SlotLabelMixin( + CheckedMixin(InputSlotMixin(AriaLabelMixin(ActiveMixin(ElementMixin(ThemableMixin(PolymerElement)))))) +) { + static get is() { + return 'vaadin-checkbox'; + } + static get template() { return html` - - +
+
+ +
+
+ +
+
+ +
+
`; } - static get is() { - return 'vaadin-checkbox'; - } - static get properties() { return { /** - * True if the checkbox is checked. - * @type {boolean} - */ - checked: { - type: Boolean, - value: false, - notify: true, - observer: '_checkedChanged', - reflectToAttribute: true - }, - - /** - * Indeterminate state of the checkbox when it's neither checked nor unchecked, but undetermined. + * True if the checkbox is in the indeterminate state which means + * it is not possible to say whether the checkbox is checked or unchecked. + * The state resets once the checkbox gets checked or unchecked. + * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Indeterminate_state_checkboxes + * * @type {boolean} */ indeterminate: { type: Boolean, notify: true, - observer: '_indeterminateChanged', - reflectToAttribute: true, - value: false + value: false, + reflectToAttribute: true }, /** - * The value given to the data submitted with the checkbox's name to the server when the control is inside a form. + * The value of the checkbox. */ value: { type: String, - value: 'on' - }, - - /** @private */ - _nativeCheckbox: { - type: Object + value: 'on', + observer: '_valueChanged', + reflectToAttribute: true } }; } - constructor() { - super(); - /** - * @type {string} - * Name of the element. - */ - this.name; - } - - get name() { - return this.checked ? this._storedName : ''; - } - - set name(name) { - this._storedName = name; - } - - ready() { - super.ready(); - - this.setAttribute('role', 'checkbox'); - - this._nativeCheckbox = this.shadowRoot.querySelector('input[type="checkbox"]'); - - this.addEventListener('click', this._handleClick.bind(this)); - - this._addActiveListeners(); - - const attrName = this.getAttribute('name'); - if (attrName) { - this.name = attrName; - } - - this.shadowRoot - .querySelector('[part~="label"]') - .querySelector('slot') - .addEventListener('slotchange', this._updateLabelAttribute.bind(this)); - - this._updateLabelAttribute(); - } - - /** @private */ - _updateLabelAttribute() { - const label = this.shadowRoot.querySelector('[part~="label"]'); - const assignedNodes = label.firstElementChild.assignedNodes(); - if (this._isAssignedNodesEmpty(assignedNodes)) { - label.setAttribute('empty', ''); - } else { - label.removeAttribute('empty'); - } - } - - /** @private */ - _isAssignedNodesEmpty(nodes) { - // The assigned nodes considered to be empty if there is no slotted content or only one empty text node - return ( - nodes.length === 0 || - (nodes.length == 1 && nodes[0].nodeType == Node.TEXT_NODE && nodes[0].textContent.trim() === '') - ); - } - - /** @private */ - _checkedChanged(checked) { - if (this.indeterminate) { - this.setAttribute('aria-checked', 'mixed'); - } else { - this.setAttribute('aria-checked', Boolean(checked)); - } - } - - /** @private */ - _indeterminateChanged(indeterminate) { - if (indeterminate) { - this.setAttribute('aria-checked', 'mixed'); - } else { - this.setAttribute('aria-checked', this.checked); - } + static get delegateProps() { + return [...super.delegateProps, 'indeterminate']; } - /** @private */ - _addActiveListeners() { - // DOWN - this._addEventListenerToNode(this, 'down', (e) => { - if (this.__interactionsAllowed(e)) { - this.setAttribute('active', ''); - } - }); - - // UP - this._addEventListenerToNode(this, 'up', () => this.removeAttribute('active')); - - // KEYDOWN - this.addEventListener('keydown', (e) => { - if (this.__interactionsAllowed(e) && e.keyCode === 32) { - e.preventDefault(); - this.setAttribute('active', ''); - } - }); - - // KEYUP - this.addEventListener('keyup', (e) => { - if (this.__interactionsAllowed(e) && e.keyCode === 32) { - e.preventDefault(); - this._toggleChecked(); - this.removeAttribute('active'); + constructor() { + super(); - if (this.indeterminate) { - this.indeterminate = false; - } - } - }); + this._setType('checkbox'); } /** - * @return {!HTMLInputElement} + * @override * @protected */ - get focusElement() { - return this.shadowRoot.querySelector('input'); + get _sourceSlot() { + return this.$.noop; } /** - * True if users' interactions (mouse or keyboard) - * should toggle the checkbox + * @protected + * @override */ - __interactionsAllowed(e) { - if (this.disabled) { - return false; - } + _toggleChecked() { + this.indeterminate = false; - // https://github.com/vaadin/vaadin-checkbox/issues/63 - if (e.target.localName === 'a') { - return false; - } + super._toggleChecked(); - return true; - } - - /** @private */ - _handleClick(e) { - if (this.__interactionsAllowed(e)) { - if (!this.indeterminate) { - if (e.composedPath()[0] !== this._nativeCheckbox) { - e.preventDefault(); - this._toggleChecked(); - } - } else { - /* - * Required for IE 11 and Edge. - * See issue here: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7344418/ - */ - this.indeterminate = false; - e.preventDefault(); - this._toggleChecked(); - } - } - } - - /** @protected */ - _toggleChecked() { - this.checked = !this.checked; this.dispatchEvent(new CustomEvent('change', { composed: false, bubbles: true })); } @@ -328,6 +175,6 @@ class CheckboxElement extends ElementMixin(ControlStateMixin(ThemableMixin(Gestu */ } -customElements.define(CheckboxElement.is, CheckboxElement); +customElements.define(Checkbox.is, Checkbox); -export { CheckboxElement }; +export { Checkbox }; diff --git a/packages/checkbox/theme/lumo/vaadin-checkbox-styles.js b/packages/checkbox/theme/lumo/vaadin-checkbox-styles.js index 86e0ccdfe4..fc91fab023 100644 --- a/packages/checkbox/theme/lumo/vaadin-checkbox-styles.js +++ b/packages/checkbox/theme/lumo/vaadin-checkbox-styles.js @@ -26,7 +26,6 @@ registerStyles( border-radius: var(--lumo-border-radius-s); background-color: var(--lumo-contrast-20pct); transition: transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), background-color 0.15s; - pointer-events: none; line-height: 1.2; cursor: var(--lumo-clickable-cursor); } diff --git a/packages/checkbox/theme/material/vaadin-checkbox-styles.js b/packages/checkbox/theme/material/vaadin-checkbox-styles.js index 26acb948cb..bf8d814342 100644 --- a/packages/checkbox/theme/material/vaadin-checkbox-styles.js +++ b/packages/checkbox/theme/material/vaadin-checkbox-styles.js @@ -32,7 +32,6 @@ registerStyles( position: relative; border-radius: 2px; box-shadow: inset 0 0 0 2px var(--material-secondary-text-color); - pointer-events: none; line-height: 1.275; background-color: transparent; } diff --git a/packages/field-base/src/aria-label-mixin.js b/packages/field-base/src/aria-label-mixin.js index 4db921dea7..0394a18d3c 100644 --- a/packages/field-base/src/aria-label-mixin.js +++ b/packages/field-base/src/aria-label-mixin.js @@ -9,27 +9,14 @@ import { InputMixin } from './input-mixin.js'; const AriaLabelMixinImplementation = (superclass) => class AriaLabelMixinClass extends InputMixin(LabelMixin(superclass)) { - constructor() { - super(); - - this.__preventDuplicateLabelClick = this.__preventDuplicateLabelClick.bind(this); - } - - /** @protected */ - connectedCallback() { - super.connectedCallback(); - - if (this._labelNode) { - this._labelNode.addEventListener('click', this.__preventDuplicateLabelClick); - } - } - /** @protected */ - disconnectedCallback() { - super.disconnectedCallback(); + ready() { + super.ready(); if (this._labelNode) { - this._labelNode.removeEventListener('click', this.__preventDuplicateLabelClick); + this._labelNode.addEventListener('click', (event) => { + this.__preventDuplicateLabelClick(event); + }); } } diff --git a/packages/field-base/src/helper-text-mixin.js b/packages/field-base/src/helper-text-mixin.js index 212e6bf22b..241c21d6ce 100644 --- a/packages/field-base/src/helper-text-mixin.js +++ b/packages/field-base/src/helper-text-mixin.js @@ -52,8 +52,8 @@ const HelperTextMixinImplementation = (superclass) => } /** @protected */ - connectedCallback() { - super.connectedCallback(); + ready() { + super.ready(); if (this._helperNode) { this._currentHelper = this._helperNode; @@ -61,11 +61,6 @@ const HelperTextMixinImplementation = (superclass) => this._applyCustomHelper(); } - } - - /** @protected */ - ready() { - super.ready(); this.__helperSlot = this.shadowRoot.querySelector('[name="helper"]'); this.__helperSlot.addEventListener('slotchange', this.__onHelperSlotChange.bind(this));