Skip to content

Commit

Permalink
Merge pull request #8 from Gyanreyer/make-hover-evts-cancelable
Browse files Browse the repository at this point in the history
v1.2.2: Make hover events cancelable
  • Loading branch information
Gyanreyer authored May 10, 2024
2 parents b82d3d3 + 61df269 commit c1a97e7
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v1.2.2

- Makes `hoverstart` and `hoverend` events cancelable for more options with controlling playback
- Allows `hoverstart` and `hoverend` events to still be emitted when the component is controlled; the component just won't update playback state in response to these events

## v1.2.1

- Adds `playbackstatechanged` event
Expand Down
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,19 +561,38 @@ console.log(player.dataset.playbackState); // "paused"
The player component will emit a custom `"hoverstart"` event when a user hovers over the player's hover target to start playback,
or the `hover()` method is manually called.

`"hoverstart"` events are cancelable. If you wish to intercept a `"hoverstart"` event and prevent the component from proceeding to start playback,
you can call `event.preventDefault()` in a `hoverstart` listener.

```js
const player = document.querySelector("hover-video-player");
player.addEventListener("hoverstart", () => console.log("The user hovered!"));
player.addEventListener("hoverstart", (evt) => {
console.log("The user hovered!");
if (shouldPreventPlay) {
// Call preventDefault to prevent the component from proceeding to start playback in response to this `hoverstart` event
evt.preventDefault();
}
});
```

#### `hoverend`

The player component will emit a custom `"hoverend"` event when a user stops hovering over the player's hover target to stop playback,
or the `blur()` method is manually called.

`"hoverend"` events are cancelable. If you wish to intercept a `"hoverend"` event and prevent the component from proceeding to pause playback,
you can call `event.preventDefault()` in a `hoverend` listener.

```js
const player = document.querySelector("hover-video-player");
player.addEventListener("hoverend", () => console.log("The user is no longer hovering!"));
player.addEventListener("hoverend", (evt) => {
console.log("The user is no longer hovering!");

if (shouldPreventPause) {
// Call preventDefault to prevent the component from proceeding to pause playback in response to this `hoverend` event
evt.preventDefault();
}
});
```

#### `playbackstatechange`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hover-video-player",
"version": "1.2.1",
"version": "1.2.2",
"description": "A web component for rendering videos that play on hover, including support for mouse and touch events and a simple API for adding thumbnails and loading states.",
"repository": {
"type": "git",
Expand Down
22 changes: 14 additions & 8 deletions src/hover-video-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export default class HoverVideoPlayer extends HTMLElement {

this.shadowRoot?.addEventListener("slotchange", this._onSlotChange);

if (!this.controlled && !this.hasAttribute("hover-target")) {
if (!this.hasAttribute("hover-target")) {
this._addHoverTargetListeners();
}

Expand Down Expand Up @@ -340,10 +340,14 @@ export default class HoverVideoPlayer extends HTMLElement {
_onHover(event: Event) {
this._activeHoverTarget = event.currentTarget;
if (!this.isHovering) {
this.dispatchEvent(new CustomEvent("hoverstart", {
const wasNotCanceled = this.dispatchEvent(new CustomEvent("hoverstart", {
detail: this._activeHoverTarget,
cancelable: true,
}));
this.hover();
// If evt.preventDefault() is called for this event, we'll cancel starting playback
if (wasNotCanceled && !this.controlled) {
this.hover();
}
}
}

Expand All @@ -365,10 +369,14 @@ export default class HoverVideoPlayer extends HTMLElement {
* Handler for blur events on hover target
*/
_onBlur(event: Event) {
this.dispatchEvent(new CustomEvent("hoverend", {
const wasNotCanceled = this.dispatchEvent(new CustomEvent("hoverend", {
detail: event.currentTarget,
cancelable: true,
}));
this.blur();
// If evt.preventDefault() is called for this event, we'll cancel pausing playback
if (wasNotCanceled && !this.controlled) {
this.blur();
}
}

/**
Expand Down Expand Up @@ -613,9 +621,7 @@ export default class HoverVideoPlayer extends HTMLElement {

this._removeHoverTargetListeners();
this._hoverTarget = newHoverTarget;
if (!this.controlled) {
this._addHoverTargetListeners();
}
this._addHoverTargetListeners();
}

/**
Expand Down
65 changes: 65 additions & 0 deletions tests/events.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from '@playwright/test';
import type HoverVideoPlayer from '../src/hover-video-player';

test("fires hoverstart and hoverend events as expected", async ({ page }) => {
await page.goto("/tests/events.html");
Expand All @@ -20,3 +21,67 @@ test("fires hoverstart and hoverend events as expected", async ({ page }) => {
await expect(hoverStartCounter).toHaveText("1");
await expect(hoverEndCounter).toHaveText("1");
});

test("hoverstart events can be prevented", async ({ page }) => {
await page.goto("/tests/events.html");

const component = await page.locator("hover-video-player");

await component.evaluate((el: HoverVideoPlayer) => {
(window as any).hoverStartListener = (evt) => {
evt.preventDefault();
};
el.addEventListener("hoverstart", (window as any).hoverStartListener);
});

await component.hover();

await expect(component).not.toHaveAttribute("data-is-hovering", "");

await page.mouse.move(0, 0);

// Remoe the listener and hover again
await component.evaluate((el: HoverVideoPlayer) => {
el.removeEventListener("hoverstart", (window as any).hoverStartListener);
});

// Now hovering should work
await component.hover();
await expect(component).toHaveAttribute("data-is-hovering", "");

await page.mouse.move(0, 0);
});

test("hoverend events can be prevented", async ({ page }) => {
await page.goto("/tests/events.html");

const component = await page.locator("hover-video-player");

await component.evaluate((el: HoverVideoPlayer) => {
(window as any).hoverEndListener = (evt) => {
evt.preventDefault();
};
el.addEventListener("hoverend", (window as any).hoverEndListener);
});

await component.hover();

await expect(component).toHaveAttribute("data-is-hovering", "");

await page.mouse.move(0, 0);

// Still hovering because hoverend was cancelled
await expect(component).toHaveAttribute("data-is-hovering", "");

await component.evaluate((el: HoverVideoPlayer) => {
el.removeEventListener("hoverend", (window as any).hoverEndListener);
});

// Re-hover so we can test that hoverend is not prevented
await component.hover();
await expect(component).toHaveAttribute("data-is-hovering", "");

// Now mousing out should work
await page.mouse.move(0, 0);
await expect(component).not.toHaveAttribute("data-is-hovering", "");
});

0 comments on commit c1a97e7

Please sign in to comment.