Skip to content

Commit 0cae296

Browse files
authored
fix(tabs, ripple): Correct scale factor calculations (#2008)
1 parent 2f670dd commit 0cae296

File tree

4 files changed

+59
-30
lines changed

4 files changed

+59
-30
lines changed

src/components/common/util.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export function partition<T>(
346346
}
347347

348348
/** Returns the center x/y coordinate of a given element. */
349-
export function getCenterPoint(element: Element) {
349+
export function getCenterPoint(element: Element): { x: number; y: number } {
350350
const { left, top, width, height } = element.getBoundingClientRect();
351351

352352
return {
@@ -355,6 +355,13 @@ export function getCenterPoint(element: Element) {
355355
};
356356
}
357357

358+
/** Returns the scale factor of a given element based on its bounding client rect and offset dimensions. */
359+
export function getScaleFactor(element: HTMLElement): { x: number; y: number } {
360+
const { offsetWidth, offsetHeight } = element;
361+
const { width, height } = element.getBoundingClientRect();
362+
return { x: offsetWidth / width || 1, y: offsetHeight / height || 1 };
363+
}
364+
358365
export function roundByDPR(value: number): number {
359366
const dpr = globalThis.devicePixelRatio || 1;
360367
return Math.round(value * dpr) / dpr;

src/components/ripple/ripple.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { expect, fixture, html } from '@open-wc/testing';
2-
1+
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
32
import IgcButtonComponent from '../button/button.js';
43
import { defineComponents } from '../common/definitions/defineComponents.js';
54
import { simulatePointerDown } from '../common/utils.spec.js';
@@ -39,4 +38,16 @@ describe('Ripple', () => {
3938

4039
simulatePointerDown(ripple);
4140
});
41+
42+
it('No ripple on non-primary pointer button', async () => {
43+
ripple.addEventListener(
44+
'animationstart',
45+
() =>
46+
expect.fail('Ripple animation should not start on non-primary button'),
47+
{ once: true }
48+
);
49+
50+
simulatePointerDown(ripple, { button: 1 });
51+
await elementUpdated(ripple);
52+
});
4253
});

src/components/ripple/ripple.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { html, LitElement } from 'lit';
2-
1+
import { LitElement, nothing } from 'lit';
32
import { registerComponent } from '../common/definitions/register.js';
4-
import { addSafeEventListener } from '../common/util.js';
3+
import {
4+
addSafeEventListener,
5+
getScaleFactor,
6+
setStyles,
7+
} from '../common/util.js';
58
import { styles } from './ripple.material.css.js';
69

710
const rippleFrames: Keyframe[] = [
@@ -17,7 +20,7 @@ const rippleAnimation: KeyframeAnimationOptions = {
1720

1821
let rippleElement: HTMLElement;
1922

20-
function getRippleElement() {
23+
function getRippleElement(): HTMLSpanElement {
2124
if (!rippleElement) {
2225
rippleElement = document.createElement('span');
2326
}
@@ -41,12 +44,19 @@ export default class IgcRippleComponent extends LitElement {
4144

4245
constructor() {
4346
super();
44-
addSafeEventListener(this, 'pointerdown', this.handler);
47+
addSafeEventListener(this, 'pointerdown', this._handler);
4548
}
4649

47-
private handler = ({ clientX, clientY }: PointerEvent) => {
50+
private async _handler(event: PointerEvent): Promise<void> {
51+
if (event.button !== 0) {
52+
return;
53+
}
54+
4855
const element = getRippleElement();
49-
const { radius, top, left } = this.getDimensions(clientX, clientY);
56+
const { radius, top, left } = this._getDimensions(
57+
event.clientX,
58+
event.clientY
59+
);
5060

5161
const styles: Partial<CSSStyleDeclaration> = {
5262
position: 'absolute',
@@ -55,8 +65,8 @@ export default class IgcRippleComponent extends LitElement {
5565
transformOrigin: 'center',
5666
transform: 'translate3d(0, 0, 0) scale(0)',
5767
willChange: 'opacity, transform',
58-
margin: '0 !important',
59-
border: 'none !important',
68+
margin: '0',
69+
border: 'none',
6070
width: `${radius}px`,
6171
height: `${radius}px`,
6272
borderRadius: '50%',
@@ -65,28 +75,28 @@ export default class IgcRippleComponent extends LitElement {
6575
background: 'var(--color, var(--ig-gray-800))',
6676
};
6777

68-
Object.assign(element.style, styles);
78+
setStyles(element, styles);
6979
this.renderRoot.appendChild(element);
7080

71-
element
72-
.animate(rippleFrames, rippleAnimation)
73-
.finished.then(() => element.remove());
74-
};
81+
await element.animate(rippleFrames, rippleAnimation).finished;
82+
element.remove();
83+
}
7584

76-
private getDimensions(x: number, y: number) {
85+
private _getDimensions(x: number, y: number) {
7786
const rect = this.getBoundingClientRect();
87+
const factor = getScaleFactor(this);
7888
const radius = Math.max(rect.width, rect.height);
7989
const halfRadius = radius / 2;
8090

8191
return {
8292
radius,
83-
top: Math.round(y - rect.top - halfRadius),
84-
left: Math.round(x - rect.left - halfRadius),
93+
top: Math.round((y - rect.top) * factor.y - halfRadius),
94+
left: Math.round((x - rect.left) * factor.x - halfRadius),
8595
};
8696
}
8797

8898
protected override render() {
89-
return html``;
99+
return nothing;
90100
}
91101
}
92102

src/components/tabs/tab-dom.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Ref } from 'lit/directives/ref.js';
2-
import { isLTR } from '../common/util.js';
2+
import { getScaleFactor, isLTR, setStyles } from '../common/util.js';
33
import type IgcTabComponent from './tab.js';
44
import type IgcTabsComponent from './tabs.js';
55

@@ -151,22 +151,23 @@ class TabsHelpers {
151151
await this._host.updateComplete;
152152

153153
if (active) {
154-
const tabHeader = getTabHeader(active);
155-
const { width } = tabHeader.getBoundingClientRect();
154+
const header = getTabHeader(active);
155+
const { offsetLeft: containerLeft, offsetWidth: containerWidth } =
156+
this.container;
157+
const scaledWidth =
158+
header.getBoundingClientRect().width * getScaleFactor(header).x;
156159

157160
const offset = this._isLeftToRight
158-
? tabHeader.offsetLeft - this.container.offsetLeft
159-
: width +
160-
tabHeader.offsetLeft -
161-
this.container.getBoundingClientRect().width;
161+
? header.offsetLeft - containerLeft
162+
: header.offsetLeft + scaledWidth - containerWidth;
162163

163164
Object.assign(styles, {
164-
width: `${width}px`,
165+
width: `${scaledWidth}px`,
165166
transform: `translateX(${offset}px)`,
166167
});
167168
}
168169

169-
Object.assign(this.indicator.style, styles);
170+
setStyles(this.indicator, styles);
170171
}
171172
}
172173

0 commit comments

Comments
 (0)