diff --git a/switch/lib/_switch-handle-theme.scss b/switch/lib/_switch-handle-theme.scss index cc20d050d2..98876f79dc 100644 --- a/switch/lib/_switch-handle-theme.scss +++ b/switch/lib/_switch-handle-theme.scss @@ -8,6 +8,8 @@ @use 'sass:map'; +@use '@material/web/ripple/ripple-theme'; + $_selected: '.md3-switch--selected'; $_unselected: '.md3-switch--unselected'; @@ -23,6 +25,10 @@ $_unselected: '.md3-switch--unselected'; @include _handle-unselected($theme); } } + + .md3-switch__ripple { + @include _ripple($theme); + } } @mixin _handle-container($theme) { @@ -135,3 +141,53 @@ $_unselected: '.md3-switch--unselected'; background-color: map.get($colors, disabled); } } + +@mixin _ripple($theme) { + @include _state-layer-size(map.get($theme, state-layer-size)); + @include _state-layer-color($theme); +} + +@mixin _state-layer-size($size) { + height: $size; + width: $size; +} + +@mixin _state-layer-color($theme) { + #{$_selected} & { + @include ripple-theme.theme( + ( + hover-state-layer-color: + map.get($theme, selected-hover-state-layer-color), + focus-state-layer-color: + map.get($theme, selected-focus-state-layer-color), + pressed-state-layer-color: + map.get($theme, selected-pressed-state-layer-color), + hover-state-layer-opacity: + map.get($theme, selected-hover-state-layer-opacity), + focus-state-layer-opacity: + map.get($theme, selected-focus-state-layer-opacity), + pressed-state-layer-opacity: + map.get($theme, selected-pressed-state-layer-opacity), + ) + ); + } + + #{$_unselected} & { + @include ripple-theme.theme( + ( + hover-state-layer-color: + map.get($theme, unselected-hover-state-layer-color), + focus-state-layer-color: + map.get($theme, unselected-focus-state-layer-color), + pressed-state-layer-color: + map.get($theme, unselected-pressed-state-layer-color), + hover-state-layer-opacity: + map.get($theme, unselected-hover-state-layer-opacity), + focus-state-layer-opacity: + map.get($theme, unselected-focus-state-layer-opacity), + pressed-state-layer-opacity: + map.get($theme, unselected-pressed-state-layer-opacity), + ) + ); + } +} diff --git a/switch/lib/_switch.scss b/switch/lib/_switch.scss index 6a56c5bf5e..ad92b86af3 100644 --- a/switch/lib/_switch.scss +++ b/switch/lib/_switch.scss @@ -60,6 +60,15 @@ $icon-enter-duration: $animation-duration - $icon-exit-duration; } } + // Ripple + .md3-switch__ripple { + position: absolute; + display: inline-flex; + left: 50%; + top: 50%; + transform: translate(-50%,-50%); + } + // Icons .md3-switch__icons { @include _icons; diff --git a/switch/lib/switch.ts b/switch/lib/switch.ts index 89ac9d2586..70e1a99ad9 100644 --- a/switch/lib/switch.ts +++ b/switch/lib/switch.ts @@ -5,14 +5,16 @@ */ import '@material/web/focus/focus-ring.js'; +import '@material/web/ripple/ripple.js'; -import {ActionElement, EndPressConfig} from '@material/web/actionelement/action-element.js'; +import {ActionElement, EndPressConfig, BeginPressConfig} from '@material/web/actionelement/action-element.js'; import {ariaProperty as legacyAriaProperty} from '@material/web/compat/base/aria-property.js'; // TODO(b/236840525): remove compat dependencies import {FormController, getFormValue} from '@material/web/controller/form-controller.js'; import {ariaProperty} from '@material/web/decorators/aria-property.js'; import {pointerPress as focusRingPointerPress, shouldShowStrongFocus} from '@material/web/focus/strong-focus.js'; +import {MdRipple} from '@material/web/ripple/ripple.js'; import {html, TemplateResult} from 'lit'; -import {eventOptions, property, state} from 'lit/decorators.js'; +import {eventOptions, property, query, state} from 'lit/decorators.js'; import {ClassInfo, classMap} from 'lit/directives/class-map.js'; import {ifDefined} from 'lit/directives/if-defined.js'; @@ -41,6 +43,9 @@ export class Switch extends ActionElement { @state() protected showFocusRing = false; + // Ripple + @query('md-ripple') readonly ripple!: MdRipple; + // FormController get form() { return this.closest('form'); @@ -80,6 +85,7 @@ export class Switch extends ActionElement { @focus="${this.handleFocus}" @blur="${this.handleBlur}" @pointerdown=${this.handlePointerDown} + @pointerenter=${this.handlePointerEnter} @pointerup=${this.handlePointerUp} @pointercancel=${this.handlePointerCancel} @pointerleave=${this.handlePointerLeave} @@ -111,6 +117,18 @@ export class Switch extends ActionElement { }; } + /** @soyTemplate */ + protected renderRipple(): TemplateResult { + return html` +
+ + +
+ `; + } + /** @soyTemplate */ protected renderFocusRing(): TemplateResult { return html` + ${this.renderRipple()}
${this.shouldShowIcons() ? this.renderIcons() : html``}
@@ -179,7 +198,13 @@ export class Switch extends ActionElement { return this.icons || this.showOnlySelectedIcon; } + override beginPress({positionEvent}: BeginPressConfig) { + this.ripple.beginPress(positionEvent); + } + override endPress({cancelled}: EndPressConfig) { + this.ripple.endPress(); + if (cancelled || this.disabled) { return; } @@ -196,6 +221,15 @@ export class Switch extends ActionElement { this.showFocusRing = false; } + protected handlePointerEnter(e: PointerEvent) { + this.ripple.beginHover(e); + } + + override handlePointerLeave(e: PointerEvent) { + super.handlePointerLeave(e); + this.ripple.endHover(); + } + @eventOptions({passive: true}) override handlePointerDown(event: PointerEvent) { super.handlePointerDown(event);