Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,44 +151,54 @@ export class Checkbox implements ComponentInterface {
connectedCallback() {
const { el } = this;

// Watch for class changes to update validation state.
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
this.validationObserver = new MutationObserver(() => {
const newIsInvalid = checkInvalidState(el);
if (this.isInvalid !== newIsInvalid) {
this.isInvalid = newIsInvalid;
/**
* Screen readers tend to announce changes
* to `aria-describedby` when the attribute
* is changed during a blur event for a
* native form control.
* However, the announcement can be spotty
* when using a non-native form control
* and `forceUpdate()`.
* This is due to `forceUpdate()` internally
* rescheduling the DOM update to a lower
* priority queue regardless if it's called
* inside a Promise or not, thus causing
* the screen reader to potentially miss the
* change.
* By using a State variable inside a Promise,
* it guarantees a re-render immediately at
* a higher priority.
*/
Promise.resolve().then(() => {
this.hintTextId = this.getHintTextId();
});
this.validationObserver = new MutationObserver((mutations) => {
// Watch for label content changes
if (mutations.some((mutation) => mutation.type === 'characterData' || mutation.type === 'childList')) {
this.hasLabelContent = this.el.textContent !== '';
}
// Watch for class changes to update validation state.
if (mutations.some((mutation) => mutation.type === 'attributes' && mutation.target === el)) {
const newIsInvalid = checkInvalidState(el);
if (this.isInvalid !== newIsInvalid) {
this.isInvalid = newIsInvalid;
/**
* Screen readers tend to announce changes
* to `aria-describedby` when the attribute
* is changed during a blur event for a
* native form control.
* However, the announcement can be spotty
* when using a non-native form control
* and `forceUpdate()`.
* This is due to `forceUpdate()` internally
* rescheduling the DOM update to a lower
* priority queue regardless if it's called
* inside a Promise or not, thus causing
* the screen reader to potentially miss the
* change.
* By using a State variable inside a Promise,
* it guarantees a re-render immediately at
* a higher priority.
*/
Promise.resolve().then(() => {
this.hintTextId = this.getHintTextId();
});
}
}
});

this.validationObserver.observe(el, {
attributes: true,
attributeFilter: ['class'],
characterData: true,
childList: true,
subtree: true,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subtree: true applies to all observation types, not just characterData. So the attributeFilter: ['class'] now fires for descendant class changes too, not just the host element. It's harmless since the handler filters by mutation.type, but you could tighten the attributes branch with mutation.target === el to match the original observer's scope.

});
}

// Always set initial state
this.isInvalid = checkInvalidState(el);
this.hasLabelContent = this.el.textContent !== '';
}

componentWillLoad() {
Expand Down Expand Up @@ -267,10 +277,6 @@ export class Checkbox implements ComponentInterface {
ev.stopPropagation();
};

private onSlotChange = () => {
this.hasLabelContent = this.el.textContent !== '';
};

private getHintTextId(): string | undefined {
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;

Expand Down Expand Up @@ -387,7 +393,7 @@ export class Checkbox implements ComponentInterface {
id={this.inputLabelId}
onClick={this.onDivLabelClick}
>
<slot onSlotchange={this.onSlotChange}></slot>
<slot></slot>
{this.renderHintText()}
</div>
<div class="native-wrapper">
Expand Down
Loading