mirror of
https://github.com/material-components/material-web.git
synced 2026-03-09 00:09:23 +08:00
TypeScript’s override keyword (added in 4.3) works similarly to @Override in Java. It makes the intention clear and ensures there is actually a member in the base class with the same name. This helps with things like:
- Typos in the overriding member name
- Remember to rename members in sub classes when renaming an overridden member in a base class
class Parent {
foo() {}
}
class Child extends Parent {
override bar() {}
// ~~~ This member cannot have an 'override' modifier because it is not declared in the base class 'Parent'.
}
This change will not cause a runtime change: the override keyword is not present in the resulting JavaScript.
PiperOrigin-RevId: 399452347
206 lines
5.6 KiB
TypeScript
206 lines
5.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import '@material/mwc-ripple/mwc-ripple.js';
|
|
|
|
import {ariaProperty} from '@material/mwc-base/aria-property.js';
|
|
import {FormElement} from '@material/mwc-base/form-element.js';
|
|
import {Ripple} from '@material/mwc-ripple/mwc-ripple.js';
|
|
import {RippleHandlers} from '@material/mwc-ripple/ripple-handlers.js';
|
|
import {html, TemplateResult} from 'lit';
|
|
import {eventOptions, property, query, queryAsync, state} from 'lit/decorators.js';
|
|
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
|
import {ifDefined} from 'lit/directives/if-defined.js';
|
|
|
|
import {MDCSwitchFoundation} from './foundation.js';
|
|
import {MDCSwitchAdapter, MDCSwitchState} from './state.js';
|
|
|
|
/** @soyCompatible */
|
|
export class Switch extends FormElement implements MDCSwitchState {
|
|
// MDCSwitchState
|
|
@property({type: Boolean}) override disabled = false;
|
|
@property({type: Boolean}) processing = false;
|
|
@property({type: Boolean}) selected = false;
|
|
|
|
// Aria
|
|
/** @soyPrefixAttribute */
|
|
@ariaProperty
|
|
@property({type: String, attribute: 'aria-label'})
|
|
ariaLabel = '';
|
|
|
|
/** @soyPrefixAttribute */
|
|
@ariaProperty
|
|
@property({type: String, attribute: 'aria-labelledby'})
|
|
ariaLabelledBy = '';
|
|
|
|
// Ripple
|
|
@queryAsync('mwc-ripple') override readonly ripple!: Promise<Ripple|null>;
|
|
@state() protected shouldRenderRipple = false;
|
|
|
|
protected rippleHandlers = new RippleHandlers(() => {
|
|
this.shouldRenderRipple = true;
|
|
return this.ripple;
|
|
});
|
|
|
|
// FormElement
|
|
@property({type: String, reflect: true}) name = '';
|
|
@property({type: String}) value = 'on';
|
|
@query('input') protected readonly formElement!: HTMLElement;
|
|
|
|
protected setFormData(formData: FormData) {
|
|
if (this.name && this.selected) {
|
|
formData.append(this.name, this.value);
|
|
}
|
|
}
|
|
|
|
// BaseElement
|
|
@query('.mdc-switch') protected readonly mdcRoot!: HTMLElement;
|
|
protected readonly mdcFoundationClass = MDCSwitchFoundation;
|
|
protected mdcFoundation?: MDCSwitchFoundation;
|
|
|
|
override click() {
|
|
// Switch uses a hidden input as its form element, but a different <button>
|
|
// for interaction. It overrides click() from FormElement to avoid clicking
|
|
// the hidden input.
|
|
if (!this.disabled) {
|
|
this.mdcRoot?.focus();
|
|
this.mdcRoot?.click();
|
|
}
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected override render(): TemplateResult {
|
|
return html`
|
|
<button
|
|
type="button"
|
|
class="mdc-switch ${classMap(this.getRenderClasses())}"
|
|
role="switch"
|
|
aria-checked="${this.selected}"
|
|
aria-label="${ifDefined(this.ariaLabel || undefined)}"
|
|
aria-labelledby="${ifDefined(this.ariaLabelledBy || undefined)}"
|
|
.disabled=${this.disabled}
|
|
@click=${this.handleClick}
|
|
@focus="${this.handleFocus}"
|
|
@blur="${this.handleBlur}"
|
|
@pointerdown="${this.handlePointerDown}"
|
|
@pointerup="${this.handlePointerUp}"
|
|
@pointerenter="${this.handlePointerEnter}"
|
|
@pointerleave="${this.handlePointerLeave}"
|
|
>
|
|
<div class="mdc-switch__track"></div>
|
|
<div class="mdc-switch__handle-track">
|
|
${this.renderHandle()}
|
|
</div>
|
|
</button>
|
|
|
|
<input
|
|
type="checkbox"
|
|
aria-hidden="true"
|
|
name="${this.name}"
|
|
.checked=${this.selected}
|
|
.value=${this.value}
|
|
>
|
|
`;
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected getRenderClasses(): ClassInfo {
|
|
return {
|
|
'mdc-switch--processing': this.processing,
|
|
'mdc-switch--selected': this.selected,
|
|
'mdc-switch--unselected': !this.selected,
|
|
};
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected renderHandle(): TemplateResult {
|
|
return html`
|
|
<div class="mdc-switch__handle">
|
|
${this.renderShadow()}
|
|
${this.renderRipple()}
|
|
<div class="mdc-switch__icons">
|
|
${this.renderOnIcon()}
|
|
${this.renderOffIcon()}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected renderShadow(): TemplateResult {
|
|
return html`
|
|
<div class="mdc-switch__shadow">
|
|
<div class="mdc-elevation-overlay"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected renderRipple(): TemplateResult {
|
|
return !this.shouldRenderRipple ? html`` : html`
|
|
<div class="mdc-switch__ripple">
|
|
<mwc-ripple
|
|
internalUseStateLayerCustomProperties
|
|
.disabled="${this.disabled}"
|
|
unbounded>
|
|
</mwc-ripple>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected renderOnIcon(): TemplateResult {
|
|
return html`
|
|
<svg class="mdc-switch__icon mdc-switch__icon--on" viewBox="0 0 24 24">
|
|
<path d="M19.69,5.23L8.96,15.96l-4.23-4.23L2.96,13.5l6,6L21.46,7L19.69,5.23z" />
|
|
</svg>
|
|
`;
|
|
}
|
|
|
|
/** @soyTemplate */
|
|
protected renderOffIcon(): TemplateResult {
|
|
return html`
|
|
<svg class="mdc-switch__icon mdc-switch__icon--off" viewBox="0 0 24 24">
|
|
<path d="M20 13H4v-2h16v2z" />
|
|
</svg>
|
|
`;
|
|
}
|
|
|
|
protected handleClick() {
|
|
this.mdcFoundation?.handleClick();
|
|
}
|
|
|
|
protected handleFocus() {
|
|
this.rippleHandlers.startFocus();
|
|
}
|
|
|
|
protected handleBlur() {
|
|
this.rippleHandlers.endFocus();
|
|
}
|
|
|
|
@eventOptions({passive: true})
|
|
protected handlePointerDown(event: PointerEvent) {
|
|
(event.target as HTMLElement).setPointerCapture(event.pointerId);
|
|
this.rippleHandlers.startPress(event);
|
|
}
|
|
|
|
protected handlePointerUp() {
|
|
this.rippleHandlers.endPress();
|
|
}
|
|
|
|
protected handlePointerEnter() {
|
|
this.rippleHandlers.startHover();
|
|
}
|
|
|
|
protected handlePointerLeave() {
|
|
this.rippleHandlers.endHover();
|
|
}
|
|
|
|
protected createAdapter(): MDCSwitchAdapter {
|
|
return {state: this};
|
|
}
|
|
}
|