@readium/navigator 2.5.8 → 2.6.0

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",
3
- "version": "2.5.8",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -60,7 +60,6 @@
60
60
  "tslib": "^2.8.1",
61
61
  "typescript": "^5.9.3",
62
62
  "typescript-plugin-css-modules": "^5.2.0",
63
- "user-agent-data-types": "^0.4.2",
64
63
  "vite": "^7.3.1"
65
64
  }
66
65
  }
@@ -0,0 +1,148 @@
1
+ import type {
2
+ Decoration as InjectableDecoration,
3
+ BuiltinDecorationStyle,
4
+ HTMLDecorationTemplate as WireHTMLDecorationTemplate,
5
+ } from "@readium/navigator-html-injectables";
6
+ import { DecorationStyleType } from "@readium/navigator-html-injectables";
7
+
8
+ export type { BuiltinDecorationStyle };
9
+ export { DecorationLayout, DecorationStyleType, DecorationWidth } from "@readium/navigator-html-injectables";
10
+
11
+ /**
12
+ * Navigator-level decoration template. `element` is a function called once per decoration to
13
+ * generate the HTML string that the injectable will render. The result is resolved on the
14
+ * navigator side (before postMessage) and sanitized by the injectable.
15
+ */
16
+ export interface HTMLDecorationTemplate extends Omit<WireHTMLDecorationTemplate, 'element'> {
17
+ element: (decoration: Decoration) => string;
18
+ }
19
+
20
+ export type DecorationStyle = BuiltinDecorationStyle | HTMLDecorationTemplate;
21
+
22
+ export interface Decoration extends Omit<InjectableDecoration, 'style'> {
23
+ style: DecorationStyle;
24
+ }
25
+
26
+ /**
27
+ * Resolves a navigator-level Decoration to a wire-safe plain object for postMessage.
28
+ * For Template styles, calls `element(decoration)` and embeds the resulting HTML string.
29
+ * For registered custom style IDs (found in `decorationTemplates`), resolves the template
30
+ * and converts the style to a Template wire object.
31
+ */
32
+ export function resolveDecorationForWire(
33
+ decoration: Decoration,
34
+ decorationTemplates?: Record<string, HTMLDecorationTemplate>
35
+ ): unknown {
36
+ const { style } = decoration;
37
+ if (style.type === DecorationStyleType.Template) {
38
+ const tpl = style as HTMLDecorationTemplate;
39
+ return { ...decoration, style: { ...tpl, element: tpl.element(decoration) } };
40
+ }
41
+ if (style.type && decorationTemplates?.[style.type]) {
42
+ const tpl = decorationTemplates[style.type];
43
+ return {
44
+ ...decoration,
45
+ style: {
46
+ type: DecorationStyleType.Template,
47
+ layout: tpl.layout,
48
+ width: tpl.width,
49
+ stylesheet: tpl.stylesheet,
50
+ isActive: style.isActive,
51
+ element: tpl.element(decoration),
52
+ },
53
+ };
54
+ }
55
+ return decoration;
56
+ }
57
+
58
+ export interface DecorationActivationEvent {
59
+ decoration: Decoration;
60
+ group: string;
61
+ /** Bounding rect of the activated decoration in navigator container coordinates (CSS pixels). */
62
+ rect?: { top: number; left: number; width: number; height: number };
63
+ /** Tap/click point in navigator container coordinates (CSS pixels). */
64
+ point?: { x: number; y: number };
65
+ }
66
+
67
+ export interface DecorationObserver {
68
+ /**
69
+ * Called when a user activates a decoration (click or tap).
70
+ * Return true to indicate the event was handled — this suppresses normal tap/click navigation.
71
+ */
72
+ onDecorationActivated(event: DecorationActivationEvent): boolean;
73
+ }
74
+
75
+ function stylesEqual(a: DecorationStyle, b: DecorationStyle): boolean {
76
+ if (a.type !== b.type) return false;
77
+ if ((a.isActive ?? false) !== (b.isActive ?? false)) return false;
78
+ if (a.type === DecorationStyleType.Template) {
79
+ const ta = a as HTMLDecorationTemplate;
80
+ const tb = b as HTMLDecorationTemplate;
81
+ // element is a function — not comparable by value; excluded from equality
82
+ return ta.layout === tb.layout &&
83
+ ta.width === tb.width &&
84
+ ta.stylesheet === tb.stylesheet;
85
+ }
86
+ const ba = a as BuiltinDecorationStyle;
87
+ const bb = b as BuiltinDecorationStyle;
88
+ return ba.tint === bb.tint &&
89
+ ba.layout === bb.layout &&
90
+ ba.width === bb.width &&
91
+ (ba.enforceContrast ?? true) === (bb.enforceContrast ?? true);
92
+ }
93
+
94
+ function serializeLocations(loc: any): any {
95
+ return typeof loc?.serialize === 'function' ? loc.serialize() : loc;
96
+ }
97
+
98
+ export function decorationsEqual(a: Decoration, b: Decoration): boolean {
99
+ return (
100
+ a.locator.href === b.locator.href &&
101
+ JSON.stringify(serializeLocations(a.locator.locations)) === JSON.stringify(serializeLocations(b.locator.locations)) &&
102
+ stylesEqual(a.style, b.style) &&
103
+ JSON.stringify(a.extras ?? null) === JSON.stringify(b.extras ?? null)
104
+ );
105
+ }
106
+
107
+ /** Configuration for decoration rendering. */
108
+ export interface DecoratorConfig {
109
+ /**
110
+ * Named custom styles. Each key is a style type ID; the value is the template that
111
+ * generates the HTML for decorations of that type. When a decoration's `style.type`
112
+ * matches a key here, the navigator resolves the template and sends it to the
113
+ * injectable as a Template-type decoration.
114
+ */
115
+ decorationTemplates?: Record<string, HTMLDecorationTemplate>;
116
+ }
117
+
118
+ const BUILTIN_DECORATION_TYPES = new Set<string>([
119
+ DecorationStyleType.Highlight,
120
+ DecorationStyleType.Underline,
121
+ DecorationStyleType.Outline,
122
+ DecorationStyleType.TextColor,
123
+ DecorationStyleType.Mask,
124
+ DecorationStyleType.Template,
125
+ ]);
126
+
127
+ export interface DecorableNavigator {
128
+ /**
129
+ * Replaces all decorations for the given group with the provided list.
130
+ * The navigator diffs the new list against the current state and issues
131
+ * add / update / remove / clear commands as needed.
132
+ */
133
+ applyDecorations(decorations: Decoration[], group: string): void;
134
+
135
+ /**
136
+ * Returns whether the given style type ID can be rendered by this navigator.
137
+ * Returns true for all built-in types and any IDs registered in DecoratorConfig.
138
+ */
139
+ supportsDecorationStyle(styleTypeId: string): boolean;
140
+
141
+ /** Registers an observer for activation events on the given group. */
142
+ registerDecorationObserver(group: string, observer: DecorationObserver): void;
143
+
144
+ /** Unregisters a previously registered observer from all groups. */
145
+ unregisterDecorationObserver(observer: DecorationObserver): void;
146
+ }
147
+
148
+ export { BUILTIN_DECORATION_TYPES };
@@ -1,8 +1,9 @@
1
- import { Layout, Link, Locator, Profile, Publication, ReadingProgression } from "@readium/shared";
1
+ import { Layout, Link, Locator, LocatorText, Profile, Publication, ReadingProgression } from "@readium/shared";
2
2
  import { Configurable, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
- import { CommsEventKey, ContextMenuEvent, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
5
+ import { CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
6
+ import { Decoration, DecorationActivationEvent, DecorationObserver, DecorableNavigator, DecoratorConfig, decorationsEqual, resolveDecorationForWire, BUILTIN_DECORATION_TYPES } from "../decorations/index.ts";
6
7
  import * as path from "path-browserify";
7
8
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
8
9
  import { FrameManager } from "./frame/FrameManager.ts";
@@ -29,6 +30,7 @@ export interface EpubNavigatorConfiguration {
29
30
  injectables?: IInjectablesConfig;
30
31
  contentProtection?: IContentProtectionConfig;
31
32
  keyboardPeripherals?: IKeyboardPeripheralsConfig;
33
+ decoratorConfig?: DecoratorConfig;
32
34
  }
33
35
 
34
36
  export interface EpubNavigatorListeners {
@@ -64,7 +66,7 @@ const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListe
64
66
  peripheral: listeners.peripheral || (() => {}),
65
67
  })
66
68
 
67
- export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences> {
69
+ export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences>, DecorableNavigator {
68
70
  private readonly pub: Publication;
69
71
  private readonly container: HTMLElement;
70
72
  private readonly listeners: EpubNavigatorListeners;
@@ -93,6 +95,13 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
93
95
 
94
96
  private resizeObserver: ResizeObserver;
95
97
 
98
+ private readonly _decoratorConfig: DecoratorConfig;
99
+
100
+ private _decorations: Map<string, Decoration[]> = new Map();
101
+ private _decorationObservers: Map<string, Set<DecorationObserver>> = new Map();
102
+ private _decorationActivationState: Map<string, boolean> = new Map();
103
+ private _decorationActivationConsumed = false;
104
+
96
105
  private reflowViewport: VisualNavigatorViewport = {
97
106
  readingOrder: [],
98
107
  progressions: new Map(),
@@ -152,6 +161,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
152
161
  this._readiumRulesPromise = createReadiumEpubRules(pub.metadata, pub.readingOrder.items);
153
162
 
154
163
  this._contentProtection = configuration.contentProtection || {};
164
+ this._decoratorConfig = configuration.decoratorConfig || {};
155
165
 
156
166
  // Merge keyboard peripherals
157
167
  this._keyboardPeripherals = this.mergeKeyboardPeripherals(
@@ -421,11 +431,19 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
421
431
  * to trigger the navigator when user's mouse/keyboard focus is
422
432
  * outside the readium-controller navigator. Be careful!
423
433
  */
424
- public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown) {
434
+ public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown, sourceFrame?: FrameManager | FXLFrameManager) {
425
435
  switch (key) {
426
436
  case "_pong":
427
437
  this.listeners.frameLoaded(this._cframes[0]!.iframe.contentWindow!);
428
438
  this.listeners.positionChanged(this.currentLocation);
439
+ if (sourceFrame) {
440
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
441
+ const i = frames.indexOf(sourceFrame);
442
+ const href = i >= 0 ? this.viewport.readingOrder[i] : undefined;
443
+ if (href) this._reapplyDecorationsToFrame(sourceFrame, href);
444
+ } else {
445
+ this._reapplyDecorationsToCurrentFrames();
446
+ }
429
447
  break;
430
448
  case "first_visible_locator":
431
449
  const loc = Locator.deserialize(data as string);
@@ -439,11 +457,31 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
439
457
  });
440
458
  this.listeners.positionChanged(this.currentLocation);
441
459
  break;
442
- case "text_selected":
443
- this.listeners.textSelected(data as BasicTextSelection);
460
+ case "text_selected": {
461
+ const selection = data as BasicTextSelection;
462
+ if (sourceFrame) {
463
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
464
+ const i = frames.indexOf(sourceFrame);
465
+ const href = i >= 0 ? this.viewport.readingOrder[i] : undefined;
466
+ if (href) {
467
+ const link = this.pub.readingOrder.findWithHref(href);
468
+ selection.locator = new Locator({ href, type: link!.type || "application/xhtml+xml", text: new LocatorText({ highlight: selection.text }) });
469
+ }
470
+ }
471
+ this.listeners.textSelected(selection);
444
472
  break;
473
+ }
474
+ case "decoration_activated": {
475
+ const handled = this._handleDecorationActivated(data as DecorationActivatedEvent);
476
+ if (handled) this._decorationActivationConsumed = true;
477
+ break;
478
+ }
445
479
  case "click":
446
480
  case "tap":
481
+ if (this._decorationActivationConsumed) {
482
+ this._decorationActivationConsumed = false;
483
+ break;
484
+ }
447
485
  const edata = data as FrameClickEvent;
448
486
  if (edata.interactiveElement) {
449
487
  const element = new DOMParser().parseFromString(
@@ -589,10 +627,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
589
627
  if(vframes.length === 0) throw Error("no cframe to attach listener to");
590
628
  vframes.forEach(f => {
591
629
  if(f.msg) f.msg.listener = (key: CommsEventKey | ManagerEventKey, value: unknown) => {
592
- this.eventListener(key, value);
630
+ this.eventListener(key, value, f);
593
631
  }
594
632
  })
595
-
633
+ this._reapplyDecorationsToCurrentFrames();
596
634
  }
597
635
 
598
636
  private async apply() {
@@ -605,6 +643,141 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
605
643
  throw Error("Link for " + this.currentLocation.href + " not found!");
606
644
  }
607
645
 
646
+ // DecorableNavigator
647
+
648
+ public supportsDecorationStyle(styleTypeId: string): boolean {
649
+ return BUILTIN_DECORATION_TYPES.has(styleTypeId) ||
650
+ !!this._decoratorConfig.decorationTemplates?.[styleTypeId];
651
+ }
652
+
653
+ public registerDecorationObserver(group: string, observer: DecorationObserver): void {
654
+ if (!this._decorationObservers.has(group))
655
+ this._decorationObservers.set(group, new Set());
656
+ this._decorationObservers.get(group)!.add(observer);
657
+
658
+ // Store activation state and send to current frames
659
+ this._decorationActivationState.set(group, true);
660
+ this._sendDecorationActivationToFrames(group, true);
661
+ }
662
+
663
+ public unregisterDecorationObserver(observer: DecorationObserver): void {
664
+ this._decorationObservers.forEach((set, group) => {
665
+ if (set.has(observer)) {
666
+ set.delete(observer);
667
+ if (set.size === 0) {
668
+ this._decorationActivationState.delete(group);
669
+ this._sendDecorationActivationToFrames(group, false);
670
+ }
671
+ }
672
+ });
673
+ }
674
+
675
+ private _sendDecorationActivationToFrames(group: string, activatable: boolean): void {
676
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
677
+ frames.forEach(f => {
678
+ if (f.msg) f.msg.send("decoration_activatable", { group, activatable });
679
+ });
680
+ }
681
+
682
+ public applyDecorations(decorations: Decoration[], group: string): void {
683
+ const previous = this._decorations.get(group) ?? [];
684
+ const prevById = new Map(previous.map(d => [d.id, d]));
685
+ const nextById = new Map(decorations.map(d => [d.id, d]));
686
+
687
+ const toRemove: string[] = [];
688
+ const toAdd: Decoration[] = [];
689
+ const toUpdate: Decoration[] = [];
690
+
691
+ for (const [id, prev] of prevById) {
692
+ if (!nextById.has(id)) toRemove.push(id);
693
+ else if (!decorationsEqual(prev, nextById.get(id)!)) toUpdate.push(nextById.get(id)!);
694
+ }
695
+ for (const [id, next] of nextById) {
696
+ if (!prevById.has(id)) toAdd.push(next);
697
+ }
698
+
699
+ this._decorations.set(group, decorations);
700
+ this._sendDecorationOps(group, toRemove, toAdd, toUpdate, previous);
701
+ // Resend activation state after ops: FIFO postMessage guarantees the group
702
+ // exists in the injectable by the time this arrives, so it won't be dropped.
703
+ // Fixes the case where registerDecorationObserver was called before the group
704
+ // was created (activation sent before first decorate:add → silently dropped).
705
+ const activatable = this._decorationActivationState.get(group);
706
+ if (activatable !== undefined) {
707
+ this._sendDecorationActivationToFrames(group, activatable);
708
+ }
709
+ }
710
+
711
+ private _sendDecorationOps(
712
+ group: string,
713
+ toRemove: string[],
714
+ toAdd: Decoration[],
715
+ toUpdate: Decoration[],
716
+ previous: Decoration[]
717
+ ): void {
718
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
719
+ const prevById = new Map(previous.map(d => [d.id, d]));
720
+ const visibleHrefs = this.viewport.readingOrder;
721
+
722
+ frames.forEach((frame, i) => {
723
+ if (!frame.msg) return;
724
+ const href = visibleHrefs[i];
725
+ if (!href) return;
726
+ for (const id of toRemove) {
727
+ const d = prevById.get(id);
728
+ if (!d || d.locator.href !== href) continue;
729
+ frame.msg.send("decorate", { group, action: "remove", decoration: { id } });
730
+ }
731
+ for (const d of toAdd) {
732
+ if (d.locator.href !== href) continue;
733
+ frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
734
+ }
735
+ for (const d of toUpdate) {
736
+ if (d.locator.href !== href) continue;
737
+ frame.msg.send("decorate", { group, action: "update", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
738
+ }
739
+ });
740
+ }
741
+
742
+ private _reapplyDecorationsToFrame(frame: FrameManager | FXLFrameManager, href: string): void {
743
+ if (!frame.msg) return;
744
+ for (const [group, decorations] of this._decorations) {
745
+ const matching = decorations.filter(d => d.locator.href === href);
746
+ if (matching.length === 0) continue;
747
+ frame.msg.send("decorate", { group, action: "clear" });
748
+ for (const d of matching)
749
+ frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
750
+ }
751
+ for (const [group, activatable] of this._decorationActivationState) {
752
+ frame.msg.send("decoration_activatable", { group, activatable });
753
+ }
754
+ }
755
+
756
+ private _reapplyDecorationsToCurrentFrames(): void {
757
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
758
+ const visibleHrefs = this.viewport.readingOrder;
759
+ frames.forEach((frame, i) => {
760
+ const href = visibleHrefs[i];
761
+ if (href) this._reapplyDecorationsToFrame(frame, href);
762
+ });
763
+ }
764
+
765
+ private _handleDecorationActivated(data: DecorationActivatedEvent): boolean {
766
+ const observers = this._decorationObservers.get(data.group);
767
+ if (!observers || observers.size === 0) return false;
768
+
769
+ const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId);
770
+ if (!decoration) return false;
771
+
772
+ const event: DecorationActivationEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
773
+ let anyHandled = false;
774
+ for (const obs of observers)
775
+ if (obs.onDecorationActivated(event)) anyHandled = true;
776
+ return anyHandled;
777
+ }
778
+
779
+ // End of Decoration
780
+
608
781
  public async destroy() {
609
782
  if (this._suspiciousActivityListener) {
610
783
  window.removeEventListener(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, this._suspiciousActivityListener);
@@ -615,6 +788,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
615
788
  this._navigatorProtector?.destroy();
616
789
  this._keyboardPeripheralsManager?.destroy();
617
790
  await this.framePool?.destroy();
791
+ this._decorations.clear();
792
+ this._decorationObservers.clear();
793
+ this._decorationActivationState.clear();
618
794
  }
619
795
 
620
796
  private async changeResource(relative: number): Promise<boolean> {
@@ -1,7 +1,7 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { FrameComms } from "./FrameComms.ts";
3
3
  import type { ReadiumWindow } from "../../../../navigator-html-injectables/types/src/helpers/dom";
4
- import { sML } from "../../helpers/index.ts";
4
+ import { sML } from "@readium/navigator-html-injectables";
5
5
  import type { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
7
7
 
@@ -1,4 +1,4 @@
1
- import { sML } from "../../helpers/sML.ts";
1
+ import { sML } from "@readium/navigator-html-injectables";
2
2
 
3
3
  export interface Point {
4
4
  X: number;
@@ -19,7 +19,7 @@ import {
19
19
  withFallback
20
20
  } from "../../preferences/guards.ts";
21
21
 
22
- import { sMLWithRequest } from "../../helpers/index.ts";
22
+ import { sMLWithRequest } from "@readium/navigator-html-injectables";
23
23
 
24
24
  export interface IEpubDefaults {
25
25
  backgroundColor?: string | null,
@@ -3,7 +3,7 @@ import { ExperimentKey, TextAlignment } from "../../preferences/Types.ts";
3
3
  import { EpubDefaults } from "./EpubDefaults.ts";
4
4
  import { EpubPreferences } from "./EpubPreferences.ts";
5
5
 
6
- import { sMLWithRequest } from "../../helpers/index.ts";
6
+ import { sMLWithRequest } from "@readium/navigator-html-injectables";
7
7
 
8
8
  export interface IEpubSettings {
9
9
  backgroundColor?: string | null,
@@ -1,3 +1,2 @@
1
1
  export * from "./lineLength.ts";
2
- export * from './sML.ts';
3
2
  export * from './scriptMode.ts';
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './Navigator.ts';
2
+ export * from './decorations/index.ts';
2
3
  export * from './webpub/index.ts';
3
4
  export * from './epub/index.ts';
4
5
  export * from './audio/index.ts';
@@ -1,4 +1,4 @@
1
- import { sML } from "../helpers/sML.ts";
1
+ import { sML } from "@readium/navigator-html-injectables";
2
2
  import { WorkerConsole } from "./utils/WorkerConsole.ts";
3
3
  import { log, table, clear } from "./utils/console.ts";
4
4
  import { isBrave } from "./utils/platform.ts";
@@ -1,7 +1,7 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { FrameComms } from "../epub/frame/FrameComms.ts";
3
3
  import type { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom";
4
- import { sML } from "../helpers/index.ts";
4
+ import { sML } from "@readium/navigator-html-injectables";
5
5
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../peripherals/KeyboardConditionBridge.ts";
7
7