2021-09-24 21:31:43 +03:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright 2021 Google LLC
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
2022-03-29 23:50:10 +03:00
|
|
|
import '../../focus/focus-ring';
|
2021-10-13 21:04:42 +03:00
|
|
|
import '@material/mwc-ripple/mwc-ripple';
|
2021-09-24 21:31:43 +03:00
|
|
|
|
2021-12-17 00:36:39 +03:00
|
|
|
import {ariaProperty as legacyAriaProperty} from '@material/mwc-base/aria-property';
|
2021-10-13 21:04:42 +03:00
|
|
|
import {Ripple} from '@material/mwc-ripple/mwc-ripple';
|
|
|
|
import {RippleHandlers} from '@material/mwc-ripple/ripple-handlers';
|
2021-12-29 20:45:59 +03:00
|
|
|
import {html, LitElement, TemplateResult} from 'lit';
|
|
|
|
import {eventOptions, property, queryAsync, state} from 'lit/decorators';
|
2021-10-13 21:04:42 +03:00
|
|
|
import {ClassInfo, classMap} from 'lit/directives/class-map';
|
|
|
|
import {ifDefined} from 'lit/directives/if-defined';
|
2021-09-24 21:31:43 +03:00
|
|
|
|
2021-12-29 20:45:59 +03:00
|
|
|
import {FormController, getFormValue} from '../../controller/form-controller';
|
2021-12-17 00:36:39 +03:00
|
|
|
import {ariaProperty} from '../../decorators/aria-property';
|
2022-03-29 23:50:10 +03:00
|
|
|
import {shouldShowStrongFocus} from '../../focus/strong-focus';
|
2021-12-17 00:36:39 +03:00
|
|
|
|
2021-09-28 02:27:12 +03:00
|
|
|
/** @soyCompatible */
|
2022-03-23 04:18:05 +03:00
|
|
|
export class Switch extends LitElement {
|
2021-12-29 20:45:59 +03:00
|
|
|
static override shadowRootOptions:
|
|
|
|
ShadowRootInit = {mode: 'open', delegatesFocus: true};
|
|
|
|
|
|
|
|
@property({type: Boolean, reflect: true}) disabled = false;
|
2021-09-24 21:31:43 +03:00
|
|
|
@property({type: Boolean}) processing = false;
|
|
|
|
@property({type: Boolean}) selected = false;
|
|
|
|
|
|
|
|
// Aria
|
|
|
|
@ariaProperty
|
2021-12-17 00:36:39 +03:00
|
|
|
// TODO(b/210730484): replace with @soyParam annotation
|
|
|
|
@property({type: String, attribute: 'data-aria-label', noAccessor: true})
|
|
|
|
override ariaLabel!: string;
|
2021-09-24 21:31:43 +03:00
|
|
|
|
2021-12-17 00:36:39 +03:00
|
|
|
// TODO: Add support in @ariaProperty for idref aria attributes
|
2021-09-24 21:31:43 +03:00
|
|
|
/** @soyPrefixAttribute */
|
2021-12-17 00:36:39 +03:00
|
|
|
@legacyAriaProperty
|
2021-09-24 21:31:43 +03:00
|
|
|
@property({type: String, attribute: 'aria-labelledby'})
|
|
|
|
ariaLabelledBy = '';
|
|
|
|
|
2022-03-29 23:50:10 +03:00
|
|
|
@state() protected showFocusRing = false;
|
|
|
|
|
2021-09-24 21:31:43 +03:00
|
|
|
// Ripple
|
2021-12-29 20:45:59 +03:00
|
|
|
@queryAsync('mwc-ripple') readonly ripple!: Promise<Ripple|null>;
|
2021-09-24 21:31:43 +03:00
|
|
|
@state() protected shouldRenderRipple = false;
|
|
|
|
|
|
|
|
protected rippleHandlers = new RippleHandlers(() => {
|
|
|
|
this.shouldRenderRipple = true;
|
|
|
|
return this.ripple;
|
|
|
|
});
|
|
|
|
|
2021-12-29 20:45:59 +03:00
|
|
|
// FormController
|
|
|
|
get form() {
|
|
|
|
return this.closest('form');
|
|
|
|
}
|
2021-09-24 21:31:43 +03:00
|
|
|
@property({type: String, reflect: true}) name = '';
|
|
|
|
@property({type: String}) value = 'on';
|
2021-12-29 20:45:59 +03:00
|
|
|
[getFormValue]() {
|
|
|
|
return this.selected ? this.value : null;
|
2021-09-24 21:31:43 +03:00
|
|
|
}
|
|
|
|
|
2021-12-29 20:45:59 +03:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.addController(new FormController(this));
|
|
|
|
}
|
2021-09-24 21:31:43 +03:00
|
|
|
|
|
|
|
override click() {
|
2022-03-23 04:18:05 +03:00
|
|
|
this.handleClick();
|
2021-12-29 20:45:59 +03:00
|
|
|
super.click();
|
2021-09-24 21:31:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @soyTemplate */
|
|
|
|
protected override render(): TemplateResult {
|
|
|
|
return html`
|
|
|
|
<button
|
|
|
|
type="button"
|
2022-03-23 23:50:50 +03:00
|
|
|
class="md3-switch ${classMap(this.getRenderClasses())}"
|
2021-09-24 21:31:43 +03:00
|
|
|
role="switch"
|
|
|
|
aria-checked="${this.selected}"
|
2021-12-17 00:36:39 +03:00
|
|
|
aria-label="${ifDefined(this.ariaLabel)}"
|
2021-09-24 21:31:43 +03:00
|
|
|
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}"
|
|
|
|
>
|
2022-03-29 23:50:10 +03:00
|
|
|
${this.renderFocusRing()}
|
2022-03-23 23:50:50 +03:00
|
|
|
<div class="md3-switch__track"></div>
|
|
|
|
<div class="md3-switch__handle-track">
|
2021-09-24 21:31:43 +03:00
|
|
|
${this.renderHandle()}
|
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<input
|
|
|
|
type="checkbox"
|
|
|
|
aria-hidden="true"
|
|
|
|
name="${this.name}"
|
|
|
|
.checked=${this.selected}
|
|
|
|
.value=${this.value}
|
|
|
|
>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @soyTemplate */
|
|
|
|
protected getRenderClasses(): ClassInfo {
|
|
|
|
return {
|
2022-03-23 23:50:50 +03:00
|
|
|
'md3-switch--processing': this.processing,
|
|
|
|
'md3-switch--selected': this.selected,
|
|
|
|
'md3-switch--unselected': !this.selected,
|
2021-09-24 21:31:43 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-29 23:50:10 +03:00
|
|
|
/** @soyTemplate */
|
|
|
|
protected renderFocusRing(): TemplateResult {
|
|
|
|
return html`<md-focus-ring .visible="${
|
|
|
|
this.showFocusRing}"></md-focus-ring>`;
|
|
|
|
}
|
|
|
|
|
2021-09-24 21:31:43 +03:00
|
|
|
/** @soyTemplate */
|
|
|
|
protected renderHandle(): TemplateResult {
|
|
|
|
return html`
|
2022-03-23 23:50:50 +03:00
|
|
|
<div class="md3-switch__handle">
|
2021-09-24 21:31:43 +03:00
|
|
|
${this.renderShadow()}
|
|
|
|
${this.renderRipple()}
|
2022-03-23 23:50:50 +03:00
|
|
|
<div class="md3-switch__icons">
|
2021-09-24 21:31:43 +03:00
|
|
|
${this.renderOnIcon()}
|
|
|
|
${this.renderOffIcon()}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @soyTemplate */
|
|
|
|
protected renderShadow(): TemplateResult {
|
|
|
|
return html`
|
2022-03-23 23:50:50 +03:00
|
|
|
<div class="md3-switch__shadow">
|
|
|
|
<div class="md3-elevation-overlay"></div>
|
2021-09-24 21:31:43 +03:00
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @soyTemplate */
|
|
|
|
protected renderRipple(): TemplateResult {
|
2021-09-28 02:27:12 +03:00
|
|
|
return !this.shouldRenderRipple ? html`` : html`
|
2022-03-23 23:50:50 +03:00
|
|
|
<div class="md3-switch__ripple">
|
2021-09-24 21:31:43 +03:00
|
|
|
<mwc-ripple
|
|
|
|
internalUseStateLayerCustomProperties
|
|
|
|
.disabled="${this.disabled}"
|
|
|
|
unbounded>
|
|
|
|
</mwc-ripple>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @soyTemplate */
|
|
|
|
protected renderOnIcon(): TemplateResult {
|
|
|
|
return html`
|
2022-03-23 23:50:50 +03:00
|
|
|
<svg class="md3-switch__icon md3-switch__icon--on" viewBox="0 0 24 24">
|
2021-09-24 21:31:43 +03:00
|
|
|
<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`
|
2022-03-23 23:50:50 +03:00
|
|
|
<svg class="md3-switch__icon md3-switch__icon--off" viewBox="0 0 24 24">
|
2021-09-24 21:31:43 +03:00
|
|
|
<path d="M20 13H4v-2h16v2z" />
|
|
|
|
</svg>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected handleClick() {
|
2022-03-23 04:18:05 +03:00
|
|
|
if (this.disabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.selected = !this.selected;
|
2021-09-24 21:31:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
protected handleFocus() {
|
2022-03-29 23:50:10 +03:00
|
|
|
this.showFocusRing = shouldShowStrongFocus();
|
2021-09-24 21:31:43 +03:00
|
|
|
this.rippleHandlers.startFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected handleBlur() {
|
2022-03-29 23:50:10 +03:00
|
|
|
this.showFocusRing = false;
|
2021-09-24 21:31:43 +03:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|