Browse Source

feat: onInteraction in interface

Sv443 11 months ago
parent
commit
4d765dc6f6
7 changed files with 55 additions and 10 deletions
  1. 3 3
      changelog.md
  2. 44 1
      contributing.md
  3. 2 1
      src/features/layout.css
  4. 1 1
      src/features/layout.ts
  5. 2 1
      src/interface.ts
  6. 2 2
      src/observers.ts
  7. 1 1
      src/utils/dom.ts

+ 3 - 3
changelog.md

@@ -24,8 +24,8 @@
     - Added a cache to save lyrics in. 1000 of the most listened to songs are saved throughout sessions for 30 days to save time and reduce server load.
   - Implemented new class BytmDialog for less duplicate code, better maintainability, the ability to make more menus easier and for them to have better accessibility
   - Expanded plugin interface
-    - Added function to register plugins (see [contributing guide](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#registerplugin))
-    - Plugins are now given access to the classes [`BytmDialog`](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#bytmdialog) and [`NanoEmitter`](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#nanoemitter), and the functions [`createHotkeyInput()`](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#createhotkeyinput), [`createToggleInput()`](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#createtoggleinput) and [`createCircularBtn()`](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#createcircularbtn)
+    - Added function to register plugins (see [contributing guide](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#registerplugin))
+    - Plugins are now given access to the classes [`BytmDialog`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#bytmdialog) and [`NanoEmitter`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#nanoemitter), and the functions [`onInteraction()`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#oninteraction), [`createHotkeyInput()`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#createhotkeyinput), [`createToggleInput()`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#createtoggleinput) and [`createCircularBtn()`](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#createcircularbtn)
   - Added an experimental fuzzy filtering algorithm when fetching lyrics to eventually yield more accurate results (hidden behind advanced mode because it's far from perfect)
 
 <div class="pr-link-cont">
@@ -60,7 +60,7 @@
 ## 1.1.0
 - **Features / Changes:**
   - The userscript is now available in 9 languages! To submit or edit translations, please [view this guide](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#submitting-translations)
-  - Added an interface for user-created plugins ([see contributing guide for more info](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#developing-a-plugin-that-interfaces-with-betterytm))
+  - Added an interface for user-created plugins ([see contributing guide for more info](https://github.com/Sv443/BetterYTM/blob/main/contributing.md#maining-a-plugin-that-interfaces-with-betterytm))
   - Made site switch hotkey customizable
   - Userscript will now show a welcome page after first install / update
   - Feature to restore last song's time on page reload

+ 44 - 1
contributing.md

@@ -265,9 +265,10 @@ The usage and example blocks on each are written in TypeScript but can be used i
   - [getResourceUrl()](#getresourceurl) - Returns a `blob:` URL provided by the local userscript extension for the specified BYTM resource file
   - [getSessionId()](#getsessionid) - Returns the unique session ID that is generated on every started session
 - DOM:
+  - [BytmDialog](#bytmdialog) - A class for creating and managing dialogs
   - [addSelectorListener()](#addselectorlistener) - Adds a listener that checks for changes in DOM elements matching a CSS selector
+  - [onInteraction()](#oninteraction) - Adds accessible event listeners to the specified element for button or link-like keyboard and mouse interactions
   - [getVideoTime()](#getvideotime) - Returns the current video time (on both YT and YTM)
-  - [BytmDialog](#bytmdialog) - A class for creating and managing dialogs
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createToggleInput()](#createtoggleinput) - Creates a toggle input element
   - [createCircularBtn()](#createcircularbtn) - Creates a generic, circular button element
@@ -489,6 +490,48 @@ The usage and example blocks on each are written in TypeScript but can be used i
 
 <br>
 
+> #### onInteraction()
+> Usage:
+> ```ts
+> unsafeWindow.BYTM.onInteraction(
+>   element: HTMLElement,
+>   callback: (event: MouseEvent | KeyboardEvent) => void,
+>   listenerOptions?: AddEventListenerOptions
+> ): void
+> ```
+>
+> Description:  
+> Adds accessible event listeners to the specified element for button or link-like keyboard and mouse interactions.  
+> All events passed to the callback function automatically have the default behavior prevented and stop immediate propagation, meaning no other listener of the same type will be called.  
+> For keyboard events this only happens as long as the captured key is a valid interaction key (Space, Enter).  
+>   
+> Arguments:
+> - `element` - The element to add the listeners to
+> - `callback` - The function to call when the element is interacted with
+> - `listenerOptions` - Optional event listener options (same as the third argument of `addEventListener`, shared between the keyboard and mouse event listeners)
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> const myButton = document.querySelector("button#myButton");
+> 
+> unsafeWindow.BYTM.onInteraction(myButton, (event) => {
+>   if(event instanceof MouseEvent)
+>     console.log("The button was clicked");
+>   else if(event instanceof KeyboardEvent)
+>     console.log("The button was activated with the keyboard (Space / Enter)");
+> }, {
+>   // if `once` is set, when either the mouse or keyboard event are triggered once,
+>   // the other listener is automatically removed as well to prevent double triggering
+>   once: true,
+>   // you can pass `capture` to listen in the capture phase (helpful for triggering before other listeners),
+>   // or an AbortController's `signal` to be able to abort the listener
+> });
+> ```
+> </details>
+
+<br>
+
 > #### getVideoTime()
 > Usage:  
 > ```ts

+ 2 - 1
src/features/layout.css

@@ -206,7 +206,8 @@ yt-multi-page-menu-section-renderer.ytd-multi-page-menu-renderer {
 }
 
 .bytm-yt-cfg-menu-option:hover {
-  background-color: var(--yt-spec-badge-chip-background, #3e3e3e) !important;
+  /* I would've loved to just use the YT-provided var(--yt-spec-badge-chip-background) but darkreader is being a bitch and a half and overwrites it */
+  background-color: rgba(255, 255, 255, 0.05);
 }
 
 .bytm-yt-cfg-menu-option-icon {

+ 1 - 1
src/features/layout.ts

@@ -149,7 +149,7 @@ export async function addConfigMenuOptionYTM(container: HTMLElement) {
 export async function addConfigMenuOptionYT(container: HTMLElement) {
   // TODO:
   const cfgOptElem = document.createElement("div");
-  cfgOptElem.classList.add("bytm-yt-cfg-menu-option");
+  cfgOptElem.classList.add("bytm-yt-cfg-menu-option", "darkreader-ignore");
   cfgOptElem.role = "button";
   cfgOptElem.tabIndex = 0;
   cfgOptElem.ariaLabel = cfgOptElem.title = t("open_menu_tooltip", scriptInfo.name);

+ 2 - 1
src/interface.ts

@@ -1,7 +1,7 @@
 import * as UserUtils from "@sv443-network/userutils";
 import { createNanoEvents } from "nanoevents";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants";
-import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, NanoEmitter, t, tp, type TrLocale, info, error } from "./utils";
+import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, NanoEmitter, t, tp, type TrLocale, info, error, onInteraction } from "./utils";
 import { addSelectorListener } from "./observers";
 import { getFeatures, setFeatures } from "./config";
 import { compareVersionArrays, compareVersions, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong, type LyricsCache } from "./features";
@@ -77,6 +77,7 @@ const globalFuncs = {
   sanitizeSong,
   compareVersions,
   compareVersionArrays,
+  onInteraction,
 };
 
 /** Initializes the BYTM interface */

+ 2 - 2
src/observers.ts

@@ -28,8 +28,8 @@ export type YTMObserverName =
 
 // YT only
 export type YTObserverName =
-  // | "ytMasthead"
-  | "ytGuide";
+  // | "ytMasthead" // the title bar
+  | "ytGuide"; // the left sidebar menu
 
 /** Options that are applied to every SelectorObserver instance */
 const defaultObserverOptions: SelectorObserverOptions = {

+ 1 - 1
src/utils/dom.ts

@@ -146,7 +146,7 @@ const interactionKeys = ["Enter", " ", "Space"];
 
 /**
  * Adds generic, accessible interaction listeners to the passed element.  
- * All listeners have the default behavior prevented and stop immediate propagation.
+ * All listeners have the default behavior prevented and stop immediate propagation (for keyboard events only as long as the captured key is valid).
  * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
  */
 export function onInteraction<TElem extends HTMLElement>(elem: TElem, listener: (evt: MouseEvent | KeyboardEvent) => void, listenerOptions?: AddEventListenerOptions) {