Skip to content

refactor(material-experimental/mdc-slide-toggle): remove usage of MDC adapter #24935

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 18, 2022
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
type="button"
[class.mdc-switch--selected]="checked"
[class.mdc-switch--unselected]="!checked"
[class.mdc-switch--checked]="checked"
[class.mdc-switch--disabled]="disabled"
[tabIndex]="tabIndex"
[disabled]="disabled"
[attr.id]="buttonId"
Expand All @@ -14,7 +16,8 @@
[attr.aria-labelledby]="_getAriaLabelledBy()"
[attr.aria-describedby]="ariaDescribedby"
[attr.aria-required]="required || null"
(click)="_handleClick($event)"
[attr.aria-checked]="checked"
(click)="_handleClick()"
#switch>
<div class="mdc-switch__track"></div>
<div class="mdc-switch__handle-track">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ describe('MDC-based MatSlideToggle without forms', () => {

// We fall back to pointing to the label if a value isn't provided.
expect(buttonElement.getAttribute('aria-labelledby')).toMatch(
/mat-mdc-slide-toggle-label-\d+/,
/mat-mdc-slide-toggle-\d+-label/,
);
}));

Expand Down
216 changes: 23 additions & 193 deletions src/material-experimental/mdc-slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,25 @@ import {
ChangeDetectionStrategy,
Component,
ViewEncapsulation,
AfterViewInit,
OnDestroy,
forwardRef,
ViewChild,
ElementRef,
Input,
Output,
EventEmitter,
ChangeDetectorRef,
Attribute,
Inject,
Optional,
} from '@angular/core';
import {deprecated} from '@material/switch';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {
BooleanInput,
coerceBooleanProperty,
coerceNumberProperty,
NumberInput,
} from '@angular/cdk/coercion';
import {NG_VALUE_ACCESSOR} from '@angular/forms';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {ThemePalette} from '@angular/material-experimental/mdc-core';
import {FocusMonitor} from '@angular/cdk/a11y';
import {_MatSlideToggleBase} from '@angular/material/slide-toggle';
import {
MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS,
MatSlideToggleDefaultOptions,
} from './slide-toggle-config';

// Increasing integer for generating unique ids for slide-toggle components.
let nextUniqueId = 0;

