Skip to content

Commit

Permalink
feat(checkbox): support :state(checked) and :state(indeterminate)
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 694362061
  • Loading branch information
asyncLiz authored and copybara-github committed Dec 10, 2024
1 parent 223b88d commit 3961f46
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 10 deletions.
23 changes: 20 additions & 3 deletions checkbox/internal/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import {
getValidityAnchor,
mixinConstraintValidation,
} from '../../labs/behaviors/constraint-validation.js';
import {
hasState,
mixinCustomStateSet,
toggleState,
} from '../../labs/behaviors/custom-state-set.js';
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
import {
getFormState,
Expand All @@ -34,7 +39,7 @@ import {CheckboxValidator} from '../../labs/behaviors/validators/checkbox-valida
// Separate variable needed for closure.
const checkboxBaseClass = mixinDelegatesAria(
mixinConstraintValidation(
mixinFormAssociated(mixinElementInternals(LitElement)),
mixinFormAssociated(mixinCustomStateSet(mixinElementInternals(LitElement))),
),
);

Expand All @@ -59,14 +64,26 @@ export class Checkbox extends checkboxBaseClass {
/**
* Whether or not the checkbox is selected.
*/
@property({type: Boolean}) checked = false;
@property({type: Boolean})
get checked(): boolean {
return this[hasState]('checked');
}
set checked(checked: boolean) {
this[toggleState]('checked', checked);
}

/**
* Whether or not the checkbox is indeterminate.
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes
*/
@property({type: Boolean}) indeterminate = false;
@property({type: Boolean})
get indeterminate(): boolean {
return this[hasState]('indeterminate');
}
set indeterminate(indeterminate: boolean) {
this[toggleState]('indeterminate', indeterminate);
}

/**
* When true, require the checkbox to be selected when participating in
Expand Down
66 changes: 59 additions & 7 deletions checkbox/internal/checkbox_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,31 @@ describe('checkbox', () => {
expect(input.checked).toEqual(true);
expect(harness.element.checked).toEqual(true);
});

it('matches :state(checked) when true', async () => {
// Arrange
const {harness} = await setupTest();

// Act
harness.element.checked = true;
await env.waitForStability();

// Assert
expect(harness.element.matches(':state(checked)'))
.withContext("element.matches(':state(checked)')")
.toBeTrue();
});

it('does not match :state(checked) when false', async () => {
// Arrange
// Act
const {harness} = await setupTest();

// Assert
expect(harness.element.matches(':state(checked)'))
.withContext("element.matches(':state(checked)')")
.toBeFalse();
});
});

describe('indeterminate', () => {
Expand All @@ -169,6 +194,31 @@ describe('checkbox', () => {
expect(input.indeterminate).toEqual(false);
expect(input.getAttribute('aria-checked')).not.toEqual('mixed');
});

it('matches :state(indeterminate) when true', async () => {
// Arrange
const {harness} = await setupTest();

// Act
harness.element.indeterminate = true;
await env.waitForStability();

// Assert
expect(harness.element.matches(':state(indeterminate)'))
.withContext("element.matches(':state(indeterminate)')")
.toBeTrue();
});

it('does not match :state(indeterminate) when false', async () => {
// Arrange
// Act
const {harness} = await setupTest();

// Assert
expect(harness.element.matches(':state(indeterminate)'))
.withContext("element.matches(':state(indeterminate)')")
.toBeFalse();
});
});

describe('disabled', () => {
Expand All @@ -186,13 +236,15 @@ describe('checkbox', () => {

describe('form submission', () => {
async function setupFormTest(propsInit: Partial<Checkbox> = {}) {
return await setupTest(html` <form>
<md-test-checkbox
.checked=${propsInit.checked === true}
.disabled=${propsInit.disabled === true}
.name=${propsInit.name ?? ''}
.value=${propsInit.value ?? ''}></md-test-checkbox>
</form>`);
return await setupTest(
html`<form>
<md-test-checkbox
.checked=${propsInit.checked === true}
.disabled=${propsInit.disabled === true}
.name=${propsInit.name ?? ''}
.value=${propsInit.value ?? ''}></md-test-checkbox>
</form>`,
);
}

it('does not submit if not checked', async () => {
Expand Down
7 changes: 7 additions & 0 deletions docs/components/checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,10 @@ Token | Default value
<!-- mdformat on(autogenerated might break rendering in catalog) -->

<!-- auto-generated API docs end -->

#### States

| State | Description |
| --- | --- |
| `:state(checked)` | Matches when the checkbox is checked. |
| `:state(indeterminate)` | Matches when the checkbox is indeterminate. |

0 comments on commit 3961f46

Please sign in to comment.