@readium/navigator-html-injectables 2.4.1 → 2.4.2-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator-html-injectables",
3
- "version": "2.4.1",
3
+ "version": "2.4.2-beta.1",
4
4
  "type": "module",
5
5
  "description": "An embeddable solution for connecting frames of HTML publications with a Readium Navigator",
6
6
  "author": "readium",
@@ -30,6 +30,7 @@ export function deselect(wnd: ReadiumWindow) {
30
30
 
31
31
  const interactiveTags = [
32
32
  "a",
33
+ "area",
33
34
  "audio",
34
35
  "button",
35
36
  "canvas",
@@ -43,17 +44,14 @@ const interactiveTags = [
43
44
  "video",
44
45
  ];
45
46
 
47
+ const interactiveRoles = ["dialog", "radiogroup", "radio", "menu", "menuitem"];
48
+
46
49
  // See https://github.com/JayPanoz/architecture/tree/touch-handling/misc/touch-handling
47
50
  export function nearestInteractiveElement(element: Element): Element | null {
48
- if (interactiveTags.indexOf(element.nodeName.toLowerCase()) !== -1) {
49
- return element;
50
- }
51
-
52
- // Checks whether the element is editable by the user.
53
- if (
54
- element.hasAttribute("contenteditable") &&
55
- element.getAttribute("contenteditable")?.toLowerCase() !== "false"
56
- ) {
51
+ // If the element or any ancestor is blocked, return null immediately
52
+ if (isElementBlocked(element)) return null;
53
+
54
+ if (isInteractiveElement(element)) {
57
55
  return element;
58
56
  }
59
57
 
@@ -65,6 +63,29 @@ export function nearestInteractiveElement(element: Element): Element | null {
65
63
  return null;
66
64
  }
67
65
 
66
+ export function isElementBlocked(element: Element | null): boolean {
67
+ if (!element) {
68
+ return true;
69
+ }
70
+
71
+ return element.closest("[inert]") !== null || element.hasAttribute("disabled");
72
+ }
73
+
74
+ export function isInteractiveElement(element: Element | null): boolean {
75
+ if (!element) {
76
+ return false;
77
+ }
78
+
79
+ // Check for interactive roles
80
+ if (element.role && interactiveRoles.includes(element.role)) return true;
81
+
82
+ if ((element as HTMLElement).tabIndex >= 0) return true;
83
+
84
+ // Use existing interactive tags logic
85
+ return interactiveTags.includes(element.nodeName.toLowerCase()) ||
86
+ element.hasAttribute("contenteditable") && element.getAttribute("contenteditable")?.toLowerCase() !== "false";
87
+ }
88
+
68
89
  /// Returns the `Locator` object to the first block element that is visible on
69
90
  /// the screen.
70
91
  export function findFirstVisibleLocator(wnd: ReadiumWindow, scrolling: boolean) {
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './comms/index.ts';
2
2
  export * from './modules/index.ts';
3
3
  export * from './Loader.ts';
4
4
  export * from './protection/index.ts';
5
+ export * from './keyboard/index.ts'
@@ -1,6 +1,6 @@
1
1
  import { KeyCombo, KeyboardPeripheral } from "./KeyboardCombinations.ts";
2
2
  import { BaseKeyboardPeripheralEvent, KeyboardEventData, BasicTextSelection } from "../modules/Peripherals.ts";
3
- import { ReadiumWindow } from "../helpers/dom.ts";
3
+ import { isInteractiveElement, nearestInteractiveElement, ReadiumWindow } from "../helpers/dom.ts";
4
4
 
5
5
  export type KeyHandler = (event: KeyboardEvent) => void;
6
6
  export type ActivityEventDispatcher = (event: KeyboardPeripheralEvent) => void;
@@ -56,6 +56,8 @@ export class KeyCombinationManager {
56
56
  ): KeyboardPeripheralEvent {
57
57
  // Capture selected text if window is available
58
58
  let selectedText: Omit<BasicTextSelection, "targetFrameSrc"> | undefined;
59
+ let interactiveElement: string | undefined;
60
+
59
61
  if (wnd) {
60
62
  const selection = wnd.getSelection();
61
63
  const selectedTextStr = selection?.toString() || '';
@@ -71,6 +73,12 @@ export class KeyCombinationManager {
71
73
  height: rect.height
72
74
  };
73
75
  }
76
+
77
+ // Capture interactive element information following the same pattern as onPointUp
78
+ const activeElement = wnd.document.activeElement;
79
+ if (activeElement && activeElement !== wnd.document.body) {
80
+ interactiveElement = nearestInteractiveElement(activeElement)?.outerHTML;
81
+ }
74
82
  }
75
83
 
76
84
  return {
@@ -84,7 +92,8 @@ export class KeyCombinationManager {
84
92
  shiftKey: event.shiftKey,
85
93
  metaKey: event.metaKey,
86
94
  targetFrameSrc: targetFrameSrc,
87
- selectedText
95
+ selectedText,
96
+ interactiveElement
88
97
  };
89
98
  }
90
99
 
@@ -128,10 +137,15 @@ export class KeyCombinationManager {
128
137
  return (event: KeyboardEvent) => {
129
138
  for (const handlerConfig of handlers) {
130
139
  if (this.match(event, [handlerConfig])) {
140
+ const suppress = handlerConfig.suppressOnInteractiveElement;
141
+ if (suppress) {
142
+ const active = document.activeElement;
143
+ if (Array.isArray(suppress) ? suppress.some(sel => active?.matches(sel)) : isInteractiveElement(active)) return;
144
+ }
131
145
  event.preventDefault();
132
146
  event.stopPropagation();
133
147
  handlerConfig.handler!(event);
134
- return; // Stop after first match
148
+ return;
135
149
  }
136
150
  }
137
151
  };
@@ -4,6 +4,7 @@ export interface KeyCombo {
4
4
  shift?: boolean;
5
5
  alt?: boolean;
6
6
  meta?: boolean;
7
+ suppressOnInteractiveElement?: boolean | string[];
7
8
  }
8
9
 
9
10
  export interface KeyboardPeripheral {
@@ -0,0 +1,2 @@
1
+ export * from "./KeyboardCombinations";
2
+ export * from "./KeyCombinationManager";
@@ -3,18 +3,12 @@ import { Module } from "./Module.ts";
3
3
  import { ReadiumWindow, nearestInteractiveElement } from "../helpers/dom.ts";
4
4
  import { BulkCopyProtector, BulkCopyProtectionOptions } from "../protection/BulkCopyProtector.ts";
5
5
  import { SelectionAnalyzer, SelectionAnalyzerOptions } from "../protection/SelectionAnalyzer.ts";
6
- import {
7
- KeyboardPeripheral
8
- } from "../protection/index.ts";
9
6
  import { SuspiciousActivityType } from "../comms/index.ts";
10
7
  import { BULK_COPY_CONFIG, SELECTION_ANALYZER_CONFIG } from "../protection/config.ts";
11
- import {
12
- KeyCombinationManager,
13
- ActivityEventDispatcher,
14
- KeyboardPeripheralEvent
15
- } from "../protection/KeyCombinationManager.ts";
16
8
  import { SuspiciousScrollingEvent } from "./snapper/ScrollSnapper.ts";
17
9
  import { SuspiciousSnappingEvent } from "./snapper/ColumnSnapper.ts";
10
+ import { ActivityEventDispatcher, KeyboardPeripheralEvent, KeyCombinationManager } from "../keyboard/KeyCombinationManager.ts";
11
+ import { KeyboardPeripheral } from "../keyboard/KeyboardCombinations.ts";
18
12
 
19
13
  export interface FrameClickEvent {
20
14
  defaultPrevented: boolean;
@@ -59,6 +53,7 @@ export interface BaseKeyboardPeripheralEvent {
59
53
  timestamp: number;
60
54
  targetFrameSrc: string;
61
55
  selectedText?: Omit<BasicTextSelection, "targetFrameSrc">;
56
+ interactiveElement?: string;
62
57
  }
63
58
 
64
59
  export interface DeveloperToolsEvent extends BaseSuspiciousActivityEvent, KeyboardEventData {
@@ -1,6 +1,4 @@
1
1
  export * from "./BulkCopyProtector.ts";
2
- export * from "./KeyCombinationManager.ts";
3
- export * from "./KeyboardCombinations.ts";
4
2
  export * from "./PatternAnalyzer.ts";
5
3
  export * from "./SelectionAnalyzer.ts";
6
- export * from "./PrintProtector.ts";
4
+ export * from "./PrintProtector.ts";
@@ -20,5 +20,7 @@ export interface ReadiumWindow extends Window {
20
20
  }
21
21
  export declare function deselect(wnd: ReadiumWindow): void;
22
22
  export declare function nearestInteractiveElement(element: Element): Element | null;
23
+ export declare function isElementBlocked(element: Element | null): boolean;
24
+ export declare function isInteractiveElement(element: Element | null): boolean;
23
25
  export declare function findFirstVisibleLocator(wnd: ReadiumWindow, scrolling: boolean): Locator;
24
26
  export {};
@@ -2,3 +2,4 @@ export * from './comms/index.ts';
2
2
  export * from './modules/index.ts';
3
3
  export * from './Loader.ts';
4
4
  export * from './protection/index.ts';
5
+ export * from './keyboard/index.ts';
@@ -4,6 +4,7 @@ export interface KeyCombo {
4
4
  shift?: boolean;
5
5
  alt?: boolean;
6
6
  meta?: boolean;
7
+ suppressOnInteractiveElement?: boolean | string[];
7
8
  }
8
9
  export interface KeyboardPeripheral {
9
10
  type: string;
@@ -0,0 +1,2 @@
1
+ export * from "./KeyboardCombinations";
2
+ export * from "./KeyCombinationManager";
@@ -4,9 +4,9 @@ import { ReadiumWindow } from "../helpers/dom.ts";
4
4
  import { BulkCopyProtectionOptions } from "../protection/BulkCopyProtector.ts";
5
5
  import { SelectionAnalyzerOptions } from "../protection/SelectionAnalyzer.ts";
6
6
  import { SuspiciousActivityType } from "../comms/index.ts";
7
- import { KeyboardPeripheralEvent } from "../protection/KeyCombinationManager.ts";
8
7
  import { SuspiciousScrollingEvent } from "./snapper/ScrollSnapper.ts";
9
8
  import { SuspiciousSnappingEvent } from "./snapper/ColumnSnapper.ts";
9
+ import { KeyboardPeripheralEvent } from "../keyboard/KeyCombinationManager.ts";
10
10
  export interface FrameClickEvent {
11
11
  defaultPrevented: boolean;
12
12
  doNotDisturb: boolean;
@@ -44,6 +44,7 @@ export interface BaseKeyboardPeripheralEvent {
44
44
  timestamp: number;
45
45
  targetFrameSrc: string;
46
46
  selectedText?: Omit<BasicTextSelection, "targetFrameSrc">;
47
+ interactiveElement?: string;
47
48
  }
48
49
  export interface DeveloperToolsEvent extends BaseSuspiciousActivityEvent, KeyboardEventData {
49
50
  type: "developer_tools";
@@ -1,6 +1,4 @@
1
1
  export * from "./BulkCopyProtector.ts";
2
- export * from "./KeyCombinationManager.ts";
3
- export * from "./KeyboardCombinations.ts";
4
2
  export * from "./PatternAnalyzer.ts";
5
3
  export * from "./SelectionAnalyzer.ts";
6
4
  export * from "./PrintProtector.ts";