From 05e17d69e5b77b51300a7fb79e5a43b882efa8dc Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Wed, 18 May 2022 12:08:34 +0200 Subject: [PATCH] fix(checkbox-group): indeterminate checkbox respects disabled states (#1698) Co-authored-by: Danny Moerkerke --- .changeset/mighty-bats-decide.md | 5 + .../src/LionCheckboxIndeterminate.js | 28 ++++- .../test/lion-checkbox-indeterminate.test.js | 117 ++++++++++++++++++ yarn.lock | 2 +- 4 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 .changeset/mighty-bats-decide.md diff --git a/.changeset/mighty-bats-decide.md b/.changeset/mighty-bats-decide.md new file mode 100644 index 000000000..5822f2a0d --- /dev/null +++ b/.changeset/mighty-bats-decide.md @@ -0,0 +1,5 @@ +--- +'@lion/checkbox-group': patch +--- + +Checkbox indeterminate now properly handles disabled states on child checkboxes. diff --git a/packages/checkbox-group/src/LionCheckboxIndeterminate.js b/packages/checkbox-group/src/LionCheckboxIndeterminate.js index 6fbc947ea..7a49cdf20 100644 --- a/packages/checkbox-group/src/LionCheckboxIndeterminate.js +++ b/packages/checkbox-group/src/LionCheckboxIndeterminate.js @@ -13,7 +13,6 @@ export class LionCheckboxIndeterminate extends LionCheckbox { :host .choice-field__nested-checkboxes { display: block; } - ::slotted([slot='checkbox']) { padding-left: 8px; } @@ -86,6 +85,7 @@ export class LionCheckboxIndeterminate extends LionCheckbox { this.__settingOwnChecked = true; const checkedElements = subCheckboxes.filter(checkbox => checkbox.checked); + switch (subCheckboxes.length - checkedElements.length) { // all checked case 0: @@ -147,16 +147,32 @@ export class LionCheckboxIndeterminate extends LionCheckbox { } this.__settingOwnSubs = true; - if (this.indeterminate && this.mixedState) { + + const subCheckboxes = this._subCheckboxes; + const checkedElements = subCheckboxes.filter(checkbox => checkbox.checked); + const disabledElements = subCheckboxes.filter(checkbox => checkbox.disabled); + const allChecked = + subCheckboxes.length > 0 && subCheckboxes.length === checkedElements.length; + const allDisabled = + subCheckboxes.length > 0 && subCheckboxes.length === disabledElements.length; + const hasDisabledElements = disabledElements.length > 0; + + if (allDisabled) { + this.checked = allChecked; + } + + if (this.indeterminate && (this.mixedState || hasDisabledElements)) { this._subCheckboxes.forEach((checkbox, i) => { // eslint-disable-next-line no-param-reassign checkbox.checked = this._indeterminateSubStates[i]; }); } else { - this._subCheckboxes.forEach(checkbox => { - // eslint-disable-next-line no-param-reassign - checkbox.checked = this._inputNode.checked; - }); + this._subCheckboxes + .filter(checkbox => !checkbox.disabled) + .forEach(checkbox => { + // eslint-disable-next-line no-param-reassign + checkbox.checked = this._inputNode.checked; + }); } this.updateComplete.then(() => { this.__settingOwnSubs = false; diff --git a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js index 9170d76f6..334031335 100644 --- a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js +++ b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js @@ -153,6 +153,34 @@ describe('', () => { expect(elIndeterminate?.checked).to.be.true; }); + it('should become indeterminate if all children except disabled ones are checked', async () => { + // Arrange + const el = /** @type {LionCheckboxGroup} */ ( + await fixture(html` + + + + + + + + `) + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('lion-checkbox-indeterminate') + ); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); + + // Act + _subCheckboxes[0].checked = true; + _subCheckboxes[2].checked = true; + await el.updateComplete; + + // Assert + expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true; + expect(elIndeterminate?.checked).to.be.false; + }); + it('should sync all children when parent is checked (from indeterminate to checked)', async () => { // Arrange const el = /** @type {LionCheckboxGroup} */ ( @@ -182,6 +210,95 @@ describe('', () => { expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true; }); + it('should not sync any disabled children when parent is checked (from indeterminate to checked)', async () => { + // Arrange + const el = /** @type {LionCheckboxGroup} */ ( + await fixture(html` + + + + + + + + `) + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('lion-checkbox-indeterminate') + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); + + // Act + _inputNode.click(); + await elIndeterminate.updateComplete; + + // Assert + expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true; + }); + + it('should remain unchecked when parent is clicked and all children are disabled', async () => { + // Arrange + const el = /** @type {LionCheckboxGroup} */ ( + await fixture(html` + + + + + + + + `) + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('lion-checkbox-indeterminate') + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); + + // Act + _inputNode.click(); + await elIndeterminate.updateComplete; + + // Assert + expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; + expect(elIndeterminate.hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false; + }); + + it('should remain checked when parent is clicked and all children are disabled and checked', async () => { + // Arrange + const el = /** @type {LionCheckboxGroup} */ ( + await fixture(html` + + + + + + + + `) + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('lion-checkbox-indeterminate') + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); + + // Act + _inputNode.click(); + await elIndeterminate.updateComplete; + + // Assert + expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; + expect(elIndeterminate.hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true; + }); + it('should sync all children when parent is checked (from unchecked to checked)', async () => { // Arrange const el = /** @type {LionCheckboxGroup} */ ( diff --git a/yarn.lock b/yarn.lock index 61c011898..21bb74b9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,7 +3430,7 @@ autosize@4.0.2: resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.2.tgz#073cfd07c8bf45da4b9fd153437f5bafbba1e4c9" integrity sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA== -awesome-phonenumber@3.0.1: +awesome-phonenumber@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-3.0.1.tgz#8d73aaa1c2b0a660b117567b0d9797623457e1d0" integrity sha512-H5rqjTJ1+HxmyuSKDoPgvHUgP+RBRhtWQ25ccy4BmSLQL5UVg3K+yo2QCX4IlkxiVNst3suGMArV9TH7B1KEPw==