Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 2e491de

Browse files
authored
fix(checkbox): change checkbox event type from change to click and add some logic for IE browser (#5316)
Closes #4893 BREAKING CHANGE: remove event listener for 'change' and add event listener for 'click'. - Add handleClick() method in foundation to handle click event. - Add setCheck() method into component to change check status.
1 parent bac43eb commit 2e491de

File tree

6 files changed

+308
-16
lines changed

6 files changed

+308
-16
lines changed

packages/mdc-checkbox/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Method Signature | Description
182182
`isIndeterminate() => boolean` | Returns true if the component is in the indeterminate state.
183183
`isChecked() => boolean` | Returns true if the component is checked.
184184
`hasNativeControl() => boolean` | Returns true if the input is present in the component.
185+
`setChecked(checkStatus: boolean) => void` | Sets the check status of the component.
185186
`setNativeControlDisabled(disabled: boolean) => void` | Sets the input to disabled.
186187
`setNativeControlAttr(attr: string, value: string) => void` | Sets an HTML attribute to the given value on the native input element.
187188
`removeNativeControlAttr(attr: string) => void` | Removes an attribute from the native input element.
@@ -192,4 +193,5 @@ Method Signature | Description
192193
--- | ---
193194
`setDisabled(disabled: boolean) => void` | Updates the `disabled` property on the underlying input. Does nothing when the underlying input is not present.
194195
`handleAnimationEnd() => void` | `animationend` event handler that should be applied to the root element.
195-
`handleChange() => void` | `change` event handler that should be applied to the checkbox element.
196+
`handleClick() => void` | `click` event handler that should be applied to the checkbox element.
197+
`handleChange() => void` | Handles check status changes.

packages/mdc-checkbox/adapter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface MDCCheckboxAdapter {
3737
isIndeterminate(): boolean;
3838
removeClass(className: string): void;
3939
removeNativeControlAttr(attr: string): void;
40+
setChecked(checkStatus: boolean): void;
4041
setNativeControlAttr(attr: string, value: string): void;
4142
setNativeControlDisabled(disabled: boolean): void;
4243
}

packages/mdc-checkbox/component.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,20 @@ export class MDCCheckbox extends MDCComponent<MDCCheckboxFoundation> implements
8686
root_!: Element; // assigned in MDCComponent constructor
8787

8888
private readonly ripple_: MDCRipple = this.createRipple_();
89-
private handleChange_!: EventListener; // assigned in initialSyncWithDOM()
89+
private handleClick_!: EventListener; // assigned in initialSyncWithDOM()
9090
private handleAnimationEnd_!: EventListener; // assigned in initialSyncWithDOM()
9191

9292
initialSyncWithDOM() {
93-
this.handleChange_ = () => this.foundation_.handleChange();
93+
this.handleClick_ = () => this.foundation_.handleClick();
9494
this.handleAnimationEnd_ = () => this.foundation_.handleAnimationEnd();
95-
this.nativeControl_.addEventListener('change', this.handleChange_);
95+
this.nativeControl_.addEventListener('click', this.handleClick_);
9696
this.listen(getCorrectEventName(window, 'animationend'), this.handleAnimationEnd_);
9797
this.installPropertyChangeHooks_();
9898
}
9999

100100
destroy() {
101101
this.ripple_.destroy();
102-
this.nativeControl_.removeEventListener('change', this.handleChange_);
102+
this.nativeControl_.removeEventListener('click', this.handleClick_);
103103
this.unlisten(getCorrectEventName(window, 'animationend'), this.handleAnimationEnd_);
104104
this.uninstallPropertyChangeHooks_();
105105
super.destroy();
@@ -117,6 +117,7 @@ export class MDCCheckbox extends MDCComponent<MDCCheckboxFoundation> implements
117117
isIndeterminate: () => this.indeterminate,
118118
removeClass: (className) => this.root_.classList.remove(className),
119119
removeNativeControlAttr: (attr) => this.nativeControl_.removeAttribute(attr),
120+
setChecked: (checkStatus) => this.checked = checkStatus,
120121
setNativeControlAttr: (attr, value) => this.nativeControl_.setAttribute(attr, value),
121122
setNativeControlDisabled: (disabled) => this.nativeControl_.disabled = disabled,
122123
};

packages/mdc-checkbox/foundation.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,17 @@ export class MDCCheckboxFoundation extends MDCFoundation<MDCCheckboxAdapter> {
4848
isIndeterminate: () => false,
4949
removeClass: () => undefined,
5050
removeNativeControlAttr: () => undefined,
51+
setChecked: () => undefined,
5152
setNativeControlAttr: () => undefined,
5253
setNativeControlDisabled: () => undefined,
5354
};
5455
}
5556

