diff --git a/ripple/lib/_ripple.scss b/ripple/lib/_ripple.scss index 3a32a119bf..11b6c56e5c 100644 --- a/ripple/lib/_ripple.scss +++ b/ripple/lib/_ripple.scss @@ -3,9 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // -// stylelint-disable selector-class-pattern -- -// Selector '.md3-*' should only be used in this project. - @use 'sass:map'; @use '../../sass/theme'; @use '../../tokens'; @@ -37,14 +34,14 @@ } :host, - .md3-ripple-surface { + .surface { position: absolute; inset: 0; pointer-events: none; overflow: hidden; } - .md3-ripple-surface { + .surface { // TODO(https://bugs.webkit.org/show_bug.cgi?id=247546) // Remove Safari workaround for incorrect ripple overflow when addressed. // This addresses `hover` and `pressed` state oveflow. @@ -79,24 +76,24 @@ } } - .md3-ripple--hovered::before { + .hovered::before { background-color: var(--_hover-state-layer-color); opacity: var(--_hover-state-layer-opacity); } - .md3-ripple--focused::before { + .focused::before { background-color: var(--_focus-state-layer-color); opacity: var(--_focus-state-layer-opacity); transition-duration: 75ms; } - .md3-ripple--pressed::after { + .pressed::after { // press ripple fade-in opacity: var(--_pressed-state-layer-opacity); transition-duration: 105ms; } - .md3-ripple--unbounded { + .unbounded { $unbounded: ( state-layer-shape: map.get(tokens.md-sys-shape-values(), 'corner-full'), ); @@ -105,7 +102,6 @@ --_state-layer-shape: #{map.get($unbounded, 'state-layer-shape')}; } - // TODO(b/230630968): create a forced-colors-mode mixin @media screen and (forced-colors: active) { :host { display: none; diff --git a/ripple/lib/ripple.ts b/ripple/lib/ripple.ts index 4cc83a3ac7..bb8762bd84 100644 --- a/ripple/lib/ripple.ts +++ b/ripple/lib/ripple.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {html, LitElement, PropertyValues, TemplateResult} from 'lit'; +import {html, LitElement, PropertyValues} from 'lit'; import {property, query, state} from 'lit/decorators.js'; -import {ClassInfo, classMap} from 'lit/directives/class-map.js'; +import {classMap} from 'lit/directives/class-map.js'; import {createAnimationSignal, EASING} from '../../motion/animation.js'; @@ -19,41 +19,79 @@ const SOFT_EDGE_CONTAINER_RATIO = 0.35; const PRESS_PSEUDO = '::after'; const ANIMATION_FILL = 'forwards'; -/** @soyCompatible */ +/** + * A ripple component. + */ export class Ripple extends LitElement { - @query('.md3-ripple-surface') mdRoot!: HTMLElement; - // TODO(https://bugs.webkit.org/show_bug.cgi?id=247546) // Remove Safari workaround that requires reflecting `unbounded` so // it can be styled against. + /** + * Sets the ripple to be an unbounded circle. + */ @property({type: Boolean, reflect: true}) unbounded = false; + + /** + * Disables the ripple. + */ @property({type: Boolean, reflect: true}) disabled = false; - @state() protected hovered = false; - @state() protected focused = false; - @state() protected pressed = false; - - protected rippleSize = ''; - protected rippleScale = ''; - protected initialSize = 0; - protected pressAnimationSignal = createAnimationSignal(); - protected growAnimation: Animation|null = null; - protected delayedEndPressHandle: number|null = null; - - /** @soyTemplate */ - protected override render(): TemplateResult { - return html`
`; + @state() private hovered = false; + @state() private focused = false; + @state() private pressed = false; + + @query('.surface') private readonly mdRoot!: HTMLElement; + private rippleSize = ''; + private rippleScale = ''; + private initialSize = 0; + private readonly pressAnimationSignal = createAnimationSignal(); + private growAnimation: Animation|null = null; + private delayedEndPressHandle?: number; + + beginHover(hoverEvent?: Event) { + if ((hoverEvent as PointerEvent)?.pointerType !== 'touch') { + this.hovered = true; + } + } + + endHover() { + this.hovered = false; } - /** @soyTemplate */ - protected getRenderRippleClasses(): ClassInfo { - return { - 'md3-ripple--hovered': this.hovered, - 'md3-ripple--focused': this.focused, - 'md3-ripple--pressed': this.pressed, - 'md3-ripple--unbounded': this.unbounded, + beginFocus() { + this.focused = true; + } + + endFocus() { + this.focused = false; + } + + beginPress(positionEvent?: Event|null) { + this.pressed = true; + clearTimeout(this.delayedEndPressHandle); + this.startPressAnimation(positionEvent); + } + + endPress() { + const pressAnimationPlayState = this.growAnimation?.currentTime ?? Infinity; + if (pressAnimationPlayState >= MINIMUM_PRESS_MS) { + this.pressed = false; + } else { + this.delayedEndPressHandle = setTimeout(() => { + this.pressed = false; + }, MINIMUM_PRESS_MS - pressAnimationPlayState); + } + } + + protected override render() { + const classes = { + 'hovered': this.hovered, + 'focused': this.focused, + 'pressed': this.pressed, + 'unbounded': this.unbounded, }; + + return html``; } protected override update(changedProps: PropertyValues