@edrlab/thorium-web 1.1.6 → 1.2.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.
Files changed (77) hide show
  1. package/dist/{ThPreferencesAdapter-B8AYujCA.d.mts → ThPreferencesAdapter-DrZ5_6Dv.d.mts} +4 -139
  2. package/dist/{ThSettingsWrapper-BlmAxcWF.d.mts → ThSettingsWrapper-8Kx0SnH4.d.mts} +1 -1
  3. package/dist/{actions-BRtHsxpm.d.mts → actions-D2CHvCHu.d.mts} +2 -2
  4. package/dist/{actionsReducer-bT0pfxLJ.d.mts → actionsReducer-kc-S130w.d.mts} +3 -24
  5. package/dist/chunk-34MVY33F.mjs +913 -0
  6. package/dist/chunk-34MVY33F.mjs.map +1 -0
  7. package/dist/{chunk-KJXHAJYK.mjs → chunk-4VHEHMJN.mjs} +128 -11
  8. package/dist/chunk-4VHEHMJN.mjs.map +1 -0
  9. package/dist/{chunk-QNYZCQKO.mjs → chunk-72XCX5TD.mjs} +4 -4
  10. package/dist/chunk-72XCX5TD.mjs.map +1 -0
  11. package/dist/{chunk-SMK7A7VF.mjs → chunk-7NEQAW7J.mjs} +3 -3
  12. package/dist/chunk-7NEQAW7J.mjs.map +1 -0
  13. package/dist/{chunk-SFHOVH6R.mjs → chunk-H4J2VY7Z.mjs} +201 -35
  14. package/dist/chunk-H4J2VY7Z.mjs.map +1 -0
  15. package/dist/{chunk-COQXDRKD.mjs → chunk-K3K7TUWM.mjs} +2 -2
  16. package/dist/chunk-K3K7TUWM.mjs.map +1 -0
  17. package/dist/{chunk-KTPIH35Y.mjs → chunk-NYZBHYW2.mjs} +5 -3
  18. package/dist/chunk-NYZBHYW2.mjs.map +1 -0
  19. package/dist/chunk-RRDEPGBK.mjs +75 -0
  20. package/dist/chunk-RRDEPGBK.mjs.map +1 -0
  21. package/dist/chunk-S4M7ED5Q.mjs +407 -0
  22. package/dist/chunk-S4M7ED5Q.mjs.map +1 -0
  23. package/dist/components/Epub/index.d.mts +17 -11
  24. package/dist/components/Epub/index.mjs +12 -831
  25. package/dist/components/Epub/index.mjs.map +1 -1
  26. package/dist/components/Misc/index.css +23 -0
  27. package/dist/components/Misc/index.css.map +1 -1
  28. package/dist/components/Misc/index.d.mts +10 -1
  29. package/dist/components/Misc/index.mjs +30 -2
  30. package/dist/components/Misc/index.mjs.map +1 -1
  31. package/dist/components/Reader/index.css +1283 -0
  32. package/dist/components/Reader/index.css.map +1 -0
  33. package/dist/components/Reader/index.d.mts +30 -0
  34. package/dist/components/Reader/index.mjs +79 -0
  35. package/dist/components/Reader/index.mjs.map +1 -0
  36. package/dist/components/WebPub/index.d.mts +15 -31
  37. package/dist/components/WebPub/index.mjs +11 -374
  38. package/dist/components/WebPub/index.mjs.map +1 -1
  39. package/dist/core/Components/index.d.mts +11 -8
  40. package/dist/core/Components/index.mjs +1 -1
  41. package/dist/core/Helpers/index.d.mts +1 -1
  42. package/dist/core/Hooks/index.d.mts +54 -5
  43. package/dist/core/Hooks/index.mjs +1 -1
  44. package/dist/errorHandler-CL2YIfQY.d.mts +26 -0
  45. package/dist/i18n/index.mjs +4 -4
  46. package/dist/immer.d-CoRThNOF.d.mts +23 -0
  47. package/dist/lib/index.d.mts +12 -162
  48. package/dist/lib/index.mjs +2 -2
  49. package/dist/locales/el/thorium-web.json +31 -0
  50. package/dist/locales/en/thorium-web.json +14 -2
  51. package/dist/locales/et/thorium-web.json +6 -2
  52. package/dist/locales/fi/thorium-shared.json +14 -8
  53. package/dist/locales/fi/thorium-web.json +2 -1
  54. package/dist/locales/fr/thorium-shared.json +100 -0
  55. package/dist/locales/it/thorium-shared.json +9 -0
  56. package/dist/locales/lt/thorium-shared.json +22 -13
  57. package/dist/locales/lt/thorium-web.json +2 -1
  58. package/dist/locales/pt-PT/thorium-shared.json +64 -1
  59. package/dist/locales/pt-PT/thorium-web.json +4 -3
  60. package/dist/locales/sv/thorium-shared.json +61 -3
  61. package/dist/locales/sv/thorium-web.json +4 -3
  62. package/dist/preferences/index.d.mts +8 -7
  63. package/dist/preferences/index.mjs +3 -3
  64. package/dist/settingsReducer-C1wwCAMv.d.mts +159 -0
  65. package/dist/{ui-BVlJcPL_.d.mts → ui-CamWuqOo.d.mts} +1 -1
  66. package/dist/useContrast-D6sjPjxy.d.mts +152 -0
  67. package/dist/{useEpubNavigator-CsraD65e.d.mts → useEpubNavigator-CwHJfoiV.d.mts} +1 -1
  68. package/dist/{usePreferences-rfT368__.d.mts → usePreferences-BXFJbval.d.mts} +1 -1
  69. package/dist/{StatefulReader-CjgFzHoO.d.mts → useReaderTransitions-guT-eA-Q.d.mts} +50 -50
  70. package/package.json +5 -3
  71. package/dist/chunk-COQXDRKD.mjs.map +0 -1
  72. package/dist/chunk-KJXHAJYK.mjs.map +0 -1
  73. package/dist/chunk-KTPIH35Y.mjs.map +0 -1
  74. package/dist/chunk-QNYZCQKO.mjs.map +0 -1
  75. package/dist/chunk-SFHOVH6R.mjs.map +0 -1
  76. package/dist/chunk-SMK7A7VF.mjs.map +0 -1
  77. package/dist/useContrast-Cgsrl7GS.d.mts +0 -15
