From b2e2ee0253265c6a3d7a239a2d2143be304492db Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Tue, 14 May 2024 13:17:15 +0300 Subject: [PATCH] refactor(ripple): Cleaned code and added tests (#1197) --- src/components/ripple/ripple.spec.ts | 42 ++++++++++++++++++++ src/components/ripple/ripple.ts | 58 ++++++++++++++++++---------- 2 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 src/components/ripple/ripple.spec.ts diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts new file mode 100644 index 000000000..5a2a69a2c --- /dev/null +++ b/src/components/ripple/ripple.spec.ts @@ -0,0 +1,42 @@ +import { expect, fixture, html } from '@open-wc/testing'; + +import IgcButtonComponent from '../button/button.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { simulatePointerDown } from '../common/utils.spec'; +import IgcRippleComponent from './ripple.js'; + +describe('Ripple', () => { + let ripple: IgcRippleComponent; + let button: IgcButtonComponent; + + before(() => { + defineComponents(IgcRippleComponent, IgcButtonComponent); + }); + + beforeEach(async () => { + button = await fixture( + html`Click me ` + ); + + ripple = button.querySelector(IgcRippleComponent.tagName)!; + }); + + it('DOM state before and after ripple animation', async () => { + ripple.addEventListener( + 'animationstart', + () => + expect(ripple).shadowDom.to.equal('', { + ignoreAttributes: ['style'], + }), + { once: true } + ); + + ripple.addEventListener( + 'animationend', + () => expect(ripple).shadowDom.to.equal(''), + { once: true } + ); + + simulatePointerDown(ripple); + }); +}); diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts index d2393fe1d..6ddab9f02 100644 --- a/src/components/ripple/ripple.ts +++ b/src/components/ripple/ripple.ts @@ -3,6 +3,26 @@ import { LitElement, html } from 'lit'; import { registerComponent } from '../common/definitions/register.js'; import { styles } from './ripple.material.css.js'; +const rippleFrames: Keyframe[] = [ + { opacity: 0.5, transform: 'scale(.3)' }, + { opacity: 0, transform: 'scale(2)' }, +]; + +const rippleAnimation: KeyframeAnimationOptions = { + duration: 600, // --igc-ripple-duration, + fill: 'forwards', + easing: 'linear', // --igc-ripple-easing +}; + +let rippleElement: HTMLElement; + +function getRippleElement() { + if (!rippleElement) { + rippleElement = document.createElement('span'); + } + return rippleElement.cloneNode() as HTMLElement; +} + /** * A ripple can be applied to an element to represent * interactive surface. @@ -20,11 +40,11 @@ export default class IgcRippleComponent extends LitElement { constructor() { super(); - this.addEventListener('mousedown', this.handler); + this.addEventListener('pointerdown', this.handler); } - private handler = ({ clientX, clientY }: MouseEvent) => { - const element = document.createElement('span'); + private handler = ({ clientX, clientY }: PointerEvent) => { + const element = getRippleElement(); const { radius, top, left } = this.getDimensions(clientX, clientY); const styles: Partial = { @@ -43,31 +63,29 @@ export default class IgcRippleComponent extends LitElement { left: `${left}px`, background: 'var(--color, hsl(var(--ig-gray-800)))', }; - const frames: Keyframe[] = [ - { opacity: 0.5, transform: 'scale(.3)' }, - { opacity: 0, transform: 'scale(2)' }, - ]; - const opts: KeyframeAnimationOptions = { - duration: 600, // --igc-ripple-duration, - fill: 'forwards', - easing: 'linear', // --igc-ripple-easing - }; Object.assign(element.style, styles); - this.shadowRoot?.appendChild(element); - element.animate(frames, opts).finished.then(() => element.remove()); + this.renderRoot.appendChild(element); + + element + .animate(rippleFrames, rippleAnimation) + .finished.then(() => element.remove()); }; private getDimensions(x: number, y: number) { - const { width, height, left, top } = this.getBoundingClientRect(); - const radius = Math.max(width, height); - const _left = Math.round(x - left - radius / 2); - const _top = Math.round(y - top - radius / 2); - return { radius, top: _top, left: _left }; + const rect = this.getBoundingClientRect(); + const radius = Math.max(rect.width, rect.height); + const halfRadius = radius / 2; + + return { + radius, + top: Math.round(y - rect.top - halfRadius), + left: Math.round(x - rect.left - halfRadius), + }; } protected override render() { - return html`
`; + return html``; } }