diff --git a/packages/checkbox-group/src/vaadin-checkbox-group.d.ts b/packages/checkbox-group/src/vaadin-checkbox-group.d.ts index f8dca5015d..f3db4e31ca 100644 --- a/packages/checkbox-group/src/vaadin-checkbox-group.d.ts +++ b/packages/checkbox-group/src/vaadin-checkbox-group.d.ts @@ -3,7 +3,7 @@ * Copyright (c) 2021 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ -import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js'; +import { Checkbox } from '@vaadin/checkbox/src/vaadin-checkbox.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; @@ -116,7 +116,7 @@ declare class CheckboxGroupElement extends ThemableMixin(DirMixin(HTMLElement)) _removeCheckboxFromValue(value: string): void; - _changeSelectedCheckbox(checkbox: CheckboxElement | null): void; + _changeSelectedCheckbox(checkbox: Checkbox | null): void; _containsFocus(): boolean; diff --git a/packages/checkbox-group/src/vaadin-checkbox-group.js b/packages/checkbox-group/src/vaadin-checkbox-group.js index e1865fb5bd..268c46a7f6 100644 --- a/packages/checkbox-group/src/vaadin-checkbox-group.js +++ b/packages/checkbox-group/src/vaadin-checkbox-group.js @@ -5,7 +5,7 @@ */ import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js'; -import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js'; +import { Checkbox } from '@vaadin/checkbox/src/vaadin-checkbox.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; @@ -198,15 +198,13 @@ class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) { this.addEventListener('focusin', () => this._setFocused(this._containsFocus())); this.addEventListener('focusout', (e) => { - // validate when stepping out of the checkbox group - if ( - !this._checkboxes.some( - (checkbox) => e.relatedTarget === checkbox || checkbox.shadowRoot.contains(e.relatedTarget) - ) - ) { - this.validate(); - this._setFocused(false); + // Skip if focus is just moved to another checkbox. + if (this._checkboxes.some((checkbox) => checkbox.contains(e.relatedTarget))) { + return; } + + this.validate(); + this._setFocused(false); }); const checkedChangedListener = (e) => { @@ -264,7 +262,7 @@ class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) { /** @private */ _filterCheckboxes(nodes) { - return Array.from(nodes).filter((child) => child instanceof CheckboxElement); + return Array.from(nodes).filter((child) => child instanceof Checkbox); } /** @private */ @@ -293,7 +291,7 @@ class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) { } /** - * @param {CheckboxElement} checkbox + * @param {Checkbox} checkbox * @protected */ _changeSelectedCheckbox(checkbox) { diff --git a/packages/checkbox-group/test/checkbox-group.test.js b/packages/checkbox-group/test/checkbox-group.test.js index 66c3a2a056..72ea5e4cf3 100644 --- a/packages/checkbox-group/test/checkbox-group.test.js +++ b/packages/checkbox-group/test/checkbox-group.test.js @@ -1,6 +1,7 @@ -import { expect } from '@esm-bundle/chai'; import sinon from 'sinon'; -import { fixtureSync, focusin, focusout } from '@vaadin/testing-helpers'; +import { expect } from '@esm-bundle/chai'; +import { sendKeys } from '@web/test-runner-commands'; +import { fixtureSync } from '@vaadin/testing-helpers'; import '@polymer/polymer/lib/elements/dom-bind.js'; import '../vaadin-checkbox-group.js'; @@ -139,26 +140,33 @@ describe('vaadin-checkbox-group', () => { expect(checkboxes[2].checked).to.be.false; }); - it('should set focused attribute on focusin event dispatched', () => { - focusin(checkboxes[0]); + it('should set focused attribute on Tab', async () => { + // Focus on the first checkbox. + await sendKeys({ press: 'Tab' }); + + expect(checkboxes[0].hasAttribute('focused')).to.be.true; expect(group.hasAttribute('focused')).to.be.true; }); - it('should not set focused attribute on focusin event dispatched when disabled', () => { - group.disabled = true; - focusin(checkboxes[0]); - expect(group.hasAttribute('focused')).to.be.false; - }); + it('should remove focused attribute on Shift+Tab', async () => { + // Focus on the first checkbox. + await sendKeys({ press: 'Tab' }); - it('should remove focused attribute on checkbox focusout', () => { - focusin(checkboxes[0]); - focusout(checkboxes[0]); + // Move focus out of the checkbox group. + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); + + expect(checkboxes[0].hasAttribute('focused')).to.be.false; expect(group.hasAttribute('focused')).to.be.false; }); - it('should remove focused attribute on checkbox-group focusout', () => { - focusin(checkboxes[0]); - focusout(group); + it('should not set focused attribute on Tab when disabled', async () => { + group.disabled = true; + // Try to focus on the first checkbox. + await sendKeys({ press: 'Tab' }); + + expect(checkboxes[0].hasAttribute('focused')).to.be.false; expect(group.hasAttribute('focused')).to.be.false; }); @@ -330,23 +338,28 @@ describe('validation', () => { expect(group.hasAttribute('invalid')).to.be.false; }); - it('should pass validation and set invalid when field is required and user blurs out of the group', () => { + it('should run validation and set invalid when field is required and user blurs out of the group', async () => { group.required = true; - focusout(group, document.body); + + // Focus on the first checkbox. + await sendKeys({ press: 'Tab' }); + // Move focus out of the checkbox group. + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); + expect(group.invalid).to.be.true; }); - it('should not run validation while user is tabbing between checkboxes inside of the group', () => { + it('should not run validation while user is tabbing between checkboxes inside of the group', async () => { group.required = true; const spy = sinon.spy(group, 'validate'); - focusout(group, checkboxes[1]); - expect(spy.called).to.be.false; - }); - it('should not run validation while user is tabbing between checkboxes and focus moves to native checkbox', () => { - group.required = true; - const spy = sinon.spy(group, 'validate'); - focusout(group, checkboxes[1].focusElement); + // Focus on the first checkbox. + await sendKeys({ press: 'Tab' }); + // Focus on the second checkbox. + await sendKeys({ press: 'Tab' }); + expect(spy.called).to.be.false; }); diff --git a/packages/checkbox/package.json b/packages/checkbox/package.json index 61bbd7a304..5d3ff7d300 100644 --- a/packages/checkbox/package.json +++ b/packages/checkbox/package.json @@ -27,7 +27,7 @@ "dependencies": { "@polymer/polymer": "^3.0.0", "@vaadin/component-base": "^22.0.0-alpha6", - "@vaadin/vaadin-control-state-mixin": "^22.0.0-alpha6", + "@vaadin/field-base": "^22.0.0-alpha6", "@vaadin/vaadin-lumo-styles": "^22.0.0-alpha6", "@vaadin/vaadin-material-styles": "^22.0.0-alpha6", "@vaadin/vaadin-themable-mixin": "^22.0.0-alpha6" diff --git a/packages/checkbox/src/vaadin-checkbox.d.ts b/packages/checkbox/src/vaadin-checkbox.d.ts index 6b553d0db2..8ca30df2e4 100644 --- a/packages/checkbox/src/vaadin-checkbox.d.ts +++ b/packages/checkbox/src/vaadin-checkbox.d.ts @@ -3,9 +3,12 @@ * Copyright (c) 2021 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ -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 { ActiveMixin } from '@vaadin/component-base/src/active-mixin.js'; import { ElementMixin } from '@vaadin/component-base/src/element-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'; /** @@ -18,95 +21,82 @@ export type CheckboxCheckedChangedEvent = CustomEvent<{ value: boolean }>; */ export type CheckboxIndeterminateChangedEvent = CustomEvent<{ value: boolean }>; -export interface CheckboxElementEventMap { +export interface CheckboxCustomEventMap { 'checked-changed': CheckboxCheckedChangedEvent; 'indeterminate-changed': CheckboxIndeterminateChangedEvent; } -export interface CheckboxEventMap extends HTMLElementEventMap, CheckboxElementEventMap {} +export interface CheckboxEventMap extends HTMLElementEventMap, CheckboxCustomEventMap {} /** - * `` is a Web Component for customized checkboxes. + * `` is an input field representing a binary choice. * * ```html - * - * Make my profile visible - * + * I accept the terms and conditions * ``` * * ### Styling * * The following shadow DOM parts are available for styling: * - * Part name | Description - * ------------------|---------------- - * `checkbox` | The wrapper element for the native - * `label` | The wrapper element in which the component's children, namely the label, is slotted + * Part name | Description + * ------------|---------------- + * `container` | The container element + * `checkbox` | The wrapper element that contains slotted `` + * `label` | The wrapper element that contains slotted `