@@ -0,0 +1,913 @@
1
+ import { ThPluginRegistry, createDefaultPlugin, ThPluginProvider, useSpacingPresets, useFonts, useSettingsComponentStatus, usePositionStorage, useLineHeight, Peripherals, NavigatorProvider, thorium_web_reader_app_default, StatefulDockingWrapper, getReaderClassNames, StatefulReaderHeader, StatefulReaderFooter, useReaderTransitions, thorium_web_button_default } from './chunk-H4J2VY7Z.mjs';
2
+ import { makeBreakpointsMap, isActiveElement } from './chunk-47AIIJFO.mjs';
3
+ import { useFullscreen, useEpubNavigator, useTimeline, useDocumentTitle, useEpubSettingsCache } from './chunk-4VHEHMJN.mjs';
4
+ import { useAppSelector, useAppDispatch, setImmersive, setFullscreen, setTimeline, setHovering, toggleImmersive, setPublicationStart, setPublicationEnd, useAppStore, toggleActionOpen, setScrollAffordance, debounce, setDirection, setPlatformModifier, setUserNavigated, setTheme, setHasArrows, setLoading } from './chunk-K3K7TUWM.mjs';
5
+ import { usePreferenceKeys, usePreferences, resolveContentProtectionConfig, buildThemeObject } from './chunk-7NEQAW7J.mjs';
6
+ import { ThArrowVariant } from './chunk-XVSFXHYB.mjs';
7
+ import { getPlatformModifier } from './chunk-5LUMM7FW.mjs';
8
+ import { ThNavigationButton } from './chunk-NYZBHYW2.mjs';
9
+ import { usePrevious } from './chunk-YZ73DHRU.mjs';
10
+ import { useI18n } from './chunk-IYAFKTPL.mjs';
11
+ import { useState, useLayoutEffect, useRef, useEffect, useCallback, useMemo } from 'react';
12
+ import { I18nProvider } from 'react-aria';
13
+ import { Layout, Locator } from '@readium/shared';
14
+ import classNames2 from 'classnames';
15
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
16
+
17
+ // src/components/assets/styles/thorium-web.reader.paginatedArrow.module.css
18
+ var thorium_web_reader_paginatedArrow_default = {
19
+ container: "thorium_web_reader_paginatedArrow_container",
20
+ leftContainer: "thorium_web_reader_paginatedArrow_leftContainer",
21
+ rightContainer: "thorium_web_reader_paginatedArrow_rightContainer",
22
+ occupiesSpace: "thorium_web_reader_paginatedArrow_occupiesSpace",
23
+ visuallyHidden: "thorium_web_reader_paginatedArrow_visuallyHidden"
24
+ };
25
+ var usePaginatedArrows = () => {
26
+ const { preferences } = usePreferences();
27
+ const hasArrows = useAppSelector((state) => state.reader.hasArrows);
28
+ const isFXL = useAppSelector((state) => state.publication.isFXL);
29
+ const breakpoint = useAppSelector((state) => state.theming.breakpoint);
30
+ const {
31
+ isScroll,
32
+ fromImmersive,
33
+ toImmersive,
34
+ fromFullscreen,
35
+ toFullscreen,
36
+ fromScroll,
37
+ toUserNavigation
38
+ } = useReaderTransitions();
39
+ const dispatch = useAppDispatch();
40
+ const prefs = useMemo(
41
+ () => isFXL ? preferences.affordances.paginated.fxl : preferences.affordances.paginated.reflow,
42
+ [isFXL, preferences.affordances.paginated.fxl, preferences.affordances.paginated.reflow]
43
+ );
44
+ const prefsMap = useMemo(
45
+ () => makeBreakpointsMap({
46
+ defaultValue: prefs.default,
47
+ fromEnum: ThArrowVariant,
48
+ pref: prefs.breakpoints,
49
+ validateKey: "variant"
50
+ }),
51
+ [prefs.default, prefs.breakpoints]
52
+ );
53
+ const { variant, discard, hint } = useMemo(() => {
54
+ const result = prefsMap[breakpoint] || prefs.default;
55
+ if (isFXL) {
56
+ return {
57
+ ...result,
58
+ variant: "layered" /* layered */
59
+ };
60
+ }
61
+ return result;
62
+ }, [breakpoint, prefsMap, isFXL, prefs.default]);
63
+ const prevVariant = usePrevious(variant);
64
+ const prevDiscard = usePrevious(discard);
65
+ useEffect(() => {
66
+ if (!prevDiscard?.includes("navigation") && discard?.includes("navigation")) {
67
+ dispatch(setUserNavigated(false));
68
+ return;
69
+ }
70
+ if (discard === "none" && prevDiscard !== "none") {
71
+ dispatch(setHasArrows(true));
72
+ dispatch(setUserNavigated(false));
73
+ return;
74
+ }
75
+ if (prevVariant === "none" /* none */ && variant !== "none" /* none */) {
76
+ dispatch(setHasArrows(true));
77
+ dispatch(setUserNavigated(false));
78
+ return;
79
+ }
80
+ const shouldHide = discard?.includes("immersive") && toImmersive || discard?.includes("fullscreen") && toFullscreen || discard?.includes("navigation") && toUserNavigation;
81
+ const shouldShow = hint?.includes("immersiveChange") && fromImmersive || hint?.includes("fullscreenChange") && fromFullscreen || hint?.includes("layoutChange") && fromScroll;
82
+ if (shouldHide) {
83
+ dispatch(setHasArrows(false));
84
+ if (discard?.includes("navigation") && toUserNavigation) {
85
+ dispatch(setUserNavigated(false));
86
+ }
87
+ } else if (shouldShow) {
88
+ dispatch(setHasArrows(true));
89
+ }
90
+ }, [toImmersive, toFullscreen, toUserNavigation, fromImmersive, fromFullscreen, fromScroll, discard, hint, prevVariant, variant, prevDiscard, dispatch]);
91
+ if (variant === "none" /* none */ || isScroll) {
92
+ return {
93
+ isVisible: false,
94
+ occupySpace: false,
95
+ shouldTrackNavigation: false,
96
+ supportsVariant: !isFXL
97
+ };
98
+ }
99
+ return {
100
+ isVisible: hasArrows,
101
+ occupySpace: variant === "stacked" /* stacked */,
102
+ shouldTrackNavigation: Array.isArray(discard) && discard.includes("navigation"),
103
+ supportsVariant: !isFXL
104
+ };
105
+ };
106
+ var StatefulReaderArrowButton = ({
107
+ direction,
108
+ className,
109
+ isDisabled,
110
+ onPress,
111
+ ...props
112
+ }) => {
113
+ const { preferences } = usePreferences();
114
+ const { t } = useI18n();
115
+ const buttonRef = useRef(null);
116
+ const isRTL = useAppSelector((state) => state.publication.isRTL);
117
+ const hasArrows = useAppSelector((state) => state.reader.hasArrows);
118
+ const {
119
+ isVisible,
120
+ occupySpace
121
+ } = usePaginatedArrows();
122
+ const [isHovering, setIsHovering] = useState(false);
123
+ const label = direction === "right" && !isRTL || direction === "left" && isRTL ? t("reader.actions.goForward") : t("reader.actions.goBackward");
124
+ const handleClassNameFromState = () => {
125
+ let className2 = "";
126
+ if (!isVisible) {
127
+ className2 = thorium_web_reader_paginatedArrow_default.visuallyHidden;
128
+ }
129
+ return className2;
130
+ };
131
+ const handleClassNameFromSpaceProp = () => {
132
+ let className2 = "";
133
+ if (occupySpace) {
134
+ className2 = thorium_web_reader_paginatedArrow_default.occupiesSpace;
135
+ }
136
+ return className2;
137
+ };
138
+ useEffect(() => {
139
+ if ((isDisabled || !hasArrows && !isHovering) && isActiveElement(buttonRef.current)) {
140
+ buttonRef.current.blur();
141
+ }
142
+ });
143
+ const blurOnEsc = (event) => {
144
+ if (isActiveElement(buttonRef.current) && event.code === "Escape") {
145
+ buttonRef.current.blur();
146
+ }
147
+ };
148
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
149
+ ThNavigationButton,
150
+ {
151
+ direction,
152
+ ref: buttonRef,
153
+ "aria-label": label,
154
+ onPress,
155
+ onHoverChange: (isHovering2) => setIsHovering(isHovering2),
156
+ onKeyDown: blurOnEsc,
157
+ className: classNames2(className, handleClassNameFromSpaceProp(), handleClassNameFromState()),
158
+ isDisabled,
159
+ preventFocusOnPress: true,
160
+ ...props,
161
+ compounds: {
162
+ tooltipTrigger: {
163
+ delay: preferences.theming.arrow.tooltipDelay,
164
+ closeDelay: preferences.theming.arrow.tooltipDelay
165
+ },
166
+ tooltip: {
167
+ placement: direction === "left" ? "right" : "left",
168
+ className: thorium_web_button_default.tooltip
169
+ },
170
+ label
171
+ }
172
+ }
173
+ ) });
174
+ };
175
+ var useEpubStatelessCache = (textAlign, columnCount, fontFamily, fontSize, fontWeight, hyphens, letterSpacing, lineLength, lineHeight, paragraphIndent, paragraphSpacing, publisherStyles, scroll, textNormalization, wordSpacing, theme, positionsList, colorScheme, reducedMotion, layoutUI, isImmersive, isHovering, arrowsOccupySpace) => {
176
+ const settingsCache = useEpubSettingsCache(
177
+ textAlign,
178
+ columnCount,
179
+ fontFamily,
180
+ fontSize,
181
+ fontWeight,
182
+ hyphens,
183
+ letterSpacing,
184
+ lineLength,
185
+ lineHeight,
186
+ paragraphIndent,
187
+ paragraphSpacing,
188
+ publisherStyles,
189
+ scroll,
190
+ textNormalization,
191
+ wordSpacing,
192
+ theme
193
+ );
194
+ const cache = useRef({
195
+ layoutUI,
196
+ isImmersive,
197
+ isHovering,
198
+ arrowsOccupySpace: arrowsOccupySpace || false,
199
+ settings: settingsCache.current.settings,
200
+ positionsList,
201
+ colorScheme,
202
+ reducedMotion
203
+ });
204
+ const memoizedCache = useMemo(() => ({
205
+ layoutUI,
206
+ isImmersive,
207
+ isHovering,
208
+ arrowsOccupySpace: arrowsOccupySpace || false,
209
+ settings: settingsCache.current.settings,
210
+ positionsList,
211
+ colorScheme,
212
+ reducedMotion
213
+ }), [
214
+ layoutUI,
215
+ isImmersive,
216
+ isHovering,
217
+ arrowsOccupySpace,
218
+ settingsCache,
219
+ positionsList,
220
+ colorScheme,
221
+ reducedMotion
222
+ ]);
223
+ cache.current = memoizedCache;
224
+ return cache;
225
+ };
226
+ var useEpubPreferencesConfig = ({
227
+ isFXL,
228
+ settings,
229
+ colorScheme,
230
+ fontLanguage,
231
+ arrowsOccupySpace,
232
+ arrowsWidth,
233
+ preferences,
234
+ getFontMetadata,
235
+ lineHeightOptions,
236
+ fxlThemeKeys,
237
+ reflowThemeKeys
238
+ }) => {
239
+ const epubPreferences = useMemo(() => {
240
+ if (isFXL) return {};
241
+ const initialConstraint = arrowsOccupySpace ? arrowsWidth.current : 0;
242
+ const themeKeys = isFXL ? fxlThemeKeys : reflowThemeKeys;
243
+ const theme = settings.theme && themeKeys.includes(settings.theme) ? settings.theme : "auto";
244
+ const themeProps = buildThemeObject({
245
+ theme,
246
+ themeKeys: preferences.theming.themes.keys,
247
+ systemThemes: preferences.theming.themes.systemThemes,
248
+ colorScheme
249
+ });
250
+ return {
251
+ columnCount: settings.columnCount === "auto" ? null : Number(settings.columnCount),
252
+ constraint: initialConstraint,
253
+ fontFamily: getFontMetadata(settings.fontFamily[fontLanguage] ?? "")?.fontStack || null,
254
+ fontSize: settings.fontSize,
255
+ fontWeight: settings.fontWeight,
256
+ hyphens: settings.hyphens,
257
+ letterSpacing: settings.publisherStyles ? void 0 : settings.letterSpacing,
258
+ lineHeight: settings.publisherStyles ? void 0 : settings.lineHeight === null ? null : lineHeightOptions[settings.lineHeight],
259
+ optimalLineLength: settings.lineLength?.optimal != null ? settings.lineLength.optimal : void 0,
260
+ maximalLineLength: settings.lineLength?.max?.isDisabled ? null : settings.lineLength?.max?.chars != null ? settings.lineLength.max.chars : void 0,
261
+ minimalLineLength: settings.lineLength?.min?.isDisabled ? null : settings.lineLength?.min?.chars != null ? settings.lineLength.min.chars : void 0,
262
+ paragraphIndent: settings.publisherStyles ? void 0 : settings.paragraphIndent,
263
+ paragraphSpacing: settings.publisherStyles ? void 0 : settings.paragraphSpacing,
264
+ scroll: settings.scroll,
265
+ textAlign: settings.textAlign,
266
+ textNormalization: settings.textNormalization,
267
+ wordSpacing: settings.publisherStyles ? void 0 : settings.wordSpacing,
268
+ ...themeProps
269
+ };
270
+ }, [
271
+ isFXL,
272
+ arrowsOccupySpace,
273
+ arrowsWidth,
274
+ settings,
275
+ colorScheme,
276
+ fontLanguage,
277
+ preferences.theming.themes.keys,
278
+ preferences.theming.themes.systemThemes,
279
+ getFontMetadata,
280
+ lineHeightOptions,
281
+ fxlThemeKeys,
282
+ reflowThemeKeys
283
+ ]);
284
+ const epubDefaults = useMemo(() => {
285
+ if (isFXL) return {};
286
+ return {
287
+ maximalLineLength: preferences.typography.maximalLineLength,
288
+ minimalLineLength: preferences.typography.minimalLineLength,
289
+ optimalLineLength: preferences.typography.optimalLineLength,
290
+ pageGutter: preferences.typography.pageGutter,
291
+ scrollPaddingTop: preferences.theming.layout.ui?.reflow === "layered-ui" /* layered */ ? (preferences.theming.icon.size || 24) * 3 : preferences.theming.icon.size || 24,
292
+ scrollPaddingBottom: preferences.theming.layout.ui?.reflow === "layered-ui" /* layered */ ? (preferences.theming.icon.size || 24) * 5 : preferences.theming.icon.size || 24,
293
+ scrollPaddingLeft: preferences.typography.pageGutter,
294
+ scrollPaddingRight: preferences.typography.pageGutter,
295
+ experiments: preferences.experiments?.reflow || null
296
+ };
297
+ }, [isFXL, preferences]);
298
+ return { epubPreferences, epubDefaults };
299
+ };
300
+ var useEpubInjectablesConfig = ({
301
+ isFXL,
302
+ isFontFamilyUsed,
303
+ fontLanguage,
304
+ getFontInjectables,
305
+ getAndroidFXLPatch
306
+ }) => {
307
+ const injectables = useMemo(() => {
308
+ let injectablesConfig;
309
+ if (isFXL) {
310
+ const androidPatch = getAndroidFXLPatch();
311
+ if (androidPatch) {
312
+ injectablesConfig = {
313
+ allowedDomains: [window.location.origin],
314
+ rules: [{
315
+ resources: [/\.xhtml$/, /\.html$/],
316
+ prepend: [androidPatch]
317
+ }]
318
+ };
319
+ }
320
+ }
321
+ if (!isFXL && isFontFamilyUsed) {
322
+ const fontResources = getFontInjectables({ language: fontLanguage });
323
+ if (fontResources) {
324
+ injectablesConfig = {
325
+ allowedDomains: fontResources.allowedDomains,
326
+ rules: [{
327
+ resources: [/\.xhtml$/, /\.html$/],
328
+ prepend: fontResources.prepend,
329
+ append: fontResources.append
330
+ }]
331
+ };
332
+ }
333
+ }
334
+ return injectablesConfig;
335
+ }, [isFXL, isFontFamilyUsed, fontLanguage, getFontInjectables, getAndroidFXLPatch]);
336
+ return { injectables };
337
+ };
338
+
339
+ // src/components/Epub/Hooks/useReaderInit.ts
340
+ var useEpubReaderInit = ({
341
+ container,
342
+ publication,
343
+ positionsList,
344
+ initialPosition,
345
+ listeners,
346
+ preferences,
347
+ cache,
348
+ isFontFamilyUsed,
349
+ fontLanguage,
350
+ getFontMetadata,
351
+ injectFontResources,
352
+ removeFontResources,
353
+ getAndroidFXLPatch,
354
+ getFontInjectables,
355
+ fxlThemeKeys,
356
+ reflowThemeKeys,
357
+ lineHeightOptions,
358
+ arrowsOccupySpace,
359
+ arrowsWidth,
360
+ colorScheme,
361
+ isFXL,
362
+ contentProtectionConfig,
363
+ onNavigatorReady,
364
+ onNavigatorLoaded,
365
+ onCleanup,
366
+ fxlProgressionCallback
367
+ }) => {
368
+ const [navigatorReady, setNavigatorReady] = useState(false);
369
+ const { epubPreferences, epubDefaults } = useEpubPreferencesConfig({
370
+ isFXL,
371
+ settings: cache.current.settings,
372
+ colorScheme,
373
+ fontLanguage,
374
+ arrowsOccupySpace,
375
+ arrowsWidth,
376
+ preferences,
377
+ getFontMetadata,
378
+ lineHeightOptions,
379
+ fxlThemeKeys,
380
+ reflowThemeKeys
381
+ });
382
+ const { injectables } = useEpubInjectablesConfig({
383
+ isFXL,
384
+ isFontFamilyUsed,
385
+ fontLanguage,
386
+ getFontInjectables,
387
+ getAndroidFXLPatch
388
+ });
389
+ const handleCleanup = useCallback(() => {
390
+ if (!isFXL) removeFontResources();
391
+ onCleanup?.();
392
+ }, [isFXL, removeFontResources, onCleanup]);
393
+ const { EpubNavigatorLoad, EpubNavigatorDestroy } = useEpubNavigator();
394
+ const isNavigatorLoadedEpub = useRef(false);
395
+ useEffect(() => {
396
+ if (!publication || isNavigatorLoadedEpub.current) return;
397
+ if (!container.current) {
398
+ console.error("Container ref is not available for navigator initialization");
399
+ return;
400
+ }
401
+ const config = {
402
+ container: container.current,
403
+ publication,
404
+ listeners,
405
+ positionsList: positionsList?.map((loc) => new Locator(loc)) || [],
406
+ initialPosition: initialPosition ? new Locator(initialPosition) : void 0,
407
+ preferences: epubPreferences,
408
+ defaults: epubDefaults,
409
+ injectables: injectables || void 0,
410
+ contentProtection: contentProtectionConfig
411
+ };
412
+ isNavigatorLoadedEpub.current = true;
413
+ onNavigatorReady?.();
414
+ EpubNavigatorLoad(config, () => {
415
+ setNavigatorReady(true);
416
+ onNavigatorLoaded?.();
417
+ }, fxlProgressionCallback);
418
+ return () => {
419
+ if (isNavigatorLoadedEpub.current) {
420
+ setNavigatorReady(false);
421
+ EpubNavigatorDestroy(() => {
422
+ isNavigatorLoadedEpub.current = false;
423
+ handleCleanup();
424
+ });
425
+ }
426
+ };
427
+ }, []);
428
+ useEffect(() => {
429
+ if (!isFXL && isFontFamilyUsed) {
430
+ const fontResources = getFontInjectables({ language: fontLanguage });
431
+ if (fontResources) {
432
+ injectFontResources(fontResources);
433
+ }
434
+ }
435
+ }, [isFXL, isFontFamilyUsed, fontLanguage, injectFontResources, getFontInjectables]);
436
+ return {
437
+ navigatorReady,
438
+ isFXL
439
+ };
440
+ };
441
+ var StatefulReader = ({
442
+ publication,
443
+ localDataKey,
444
+ plugins,
445
+ positionStorage
446
+ }) => {
447
+ const [pluginsRegistered, setPluginsRegistered] = useState(false);
448
+ useLayoutEffect(() => {
449
+ if (plugins && plugins.length > 0) {
450
+ plugins.forEach((plugin) => {
451
+ ThPluginRegistry.register(plugin);
452
+ });
453
+ } else {
454
+ ThPluginRegistry.register(createDefaultPlugin());
455
+ }
456
+ setPluginsRegistered(true);
457
+ }, [plugins]);
458
+ if (!pluginsRegistered) {
459
+ return null;
460
+ }
461
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ThPluginProvider, { children: /* @__PURE__ */ jsx(StatefulReaderInner, { publication, localDataKey, positionStorage }) }) });
462
+ };
463
+ var StatefulReaderInner = ({ publication, localDataKey, positionStorage }) => {
464
+ const { fxlActionKeys, fxlThemeKeys, reflowActionKeys, reflowThemeKeys } = usePreferenceKeys();
465
+ const { preferences, getFontMetadata, getFontInjectables } = usePreferences();
466
+ const { t } = useI18n();
467
+ const { getEffectiveSpacingValue } = useSpacingPresets();
468
+ const { occupySpace: arrowsOccupySpace } = usePaginatedArrows();
469
+ const { injectFontResources, removeFontResources, getAndroidFXLPatch } = useFonts();
470
+ const container = useRef(null);
471
+ const arrowsWidth = useRef(2 * ((preferences.theming.arrow.size || 40) + (preferences.theming.arrow.offset || 0)));
472
+ const isFXL = useAppSelector((state) => state.publication.isFXL);
473
+ const positionsList = useAppSelector((state) => state.publication.positionsList);
474
+ const fontLanguage = useAppSelector((state) => state.publication.fontLanguage);
475
+ const { isComponentUsed: isFontFamilyUsed } = useSettingsComponentStatus({
476
+ settingsKey: "fontFamily" /* fontFamily */,
477
+ publicationType: isFXL ? "fxl" : "reflow",
478
+ componentType: "text"
479
+ });
480
+ const textAlign = useAppSelector((state) => state.settings.textAlign);
481
+ const columnCount = useAppSelector((state) => state.settings.columnCount);
482
+ const fontFamily = useAppSelector((state) => state.settings.fontFamily);
483
+ const fontSize = useAppSelector((state) => state.settings.fontSize);
484
+ const fontWeight = useAppSelector((state) => state.settings.fontWeight);
485
+ const hyphens = useAppSelector((state) => state.settings.hyphens);
486
+ const letterSpacing = getEffectiveSpacingValue("letterSpacing" /* letterSpacing */);
487
+ const lineLength = useAppSelector((state) => state.settings.lineLength);
488
+ const lineHeight = getEffectiveSpacingValue("lineHeight" /* lineHeight */);
489
+ const paragraphIndent = getEffectiveSpacingValue("paragraphIndent" /* paragraphIndent */);
490
+ const paragraphSpacing = getEffectiveSpacingValue("paragraphSpacing" /* paragraphSpacing */);
491
+ const publisherStyles = useAppSelector((state) => state.settings.publisherStyles);
492
+ const scroll = useAppSelector((state) => state.settings.scroll);
493
+ const isScroll = scroll && !isFXL;
494
+ const textNormalization = useAppSelector((state) => state.settings.textNormalization);
495
+ const wordSpacing = getEffectiveSpacingValue("wordSpacing" /* wordSpacing */);
496
+ const themeObject = useAppSelector((state) => state.theming.theme);
497
+ const theme = isFXL ? themeObject.fxl : themeObject.reflow;
498
+ const previousTheme = usePrevious(theme);
499
+ const colorScheme = useAppSelector((state) => state.theming.colorScheme);
500
+ const reducedMotion = useAppSelector((state) => state.theming.prefersReducedMotion);
501
+ const breakpoint = useAppSelector((state) => state.theming.breakpoint);
502
+ const isImmersive = useAppSelector((state) => state.reader.isImmersive);
503
+ const isHovering = useAppSelector((state) => state.reader.isHovering);
504
+ const layoutUI = isFXL ? preferences.theming.layout.ui?.fxl || "layered-ui" /* layered */ : isScroll ? preferences.theming.layout.ui?.reflow || "layered-ui" /* layered */ : "stacked-ui" /* stacked */;
505
+ const cache = useEpubStatelessCache(
506
+ textAlign,
507
+ columnCount,
508
+ fontFamily,
509
+ fontSize,
510
+ fontWeight,
511
+ hyphens,
512
+ letterSpacing,
513
+ lineLength,
514
+ lineHeight,
515
+ paragraphIndent,
516
+ paragraphSpacing,
517
+ publisherStyles,
518
+ isScroll,
519
+ textNormalization,
520
+ wordSpacing,
521
+ theme,
522
+ positionsList,
523
+ colorScheme,
524
+ reducedMotion,
525
+ layoutUI,
526
+ isImmersive,
527
+ isHovering,
528
+ arrowsOccupySpace
529
+ );
530
+ const atPublicationStart = useAppSelector((state) => state.publication.atPublicationStart);
531
+ const atPublicationEnd = useAppSelector((state) => state.publication.atPublicationEnd);
532
+ const dispatch = useAppDispatch();
533
+ useEffect(() => {
534
+ dispatch(setImmersive(false));
535
+ }, [isScroll, dispatch]);
536
+ const onFsChange = useCallback((isFullscreen) => {
537
+ dispatch(setFullscreen(isFullscreen));
538
+ }, [dispatch]);
539
+ const fs = useFullscreen(onFsChange);
540
+ const epubNavigator = useEpubNavigator();
541
+ const {
542
+ goLeft,
543
+ goRight,
544
+ goBackward,
545
+ goForward,
546
+ navLayout,
547
+ currentLocator,
548
+ currentPositions,
549
+ canGoBackward,
550
+ canGoForward,
551
+ isScrollStart,
552
+ isScrollEnd,
553
+ getCframes,
554
+ submitPreferences
555
+ } = epubNavigator;
556
+ const { setLocalData, getLocalData, localData } = usePositionStorage(localDataKey, positionStorage);
557
+ const timeline = useTimeline({
558
+ publication,
559
+ currentLocation: localData,
560
+ currentPositions: currentPositions() || [],
561
+ positionsList,
562
+ onChange: (timeline2) => {
563
+ dispatch(setTimeline(timeline2));
564
+ }
565
+ });
566
+ const lineHeightOptions = useLineHeight();
567
+ const documentTitleFormat = preferences.metadata?.documentTitle?.format;
568
+ let documentTitle;
569
+ if (documentTitleFormat) {
570
+ if (typeof documentTitleFormat === "object" && "key" in documentTitleFormat) {
571
+ const translatedTitle = t(documentTitleFormat.key);
572
+ documentTitle = translatedTitle !== documentTitleFormat.key ? translatedTitle : documentTitleFormat.fallback;
573
+ } else {
574
+ switch (documentTitleFormat) {
575
+ case "title" /* title */:
576
+ documentTitle = timeline?.title;
577
+ break;
578
+ case "chapter" /* chapter */:
579
+ documentTitle = timeline?.progression?.currentChapter;
580
+ break;
581
+ case "titleAndChapter" /* titleAndChapter */:
582
+ if (timeline?.title && timeline?.progression?.currentChapter) {
583
+ documentTitle = `${timeline.title} \u2013 ${timeline.progression.currentChapter}`;
584
+ }
585
+ break;
586
+ case "none" /* none */:
587
+ documentTitle = void 0;
588
+ break;
589
+ default:
590
+ documentTitle = documentTitleFormat;
591
+ break;
592
+ }
593
+ }
594
+ }
595
+ useDocumentTitle(documentTitle);
596
+ const activateImmersiveOnAction = useCallback(() => {
597
+ if (!cache.current.isImmersive) dispatch(setImmersive(true));
598
+ }, [cache, dispatch]);
599
+ const toggleIsImmersive = useCallback(() => {
600
+ dispatch(setHovering(false));
601
+ dispatch(toggleImmersive());
602
+ }, [dispatch]);
603
+ const handleTap = useCallback((event) => {
604
+ const _cframes = getCframes();
605
+ if (_cframes) {
606
+ if (!cache.current.settings.scroll) {
607
+ const oneQuarter = (_cframes.length === 2 ? _cframes[0].window.innerWidth + _cframes[1].window.innerWidth : _cframes[0].window.innerWidth) * window.devicePixelRatio / 4;
608
+ const navigationCallback = () => {
609
+ dispatch(setUserNavigated(true));
610
+ activateImmersiveOnAction();
611
+ };
612
+ if (event.x < oneQuarter) {
613
+ goLeft(!cache.current.reducedMotion, navigationCallback);
614
+ } else if (event.x > oneQuarter * 3) {
615
+ goRight(!cache.current.reducedMotion, navigationCallback);
616
+ } else if (oneQuarter <= event.x && event.x <= oneQuarter * 3) {
617
+ toggleIsImmersive();
618
+ }
619
+ } else {
620
+ if (preferences.affordances.scroll.toggleOnMiddlePointer.includes("tap")) {
621
+ toggleIsImmersive();
622
+ }
623
+ }
624
+ }
625
+ }, [getCframes, cache, preferences.affordances.scroll, goLeft, goRight, dispatch, activateImmersiveOnAction, toggleIsImmersive]);
626
+ const handleClick = useCallback((event) => {
627
+ if (cache.current.layoutUI === "layered-ui" /* layered */ && (!cache.current.settings.scroll || preferences.affordances.scroll.toggleOnMiddlePointer.includes("click"))) {
628
+ toggleIsImmersive();
629
+ }
630
+ }, [cache, preferences.affordances.scroll, toggleIsImmersive]);
631
+ const updatePublicationNavigationState = useCallback(() => {
632
+ if (canGoBackward()) {
633
+ dispatch(setPublicationStart(false));
634
+ } else {
635
+ dispatch(setPublicationStart(true));
636
+ }
637
+ if (canGoForward()) {
638
+ dispatch(setPublicationEnd(false));
639
+ } else {
640
+ dispatch(setPublicationEnd(true));
641
+ }
642
+ }, [canGoBackward, canGoForward, dispatch]);
643
+ const handleFXLProgression = useCallback((locator) => {
644
+ setLocalData(locator);
645
+ updatePublicationNavigationState();
646
+ }, [setLocalData, updatePublicationNavigationState]);
647
+ const initReadingEnv = useCallback(async () => {
648
+ if (navLayout() === Layout.fixed) {
649
+ const cLoc = currentLocator();
650
+ if (cLoc) {
651
+ handleFXLProgression(cLoc);
652
+ }
653
+ }
654
+ }, [navLayout, currentLocator, handleFXLProgression]);
655
+ const appStore = useAppStore();
656
+ const p = useMemo(() => new Peripherals(appStore, preferences.actions, {
657
+ moveTo: (direction) => {
658
+ const navigationCallback = () => {
659
+ dispatch(setUserNavigated(true));
660
+ activateImmersiveOnAction();
661
+ };
662
+ switch (direction) {
663
+ case "right":
664
+ if (!cache.current.settings.scroll) {
665
+ goRight(!cache.current.reducedMotion, navigationCallback);
666
+ }
667
+ break;
668
+ case "left":
669
+ if (!cache.current.settings.scroll) {
670
+ goLeft(!cache.current.reducedMotion, navigationCallback);
671
+ }
672
+ break;
673
+ }
674
+ },
675
+ goProgression: (shiftKey) => {
676
+ if (!cache.current.settings?.scroll) {
677
+ const callback = () => {
678
+ dispatch(setUserNavigated(true));
679
+ activateImmersiveOnAction();
680
+ };
681
+ shiftKey ? goBackward(!cache.current.reducedMotion, callback) : goForward(!cache.current.reducedMotion, callback);
682
+ }
683
+ },
684
+ toggleAction: (actionKey) => {
685
+ switch (actionKey) {
686
+ case "fullscreen" /* fullscreen */:
687
+ fs.handleFullscreen();
688
+ break;
689
+ case "settings" /* settings */:
690
+ case "toc" /* toc */:
691
+ dispatch(toggleActionOpen({
692
+ key: actionKey
693
+ }));
694
+ break;
695
+ }
696
+ }
697
+ }), [appStore, preferences.actions, dispatch, activateImmersiveOnAction, cache, goRight, goLeft, goBackward, goForward, fs]);
698
+ const listeners = useMemo(() => ({
699
+ frameLoaded: async function(_wnd) {
700
+ await initReadingEnv();
701
+ const _cframes = getCframes();
702
+ _cframes?.forEach(
703
+ (frameManager) => {
704
+ if (frameManager) p.observe(frameManager.window);
705
+ }
706
+ );
707
+ p.observe(window);
708
+ },
709
+ positionChanged: async function(locator) {
710
+ if (navLayout() !== Layout.fixed) {
711
+ const debouncedHandleProgression = debounce(
712
+ async () => {
713
+ setLocalData(locator);
714
+ updatePublicationNavigationState();
715
+ },
716
+ 250
717
+ );
718
+ debouncedHandleProgression();
719
+ }
720
+ },
721
+ tap: function(_e) {
722
+ handleTap(_e);
723
+ return true;
724
+ },
725
+ click: function(_e) {
726
+ handleClick(_e);
727
+ return true;
728
+ },
729
+ zoom: function(_scale) {
730
+ },
731
+ miscPointer: function(_amount) {
732
+ },
733
+ scroll: function(_delta) {
734
+ if (cache.current.settings.scroll && navLayout() !== Layout.fixed) {
735
+ if (isScrollStart() || isScrollEnd()) {
736
+ if (
737
+ // Keep consistent with pagination behavior
738
+ cache.current.layoutUI === "layered-ui" /* layered */
739
+ ) {
740
+ dispatch(setScrollAffordance(true));
741
+ }
742
+ } else if (!cache.current.isImmersive && _delta > 20) {
743
+ if (preferences.affordances.scroll.hideOnForwardScroll) {
744
+ dispatch(setImmersive(true));
745
+ }
746
+ } else if (cache.current.isImmersive && _delta < -20) {
747
+ if (
748
+ // Keep consistent with pagination behavior
749
+ cache.current.layoutUI === "layered-ui" /* layered */ && preferences.affordances.scroll.showOnBackwardScroll
750
+ ) {
751
+ dispatch(setImmersive(false));
752
+ }
753
+ }
754
+ }
755
+ },
756
+ customEvent: function(_key, _data) {
757
+ },
758
+ handleLocator: function(locator) {
759
+ const href = locator.href;
760
+ if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:") || href.startsWith("tel:")) {
761
+ if (confirm(`Open "${href}" ?`)) window.open(href, "_blank");
762
+ } else {
763
+ console.warn("Unhandled locator", locator);
764
+ }
765
+ return false;
766
+ },
767
+ textSelected: function(_selection) {
768
+ },
769
+ contentProtection: function(_type, _data) {
770
+ },
771
+ contextMenu: function(_data) {
772
+ },
773
+ peripheral: function(_data) {
774
+ }
775
+ }), [p, initReadingEnv, getCframes, navLayout, setLocalData, dispatch, handleTap, handleClick, cache, preferences.affordances.scroll, isScrollStart, isScrollEnd, updatePublicationNavigationState]);
776
+ const initialPosition = useMemo(() => getLocalData(), [getLocalData]);
777
+ const { navigatorReady } = useEpubReaderInit({
778
+ container,
779
+ publication,
780
+ positionsList,
781
+ initialPosition,
782
+ listeners,
783
+ preferences,
784
+ cache,
785
+ isFontFamilyUsed,
786
+ fontLanguage,
787
+ getFontMetadata,
788
+ injectFontResources,
789
+ removeFontResources,
790
+ getAndroidFXLPatch,
791
+ getFontInjectables,
792
+ fxlThemeKeys,
793
+ reflowThemeKeys,
794
+ lineHeightOptions,
795
+ arrowsOccupySpace,
796
+ arrowsWidth,
797
+ colorScheme,
798
+ isFXL,
799
+ contentProtectionConfig: resolveContentProtectionConfig(preferences.contentProtection, t),
800
+ onNavigatorReady: () => {
801
+ dispatch(setLoading(false));
802
+ },
803
+ onNavigatorLoaded: () => {
804
+ p.observe(window);
805
+ },
806
+ onCleanup: () => {
807
+ p.destroy();
808
+ },
809
+ fxlProgressionCallback: handleFXLProgression
810
+ });
811
+ const applyConstraint = useCallback(async (value) => {
812
+ await submitPreferences({
813
+ constraint: value
814
+ });
815
+ }, [submitPreferences]);
816
+ useLayoutEffect(() => {
817
+ if (!navigatorReady) return;
818
+ applyConstraint(arrowsOccupySpace ? arrowsWidth.current : 0).catch(console.error);
819
+ }, [arrowsOccupySpace, applyConstraint, navigatorReady]);
820
+ useLayoutEffect(() => {
821
+ if (!navigatorReady) return;
822
+ if (cache.current.colorScheme !== colorScheme) {
823
+ cache.current.colorScheme = colorScheme;
824
+ }
825
+ const theme2 = isFXL ? themeObject.fxl : themeObject.reflow;
826
+ if (theme2 !== "auto" && previousTheme !== theme2) return;
827
+ const applyCurrentTheme = async () => {
828
+ const themeKeys = isFXL ? fxlThemeKeys : reflowThemeKeys;
829
+ const themeKey = themeKeys.includes(theme2) ? theme2 : "auto";
830
+ const themeProps = buildThemeObject({
831
+ theme: themeKey,
832
+ themeKeys: preferences.theming.themes.keys,
833
+ systemThemes: preferences.theming.themes.systemThemes,
834
+ colorScheme
835
+ });
836
+ await submitPreferences(themeProps);
837
+ dispatch(setTheme({
838
+ key: isFXL ? "fxl" : "reflow",
839
+ value: themeKey
840
+ }));
841
+ };
842
+ applyCurrentTheme().catch(console.error);
843
+ }, [cache, themeObject, previousTheme, preferences.theming.themes, fxlThemeKeys, reflowThemeKeys, colorScheme, isFXL, submitPreferences, dispatch, navigatorReady]);
844
+ useLayoutEffect(() => {
845
+ preferences.direction && dispatch(setDirection(preferences.direction));
846
+ dispatch(setPlatformModifier(getPlatformModifier()));
847
+ }, [preferences.direction, dispatch]);
848
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(I18nProvider, { locale: preferences.locale, children: /* @__PURE__ */ jsx(NavigatorProvider, { navigator: epubNavigator, children: /* @__PURE__ */ jsx("main", { className: thorium_web_reader_app_default.main, children: /* @__PURE__ */ jsx(StatefulDockingWrapper, { children: /* @__PURE__ */ jsxs(
849
+ "div",
850
+ {
851
+ className: getReaderClassNames({
852
+ isScroll,
853
+ isImmersive,
854
+ isHovering,
855
+ isFXL,
856
+ layoutUI,
857
+ breakpoint
858
+ }),
859
+ children: [
860
+ /* @__PURE__ */ jsx(
861
+ StatefulReaderHeader,
862
+ {
863
+ actionKeys: isFXL ? fxlActionKeys : reflowActionKeys,
864
+ actionsOrder: isFXL ? preferences.actions.fxlOrder : preferences.actions.reflowOrder,
865
+ layout: layoutUI,
866
+ runningHeadFormatPref: isFXL ? preferences.theming.header?.runningHead?.format?.fxl : preferences.theming.header?.runningHead?.format?.reflow
867
+ }
868
+ ),
869
+ !isScroll ? /* @__PURE__ */ jsx("nav", { className: classNames2(thorium_web_reader_paginatedArrow_default.container, thorium_web_reader_paginatedArrow_default.leftContainer), children: /* @__PURE__ */ jsx(
870
+ StatefulReaderArrowButton,
871
+ {
872
+ direction: "left",
873
+ isDisabled: atPublicationStart,
874
+ onPress: () => {
875
+ const navigationCallback = () => {
876
+ dispatch(setUserNavigated(true));
877
+ activateImmersiveOnAction();
878
+ };
879
+ goLeft(!reducedMotion, navigationCallback);
880
+ }
881
+ }
882
+ ) }) : /* @__PURE__ */ jsx(Fragment, {}),
883
+ /* @__PURE__ */ jsx("article", { className: thorium_web_reader_app_default.wrapper, "aria-label": t("reader.app.publicationWrapper"), children: /* @__PURE__ */ jsx("div", { id: "thorium-web-container", className: thorium_web_reader_app_default.iframeContainer, ref: container }) }),
884
+ !isScroll ? /* @__PURE__ */ jsx("nav", { className: classNames2(thorium_web_reader_paginatedArrow_default.container, thorium_web_reader_paginatedArrow_default.rightContainer), children: /* @__PURE__ */ jsx(
885
+ StatefulReaderArrowButton,
886
+ {
887
+ direction: "right",
888
+ isDisabled: atPublicationEnd,
889
+ onPress: () => {
890
+ const navigationCallback = () => {
891
+ dispatch(setUserNavigated(true));
892
+ activateImmersiveOnAction();
893
+ };
894
+ goRight(!reducedMotion, navigationCallback);
895
+ }
896
+ }
897
+ ) }) : /* @__PURE__ */ jsx(Fragment, {}),
898
+ /* @__PURE__ */ jsx(
899
+ StatefulReaderFooter,
900
+ {
901
+ layout: layoutUI,
902
+ progressionFormatPref: isFXL ? preferences.theming.progression?.format?.fxl : preferences.theming.progression?.format?.reflow,
903
+ progressionFormatFallback: isFXL ? "readingOrderIndex" /* readingOrderIndex */ : "resourceProgression" /* resourceProgression */
904
+ }
905
+ )
906
+ ]
907
+ }
908
+ ) }) }) }) }) });
909
+ };
910
+
911
+ export { StatefulReader };
912
+ //# sourceMappingURL=chunk-34MVY33F.mjs.map
913
+ //# sourceMappingURL=chunk-34MVY33F.mjs.map