fix(packages): prevent controls click triggering interactions (#1885)

This commit is contained in:
Sam Potts
2026-07-30 07:37:20 -07:00
committed by GitHub
parent 75dcc6675b
commit 0f4a9b4127
6 changed files with 64 additions and 1 deletions
+14
View File
@@ -53,6 +53,13 @@ test.describe('Mouse Gestures', () => {
await expect(player.playButton).not.toHaveAttribute(DATA_ATTRS.paused, { timeout: 5_000 });
});
test('click on controls container does not trigger container gesture', async () => {
await expect(player.playButton).toHaveAttribute(DATA_ATTRS.paused, '');
await player.controls.dispatchEvent('pointerdown', { button: 0, pointerType: 'mouse' });
await player.controls.dispatchEvent('pointerup', { button: 0, pointerType: 'mouse' });
await expect(player.playButton).toHaveAttribute(DATA_ATTRS.paused, '');
});
test('click on slider does not trigger container gesture', async ({ page }) => {
// Start playback so the slider has a seekable range
await player.play();
@@ -91,6 +98,13 @@ test.describe('React Mouse Gestures', () => {
await expect(player.playButton).not.toHaveAttribute(DATA_ATTRS.paused, { timeout: 5_000 });
});
test('click on controls container does not trigger container gesture', async () => {
await expect(player.playButton).toHaveAttribute(DATA_ATTRS.paused, '');
await player.controls.dispatchEvent('pointerdown', { button: 0, pointerType: 'mouse' });
await player.controls.dispatchEvent('pointerup', { button: 0, pointerType: 'mouse' });
await expect(player.playButton).toHaveAttribute(DATA_ATTRS.paused, '');
});
test('click on slider does not trigger container gesture', async ({ page }) => {
await player.play();
await page.waitForTimeout(500);
@@ -464,6 +464,24 @@ describe('interactive child filtering', () => {
expect(handler).not.toHaveBeenCalled();
});
it('does not fire from any child inside a marked controls surface', () => {
const container = setup();
const controls = document.createElement('div');
const label = document.createElement('span');
controls.setAttribute('data-interactive', '');
controls.appendChild(label);
container.appendChild(controls);
const handler = vi.fn();
createTapGesture(container, handler);
pointerDown(label);
vi.advanceTimersByTime(50);
pointerUp(label, { pointerType: 'mouse', clientX: 150 });
expect(handler).not.toHaveBeenCalled();
});
it('fires when event originates from a non-interactive child', () => {
const container = setup();
const overlay = document.createElement('div');
@@ -20,6 +20,8 @@ export class ControlsElement extends MediaElement {
override connectedCallback(): void {
super.connectedCallback();
this.setAttribute('data-interactive', '');
if (__DEV__ && !this.#mediaState.value && this.#mediaState.displayName) {
logMissingFeature(this.localName, this.#mediaState.displayName);
}
@@ -98,6 +98,18 @@ afterEach(() => {
});
describe('ControlsElement', () => {
it('marks the controls surface as interactive', async () => {
const provider = document.createElement('test-controls-player-provider') as TestPlayerProviderElement;
const controls = createDefinedElement(ControlsElement);
document.body.append(provider);
provider.append(controls);
await controls.updateComplete;
expect(controls.hasAttribute('data-interactive')).toBe(true);
});
it('closes owned popovers, menus, and tooltips when controls hide', async () => {
const provider = document.createElement('test-controls-player-provider') as TestPlayerProviderElement;
const controls = createDefinedElement(ControlsElement);
@@ -42,7 +42,7 @@ export const ControlsRoot = forwardRef(function ControlsRoot(
state,
stateAttrMap: ControlsDataAttrs,
ref: [forwardedRef],
props: [{ children }, elementProps],
props: [{ children }, elementProps, { 'data-interactive': '' }],
}
)}
</ControlsContextProvider>
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { createPlayerWrapper } from '../../../testing/mocks';
import { ControlsRoot } from '../controls-root';
describe('ControlsRoot', () => {
it('marks the controls surface as interactive', () => {
const { Wrapper } = createPlayerWrapper({
controlsVisible: true,
userActive: true,
});
const { getByTestId } = render(<ControlsRoot data-testid="controls" />, { wrapper: Wrapper });
expect(getByTestId('controls').hasAttribute('data-interactive')).toBe(true);
});
});