/** @docs-private */
export const MAT_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
export const MAT_SLIDE_TOGGLE_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MatSlideToggle),
multi: true,
Expand All @@ -63,6 +48,7 @@ export class MatSlideToggleChange {
selector: 'mat-slide-toggle',
templateUrl: 'slide-toggle.html',
styleUrls: ['slide-toggle.css'],
inputs: ['disabled', 'disableRipple', 'color', 'tabIndex'],
host: {
'class': 'mat-mdc-slide-toggle',
'[id]': 'id',
Expand All @@ -71,9 +57,6 @@ export class MatSlideToggleChange {
'[attr.aria-label]': 'null',
'[attr.name]': 'null',
'[attr.aria-labelledby]': 'null',
'[class.mat-primary]': 'color === "primary"',
'[class.mat-accent]': 'color !== "primary" && color !== "warn"',
'[class.mat-warn]': 'color === "warn"',
'[class.mat-mdc-slide-toggle-focused]': '_focused',
'[class.mat-mdc-slide-toggle-checked]': 'checked',
'[class._mat-animation-noopable]': '_noopAnimations',
Expand All @@ -83,116 +66,9 @@ export class MatSlideToggleChange {
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [MAT_SLIDE_TOGGLE_VALUE_ACCESSOR],
})
export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDestroy {
private _onChange = (_: any) => {};
private _onTouched = () => {};

private _uniqueId: string = `mat-mdc-slide-toggle-${++nextUniqueId}`;
private _required: boolean = false;
private _checked: boolean = false;
private _foundation: deprecated.MDCSwitchFoundation;
private _adapter: deprecated.MDCSwitchAdapter = {
addClass: className => this._switchElement.nativeElement.classList.add(className),
removeClass: className => this._switchElement.nativeElement.classList.remove(className),
setNativeControlChecked: checked => (this._checked = checked),
setNativeControlDisabled: disabled => (this._disabled = disabled),
setNativeControlAttr: (name, value) => {
this._switchElement.nativeElement.setAttribute(name, value);
},
};

/** Whether the slide toggle is currently focused. */
_focused: boolean;

/** Whether noop animations are enabled. */
_noopAnimations: boolean;

export class MatSlideToggle extends _MatSlideToggleBase<MatSlideToggleChange> {
/** Unique ID for the label element. */
_labelId = `mat-mdc-slide-toggle-label-${++nextUniqueId}`;

/** The color palette for this slide toggle. */
@Input() color: ThemePalette;

/** Name value will be applied to the button element if present. */
@Input() name: string | null = null;

/** A unique id for the slide-toggle button. If none is supplied, it will be auto-generated. */
@Input() id: string = this._uniqueId;

/** Tabindex for the input element. */
@Input()
get tabIndex(): number {
return this._tabIndex;
}
set tabIndex(value: NumberInput) {
this._tabIndex = coerceNumberProperty(value);
}
private _tabIndex: number;

/** Whether the label should appear after or before the slide-toggle. Defaults to 'after'. */
@Input() labelPosition: 'before' | 'after' = 'after';

/** Used to set the aria-label attribute on the underlying button element. */
@Input('aria-label') ariaLabel: string | null = null;

/** Used to set the aria-labelledby attribute on the underlying button element. */
@Input('aria-labelledby') ariaLabelledby: string | null = null;

/** Used to set the aria-describedby attribute on the underlying button element. */
@Input('aria-describedby') ariaDescribedby: string;

/** Whether the slide-toggle is required. */
@Input()
get required(): boolean {
return this._required;
}
set required(value: BooleanInput) {
this._required = coerceBooleanProperty(value);
}

/** Whether the slide-toggle element is checked or not. */
@Input()
get checked(): boolean {
return this._checked;
}
set checked(value: BooleanInput) {
this._checked = coerceBooleanProperty(value);

if (this._foundation) {
this._foundation.setChecked(this._checked);
}
}

/** Whether to disable the ripple on this checkbox. */
@Input()
get disableRipple(): boolean {
return this._disableRipple;
}
set disableRipple(disableRipple: BooleanInput) {
this._disableRipple = coerceBooleanProperty(disableRipple);
}
private _disableRipple = false;

/** Whether the slide toggle is disabled. */
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(disabled: BooleanInput) {
this._disabled = coerceBooleanProperty(disabled);

if (this._foundation) {
this._foundation.setDisabled(this._disabled);
}
}
private _disabled = false;

/** An event will be dispatched each time the slide-toggle changes its value. */
@Output() readonly change: EventEmitter<MatSlideToggleChange> =
new EventEmitter<MatSlideToggleChange>();

/** Event will be dispatched each time the slide-toggle input is toggled. */
@Output() readonly toggleChange: EventEmitter<void> = new EventEmitter<void>();
_labelId: string;

/** Returns the unique id for the visual hidden button. */
get buttonId(): string {
Expand All @@ -203,51 +79,29 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
@ViewChild('switch') _switchElement: ElementRef<HTMLElement>;

constructor(
private _elementRef: ElementRef,
private _focusMonitor: FocusMonitor,
private _changeDetectorRef: ChangeDetectorRef,
elementRef: ElementRef,
focusMonitor: FocusMonitor,
changeDetectorRef: ChangeDetectorRef,
@Attribute('tabindex') tabIndex: string,
@Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS)
public defaults: MatSlideToggleDefaultOptions,
defaults: MatSlideToggleDefaultOptions,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
) {
this.tabIndex = parseInt(tabIndex) || 0;
this.color = defaults.color || 'accent';
this._noopAnimations = animationMode === 'NoopAnimations';
}

ngAfterViewInit() {
const foundation = (this._foundation = new deprecated.MDCSwitchFoundation(this._adapter));
foundation.setDisabled(this.disabled);
foundation.setChecked(this.checked);

this._focusMonitor.monitor(this._elementRef, true).subscribe(focusOrigin => {
if (focusOrigin === 'keyboard' || focusOrigin === 'program') {
this._focused = true;
} else if (!focusOrigin) {
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
// Angular does not expect events to be raised during change detection, so any state
// change (such as a form control's ng-touched) will cause a changed-after-checked error.
// See https://github.com/angular/angular/issues/17793. To work around this, we defer
// telling the form control it has been touched until the next tick.
Promise.resolve().then(() => {
this._focused = false;
this._onTouched();
this._changeDetectorRef.markForCheck();
});
}
});
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
this._foundation?.destroy();
super(
elementRef,
focusMonitor,
changeDetectorRef,
tabIndex,
defaults,
animationMode,
'mat-mdc-slide-toggle-',
);
this._labelId = this._uniqueId + '-label';
}

/** Method being called whenever the underlying button is clicked. */
_handleClick(event: Event) {
_handleClick() {
this.toggleChange.emit();
this._foundation.handleChange(event);

if (!this.defaults.disableToggleValue) {
this.checked = !this.checked;
Expand All @@ -256,37 +110,13 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
}
}

/** Implemented as part of ControlValueAccessor. */
writeValue(value: any): void {
this.checked = !!value;
this._changeDetectorRef.markForCheck();
}

/** Implemented as part of ControlValueAccessor. */
registerOnChange(fn: any): void {
this._onChange = fn;
}

/** Implemented as part of ControlValueAccessor. */
registerOnTouched(fn: any): void {
this._onTouched = fn;
}

/** Implemented as a part of ControlValueAccessor. */
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this._changeDetectorRef.markForCheck();
}

/** Focuses the slide-toggle. */
focus(): void {
this._switchElement.nativeElement.focus();
}

/** Toggles the checked state of the slide-toggle. */
toggle(): void {
this.checked = !this.checked;
this._onChange(this.checked);
protected _createChangeEvent(isChecked: boolean) {
return new MatSlideToggleChange(this, isChecked);
}

_getAriaLabelledBy() {
Expand Down
4 changes: 2 additions & 2 deletions src/material/slide-toggle/slide-toggle.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<label [attr.for]="inputId" class="mat-slide-toggle-label" #label>
<span #toggleBar class="mat-slide-toggle-bar"
<span class="mat-slide-toggle-bar"
[class.mat-slide-toggle-bar-no-side-margin]="!labelContent.textContent || !labelContent.textContent.trim()">

<input #input class="mat-slide-toggle-input cdk-visually-hidden" type="checkbox"
Expand All @@ -17,7 +17,7 @@
(change)="_onChangeEvent($event)"
(click)="_onInputClick($event)">

<span class="mat-slide-toggle-thumb-container" #thumbContainer>
<span class="mat-slide-toggle-thumb-container">
<span class="mat-slide-toggle-thumb"></span>
<span class="mat-slide-toggle-ripple mat-focus-indicator" mat-ripple
[matRippleTrigger]="label"
Expand Down
Loading