@scarlett-player/gestures 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @scarlett-player/gestures
2
+
3
+ Touch gestures for [Scarlett Player](https://scarlettplayer.com). Double-tap the right of the picture to jump forward, the left to jump back, keep tapping to go further, and single-tap to toggle the controls. Gestures are gated on `pointerType === 'touch'`, so a mouse or pen never triggers any of them and desktop behaviour is unchanged.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @scarlett-player/core @scarlett-player/gestures
9
+ ```
10
+
11
+ `@scarlett-player/core` is a peer dependency. `@scarlett-player/ui` is optional: tap-to-toggle needs it, double-tap seeking does not.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createPlayer } from '@scarlett-player/core';
17
+ import { uiPlugin } from '@scarlett-player/ui';
18
+ import { createGesturesPlugin } from '@scarlett-player/gestures';
19
+
20
+ const player = await createPlayer({
21
+ container: '#player',
22
+ src: 'https://example.com/video.m3u8',
23
+ plugins: [uiPlugin(), createGesturesPlugin({ seekSeconds: 10 })],
24
+ });
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ | Option | Type | Default | Description |
30
+ |---|---|---|---|
31
+ | `enabled` | `boolean \| 'auto'` | `'auto'` | `'auto'` installs the gesture surface when `matchMedia('(any-pointer: coarse)')` matches, so a touchscreen laptop counts. `true` forces it, `false` disables the plugin. No user agent sniffing |
32
+ | `seekSeconds` | `number` | `10` | Seconds moved per seek step |
33
+ | `doubleTapWindowMs` | `number` | `275` | Second tap within this window makes a double tap |
34
+ | `accumulationWindowMs` | `number` | `650` | A further tap in the same zone within this window extends the seek |
35
+ | `zones` | `{ left?, right? }` | `{ left: 0.33, right: 0.33 }` | Fractions of the width given to each seek zone. The middle is inert on purpose |
36
+ | `slopPx` | `number` | `10` | Movement in CSS pixels that turns a tap into a drag and cancels it |
37
+ | `feedback` | `boolean` | `true` | Show the zone highlight and cumulative seconds label |
38
+ | `haptics` | `boolean` | `true` | `navigator.vibrate(10)` on each seek step where supported. iOS Safari ignores it |
39
+ | `tapToToggleControls` | `boolean` | `true` | Let a single tap show or hide the controls |
40
+
41
+ ## Behaviour
42
+
43
+ - Not installed for audio: when `mediaType` is `'audio'` at init the plugin stays inactive.
44
+ - Seeking is refused while Chromecast or AirPlay is active, on live streams with no seekable range, and on VOD until a finite duration is known.
45
+ - On live DVR a seek is clamped to the seekable range. A forward tap at the live edge announces "Already at the live edge" instead of showing a ripple.
46
+ - Each tap in a run adds `seekSeconds`; the label shows the cumulative total. A tap in the opposite zone ends the run rather than reversing it, and a drag or a second finger cancels it.
47
+ - A single tap shows hidden controls immediately. Hiding waits out `doubleTapWindowMs` so a seek is never preceded by the controls blinking away, and nothing hides while paused.
48
+ - The surface never calls `preventDefault` or `stopPropagation`, so document-level click handlers (menus closing, for example) still fire.
49
+
50
+ The plugin exposes `ownsTapInteraction()`, which the UI package checks before running its own show-controls logic on touch. It returns `true` while the plugin is active and `tapToToggleControls` is on.
51
+
52
+ ## Events
53
+
54
+ | Event | Payload | When |
55
+ |---|---|---|
56
+ | `gesture:tap` | `{ zone }` | A single tap landed. `zone` is `'left'`, `'middle'` or `'right'` |
57
+ | `gesture:seek` | `{ direction, seconds, cumulative }` | A seek step moved the playhead. `direction` is `'forward'` or `'backward'` |
58
+
59
+ Each seek step also emits the core `playback:seeking` event with the target time.
60
+
61
+ ## CSS
62
+
63
+ Styles are injected once per document in a `<style id="sp-gestures-styles">`. There are no CSS custom properties.
64
+
65
+ | Class | Element |
66
+ |---|---|
67
+ | `.sp-gestures` | The surface. Absolutely positioned at `z-index: 6`, leaving the bottom 64px to the progress and control bars |
68
+ | `.sp-gestures__zone` | One seek zone, `--left` or `--right`, sized from `zones`. `--active` while feedback is showing |
69
+ | `.sp-gestures__label` | The cumulative seconds label inside a zone |
70
+ | `.sp-gestures__live` | Visually hidden polite live region carrying the announcement |
71
+
72
+ The zone transition is disabled under `prefers-reduced-motion: reduce`.
73
+
74
+ ## Lower level exports
75
+
76
+ `createRecognizer`, `zoneFor` and `DEFAULT_RECOGNIZER_OPTIONS` expose the pure tap state machine, and `GestureOverlay` the DOM surface, for hosts that want to build their own handling on top.
77
+
78
+ ## License
79
+
80
+ MIT
package/dist/index.cjs CHANGED
@@ -350,13 +350,16 @@ var GestureOverlay = class {
350
350
  }
351
351
  };
352
352
 
353
+ // src/version.ts
354
+ var PKG_VERSION = true ? "1.8.1" : "0.0.0-dev";
355
+
353
356
  // src/index.ts
354
357
  function hasCoarsePointer() {
355
358
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
356
359
  return false;
357
360
  }
358
361
  try {
359
- return window.matchMedia("(pointer: coarse)").matches;
362
+ return window.matchMedia("(any-pointer: coarse)").matches;
360
363
  } catch {
361
364
  return false;
362
365
  }
@@ -474,7 +477,7 @@ function createGesturesPlugin(config = {}) {
474
477
  return {
475
478
  id: "gestures",
476
479
  name: "Gestures",
477
- version: "1.0.0",
480
+ version: PKG_VERSION,
478
481
  type: "feature",
479
482
  init(pluginApi) {
480
483
  api = pluginApi;
package/dist/index.d.cts CHANGED
@@ -58,10 +58,11 @@ interface GesturesPluginConfig {
58
58
  /**
59
59
  * Whether gestures are active.
60
60
  *
61
- * `'auto'` enables them when the device reports a coarse pointer, and every
62
- * individual gesture is additionally checked for `pointerType === 'touch'`,
63
- * so a touchscreen laptop works with a finger and is untouched by the mouse.
64
- * Never UA sniffing.
61
+ * `'auto'` enables them wherever the device has a coarse pointer at all
62
+ * (`matchMedia('(any-pointer: coarse)')`, not the primary-pointer
63
+ * `(pointer: coarse)`), and every individual gesture is additionally checked
64
+ * for `pointerType === 'touch'`, so a touchscreen laptop works with a finger
65
+ * and is untouched by the mouse. Never UA sniffing.
65
66
  *
66
67
  * @defaultValue 'auto'
67
68
  */
@@ -235,9 +236,10 @@ declare class GestureOverlay {
235
236
  *
236
237
  * @example
237
238
  * ```ts
239
+ * import { createPlayer } from '@scarlett-player/core';
238
240
  * import { createGesturesPlugin } from '@scarlett-player/gestures';
239
241
  *
240
- * const player = new ScarlettPlayer({
242
+ * const player = await createPlayer({
241
243
  * container: '#player',
242
244
  * plugins: [uiPlugin(), createGesturesPlugin({ seekSeconds: 10 })],
243
245
  * });
package/dist/index.d.ts CHANGED
@@ -58,10 +58,11 @@ interface GesturesPluginConfig {
58
58
  /**
59
59
  * Whether gestures are active.
60
60
  *
61
- * `'auto'` enables them when the device reports a coarse pointer, and every
62
- * individual gesture is additionally checked for `pointerType === 'touch'`,
63
- * so a touchscreen laptop works with a finger and is untouched by the mouse.
64
- * Never UA sniffing.
61
+ * `'auto'` enables them wherever the device has a coarse pointer at all
62
+ * (`matchMedia('(any-pointer: coarse)')`, not the primary-pointer
63
+ * `(pointer: coarse)`), and every individual gesture is additionally checked
64
+ * for `pointerType === 'touch'`, so a touchscreen laptop works with a finger
65
+ * and is untouched by the mouse. Never UA sniffing.
65
66
  *
66
67
  * @defaultValue 'auto'
67
68
  */
@@ -235,9 +236,10 @@ declare class GestureOverlay {
235
236
  *
236
237
  * @example
237
238
  * ```ts
239
+ * import { createPlayer } from '@scarlett-player/core';
238
240
  * import { createGesturesPlugin } from '@scarlett-player/gestures';
239
241
  *
240
- * const player = new ScarlettPlayer({
242
+ * const player = await createPlayer({
241
243
  * container: '#player',
242
244
  * plugins: [uiPlugin(), createGesturesPlugin({ seekSeconds: 10 })],
243
245
  * });
package/dist/index.js CHANGED
@@ -319,13 +319,16 @@ var GestureOverlay = class {
319
319
  }
320
320
  };
321
321
 
322
+ // src/version.ts
323
+ var PKG_VERSION = true ? "1.8.1" : "0.0.0-dev";
324
+
322
325
  // src/index.ts
323
326
  function hasCoarsePointer() {
324
327
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
325
328
  return false;
326
329
  }
327
330
  try {
328
- return window.matchMedia("(pointer: coarse)").matches;
331
+ return window.matchMedia("(any-pointer: coarse)").matches;
329
332
  } catch {
330
333
  return false;
331
334
  }
@@ -443,7 +446,7 @@ function createGesturesPlugin(config = {}) {
443
446
  return {
444
447
  id: "gestures",
445
448
  name: "Gestures",
446
- version: "1.0.0",
449
+ version: PKG_VERSION,
447
450
  type: "feature",
448
451
  init(pluginApi) {
449
452
  api = pluginApi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scarlett-player/gestures",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Gestures Plugin for Scarlett Player - double-tap seek zones and tap to toggle controls",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "peerDependencies": {
25
- "@scarlett-player/core": "^1.5.0"
25
+ "@scarlett-player/core": "^1.8.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@vitest/coverage-v8": "^1.6.0",
@@ -30,7 +30,7 @@
30
30
  "tsup": "^8.5.1",
31
31
  "typescript": "^5.3.0",
32
32
  "vitest": "^1.6.0",
33
- "@scarlett-player/core": "1.7.0"
33
+ "@scarlett-player/core": "1.8.1"
34
34
  },
35
35
  "keywords": [
36
36
  "video",
@@ -54,10 +54,11 @@
54
54
  },
55
55
  "homepage": "https://scarlettplayer.com",
56
56
  "scripts": {
57
- "build": "tsup src/index.ts --format esm,cjs --dts",
58
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
57
+ "build": "tsup",
58
+ "dev": "tsup --watch",
59
59
  "test": "vitest --run",
60
60
  "test:watch": "vitest",
61
- "test:coverage": "vitest --coverage"
61
+ "test:coverage": "vitest --coverage",
62
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
62
63
  }
63
64
  }