5657
private currentCheckState_ = strings.TRANSITION_STATE_INIT;
58+
// Native checkboxes can only have two real states: checked/true or unchecked/false
59+
// The indeterminate state is visual only.
60+
// See https://stackoverflow.com/a/33529024 for more details.
61+
private realCheckState_ = false;
5762
private currentAnimationClass_ = '';
5863
private animEndLatchTimer_ = 0;
5964
private enableAnimationEndHandler_ = false;
@@ -64,6 +69,7 @@ export class MDCCheckboxFoundation extends MDCFoundation<MDCCheckboxAdapter> {
6469

6570
init() {
6671
this.currentCheckState_ = this.determineCheckState_();
72+
this.realCheckState_ = this.adapter_.isChecked();
6773
this.updateAriaChecked_();
6874
this.adapter_.addClass(cssClasses.UPGRADED);
6975
}
@@ -98,7 +104,20 @@ export class MDCCheckboxFoundation extends MDCFoundation<MDCCheckboxAdapter> {
98104
}
99105

100106
/**
101-
* Handles the change event for the checkbox
107+
* Handles the click event for the checkbox
108+
*/
109+
handleClick() {
110+
// added for IE browser to fix compatibility issue:
111+
// https://github.com/material-components/material-components-web/issues/4893
112+
const {TRANSITION_STATE_INDETERMINATE} = strings;
113+
if (this.currentCheckState_ === TRANSITION_STATE_INDETERMINATE) {
114+
this.adapter_.setChecked(!this.realCheckState_);
115+
}
116+
this.transitionCheckState_();
117+
}
118+
119+
/**
120+
* Handles the actions after check status changes
102121
*/
103122
handleChange() {
104123
this.transitionCheckState_();
@@ -118,11 +137,16 @@ export class MDCCheckboxFoundation extends MDCFoundation<MDCCheckboxAdapter> {
118137
this.updateAriaChecked_();
119138

120139
const {TRANSITION_STATE_UNCHECKED} = strings;
140+
const {TRANSITION_STATE_CHECKED} = strings;
121141
const {SELECTED} = cssClasses;
122142
if (newState === TRANSITION_STATE_UNCHECKED) {
123143
this.adapter_.removeClass(SELECTED);
144+
this.realCheckState_ = false;
124145
} else {
125146
this.adapter_.addClass(SELECTED);
147+
if (newState === TRANSITION_STATE_CHECKED) {
148+
this.realCheckState_ = true;
149+
}
126150
}
127151

128152
// Check to ensure that there isn't a previously existing animation class, in case for example

packages/mdc-checkbox/test/component.test.ts

+22-8
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ describe('MDCCheckbox', () => {
150150
expect(component.ripple instanceof MDCRipple).toBeTruthy();
151151
});
152152

153-
it('checkbox change event calls #foundation.handleChange', () => {
153+
it('checkbox click event calls #foundation.handleClick', () => {
154154
const {cb, component} = setupTest();
155-
(component as any).foundation_.handleChange = jasmine.createSpy();
156-
emitEvent(cb, 'change');
157-
expect((component as any).foundation_.handleChange).toHaveBeenCalled();
155+
(component as any).foundation_.handleClick = jasmine.createSpy();
156+
emitEvent(cb, 'click');
157+
expect((component as any).foundation_.handleClick).toHaveBeenCalled();
158158
});
159159

160160
it('root animationend event calls #foundation.handleAnimationEnd', () => {
@@ -178,12 +178,12 @@ describe('MDCCheckbox', () => {
178178
expect(mockFoundation.handleChange).toHaveBeenCalled();
179179
});
180180

181-
it('checkbox change event handler is destroyed on #destroy', () => {
181+
it('checkbox click event handler is destroyed on #destroy', () => {
182182
const {cb, component} = setupTest();
183-
(component as any).foundation_.handleChange = jasmine.createSpy();
183+
(component as any).foundation_.handleClick = jasmine.createSpy();
184184
component.destroy();
185-
emitEvent(cb, 'change');
186-
expect((component as any).foundation_.handleChange).not.toHaveBeenCalled();
185+
emitEvent(cb, 'click');
186+
expect((component as any).foundation_.handleClick).not.toHaveBeenCalled();
187187
});
188188

189189
it('root animationend event handler is destroyed on #destroy', () => {
@@ -304,6 +304,20 @@ describe('MDCCheckbox', () => {
304304
.toBe(false);
305305
});
306306

307+
it('#adapter.setChecked returns true when checkbox is checked', () => {
308+
const {cb, component} = setupTest();
309+
(component.getDefaultFoundation() as any)
310+
.adapter_.setChecked(true);
311+
expect(cb.checked).toBe(true);
312+
});
313+
314+
it('#adapter.setChecked returns false when checkbox is not checked', () => {
315+
const {cb, component} = setupTest();
316+
(component.getDefaultFoundation() as any)
317+
.adapter_.setChecked(false);
318+
expect(cb.checked).toBe(false);
319+
});
320+
307321
it('#adapter.hasNativeControl returns true when checkbox exists', () => {
308322
const {component} = setupTest();
309323
expect(

0 commit comments

Comments
 (0)