@libraz/mejiro-react 0.5.0 → 0.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/README.md +11 -0
- package/dist/index.d.ts +136 -9
- package/dist/index.js +232 -63
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,6 +31,17 @@ export default function App() {
|
|
|
31
31
|
html, body, #root { height: 100%; margin: 0; }
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
To embed in normal page flow instead (a blog post, a docs page) with no height
|
|
35
|
+
math, use `fit="width"` — the reader self-sizes its height from its width:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<MejiroReader epubUrl="/book.epub" fit="width" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> Embedding inside a framework with an unlayered global CSS reset (Next.js,
|
|
42
|
+
> Tailwind preflight, normalize.css)? See the [cascade-layers note](https://github.com/libraz/mejiro/tree/main/docs/en/08-react-and-vue.md#css-cascade-layers-host-resets-can-clobber-the-reader-chrome)
|
|
43
|
+
> so host resets don't clobber the reader chrome.
|
|
44
|
+
|
|
34
45
|
## Templates
|
|
35
46
|
|
|
36
47
|
Copy-paste-ready starters live under [`examples/`](https://github.com/libraz/mejiro/tree/main/examples):
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, CSSProperties, PointerEvent, TextareaHTMLAttributes, RefObject } from 'react';
|
|
3
3
|
import { EpubBook, AssetResolver, EditableEpub, ManuscriptDialect, EpubChapter, EditableEpubBook, AnnotatedParagraph, AddImageInput, EditableEpubImage, EpubExportOptions, EpubProjectMetadata, EpubProject } from '@libraz/mejiro/epub';
|
|
4
|
-
import { BookOptions, ChapterLayout, SpreadResult, PageResult, InChapterAnchor, ReadingAnchor, ManuscriptChapter, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
|
|
4
|
+
import { BookOptions, ChapterLayout, SpreadResult, PageResult, ComputePageSizeOptions, InChapterAnchor, ReadingAnchor, ManuscriptChapter, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
|
|
5
5
|
import { RenderPage } from '@libraz/mejiro/render';
|
|
6
6
|
import { FontFamily, InlineAnnotation } from '@libraz/mejiro/browser';
|
|
7
7
|
|
|
@@ -567,6 +567,22 @@ type MejiroReaderMode = 'paginated' | 'scroll';
|
|
|
567
567
|
* - `auto` — switch based on the surface aspect ratio (single when portrait).
|
|
568
568
|
*/
|
|
569
569
|
type MejiroSpreadMode = 'double' | 'single' | 'auto';
|
|
570
|
+
/**
|
|
571
|
+
* Which page of a spread prints its page number: `'both'` (right = odd,
|
|
572
|
+
* left = even), only `'right'`, only `'left'`, or `'none'`.
|
|
573
|
+
*/
|
|
574
|
+
type PageNumberDisplay = 'both' | 'right' | 'left' | 'none';
|
|
575
|
+
/**
|
|
576
|
+
* How the reader sizes itself inside its container.
|
|
577
|
+
* - `fill` — fill the container's given height; the spread is fitted inside it,
|
|
578
|
+
* letterboxing if the box aspect doesn't match the spread (the default; the
|
|
579
|
+
* host must give the reader a height).
|
|
580
|
+
* - `width` — self-size from width: the reader derives its own height from its
|
|
581
|
+
* measured width and the page aspect ratio, so the spread fills exactly with
|
|
582
|
+
* no letterbox and the host needs no height/aspect magic numbers. The host
|
|
583
|
+
* only constrains the width.
|
|
584
|
+
*/
|
|
585
|
+
type MejiroReaderFit = 'fill' | 'width';
|
|
570
586
|
/** Built-in reader theme presets. */
|
|
571
587
|
type MejiroThemeName = 'light' | 'dark' | 'sepia' | 'high-contrast' | 'auto';
|
|
572
588
|
/**
|
|
@@ -656,6 +672,23 @@ interface MejiroReaderHandle {
|
|
|
656
672
|
*/
|
|
657
673
|
subscribe<E extends keyof MejiroReaderEventMap>(event: E, listener: MejiroReaderEventMap[E]): () => void;
|
|
658
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* Context passed to a {@link MejiroReaderCommonProps.renderSettings} render prop.
|
|
677
|
+
*
|
|
678
|
+
* Lets a host replace the built-in settings controls with its own UI while
|
|
679
|
+
* keeping the panel chrome and its open/close accordion. All fields are wired
|
|
680
|
+
* to the live reader.
|
|
681
|
+
*/
|
|
682
|
+
interface MejiroReaderSettingsSlot {
|
|
683
|
+
/** Current effective settings (font, size, line spacing, kinsoku, hanging). */
|
|
684
|
+
settings: EditableSettings;
|
|
685
|
+
/** Applies a partial settings change and re-flows the chapter. */
|
|
686
|
+
update: (partial: Partial<BookOptions>) => void;
|
|
687
|
+
/** Whether the settings panel is currently open. */
|
|
688
|
+
open: boolean;
|
|
689
|
+
/** Toggles the settings panel open/closed (same as the header button). */
|
|
690
|
+
toggle: () => void;
|
|
691
|
+
}
|
|
659
692
|
/** Props shared across every {@link MejiroReader} source mode. */
|
|
660
693
|
interface MejiroReaderCommonProps {
|
|
661
694
|
/**
|
|
@@ -668,6 +701,15 @@ interface MejiroReaderCommonProps {
|
|
|
668
701
|
* ```
|
|
669
702
|
*/
|
|
670
703
|
options?: BookOptions;
|
|
704
|
+
/**
|
|
705
|
+
* Page-geometry overrides forwarded to `MejiroBook.computePageSize`. Use to
|
|
706
|
+
* tune how the spread is sized inside the surface — most usefully to shrink
|
|
707
|
+
* the reserved margins so the pages fill their frame, e.g.
|
|
708
|
+
* `pageGeometry={{ gutterOffset: 0, headerOffset: 0 }}`. Also accepts
|
|
709
|
+
* `aspect`, `minWidth`, `minHeight`, `maxHeight`, and inner `padding`; omitted
|
|
710
|
+
* fields fall back to the built-in defaults.
|
|
711
|
+
*/
|
|
712
|
+
pageGeometry?: ComputePageSizeOptions;
|
|
671
713
|
/** Font choices for the settings panel. */
|
|
672
714
|
fonts?: FontChoice[];
|
|
673
715
|
/**
|
|
@@ -699,6 +741,16 @@ interface MejiroReaderCommonProps {
|
|
|
699
741
|
* only the right page; `auto` flips to `single` for portrait viewports.
|
|
700
742
|
*/
|
|
701
743
|
spreadMode?: MejiroSpreadMode;
|
|
744
|
+
/**
|
|
745
|
+
* How the reader sizes itself in its container. `fill` (default) fills the
|
|
746
|
+
* container height and letterboxes the spread; `width` makes the reader
|
|
747
|
+
* self-size — it derives its height from its width and the page aspect, so an
|
|
748
|
+
* embedding host only has to constrain the width (no height/aspect magic
|
|
749
|
+
* numbers, no letterbox). In `width` mode the reserved `gutterOffset` /
|
|
750
|
+
* `headerOffset` default to 0 so the spread fills edge-to-edge; override via
|
|
751
|
+
* {@link MejiroReaderProps.pageGeometry} if you still want them. @defaultValue 'fill'
|
|
752
|
+
*/
|
|
753
|
+
fit?: MejiroReaderFit;
|
|
702
754
|
/**
|
|
703
755
|
* Enable surface-tap chrome toggling. Tapping the center of the spread
|
|
704
756
|
* (away from buttons) hides the header and chapter panel; tapping again
|
|
@@ -769,6 +821,15 @@ interface MejiroReaderCommonProps {
|
|
|
769
821
|
chapterNavMode?: MejiroChapterNavMode;
|
|
770
822
|
/** Show the settings panel toggle. @defaultValue `!bare` */
|
|
771
823
|
enableSettings?: boolean;
|
|
824
|
+
/**
|
|
825
|
+
* Replaces the built-in settings controls with custom UI while keeping the
|
|
826
|
+
* panel chrome and its open/close accordion. Receives a
|
|
827
|
+
* {@link MejiroReaderSettingsSlot} wired to the live reader, so a host can
|
|
828
|
+
* build its own settings form without a parallel `options` shadow. The header
|
|
829
|
+
* "Settings" button still toggles the panel. `enableSettings` remains the
|
|
830
|
+
* on/off switch.
|
|
831
|
+
*/
|
|
832
|
+
renderSettings?: (slot: MejiroReaderSettingsSlot) => ReactNode;
|
|
772
833
|
/** Show the image-overlay editing/demo button. @defaultValue false */
|
|
773
834
|
enableImageOverlay?: boolean;
|
|
774
835
|
/** Show the stats line. @defaultValue `!bare` */
|
|
@@ -777,6 +838,14 @@ interface MejiroReaderCommonProps {
|
|
|
777
838
|
enableKeyboard?: boolean;
|
|
778
839
|
/** Show the "n / total" indicator. @defaultValue `!bare` */
|
|
779
840
|
enablePageIndicator?: boolean;
|
|
841
|
+
/**
|
|
842
|
+
* Which page of a spread shows its page number in the running head.
|
|
843
|
+
* `'both'` numbers each page (right = odd, left = even), `'right'` /
|
|
844
|
+
* `'left'` number only that side, `'none'` hides them (the
|
|
845
|
+
* {@link MejiroReaderProps.enablePageIndicator} "n / total" badge is
|
|
846
|
+
* independent). @defaultValue 'both'
|
|
847
|
+
*/
|
|
848
|
+
pageNumbers?: PageNumberDisplay;
|
|
780
849
|
/**
|
|
781
850
|
* Reader-side annotations to render as highlights. Each annotation whose
|
|
782
851
|
* `chapter` matches the current chapter is converted to spread-local
|
|
@@ -1248,10 +1317,39 @@ declare function useAnnotations(options: UseAnnotationsOptions): UseAnnotationsR
|
|
|
1248
1317
|
|
|
1249
1318
|
/** Options for {@link useChapterLayout}. */
|
|
1250
1319
|
interface UseChapterLayoutOptions {
|
|
1251
|
-
/**
|
|
1320
|
+
/** Observe the surface element for size changes and re-flow. @defaultValue true */
|
|
1252
1321
|
enableResize?: boolean;
|
|
1253
|
-
/** Debounce window (ms)
|
|
1322
|
+
/** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
|
|
1254
1323
|
resizeDebounce?: number;
|
|
1324
|
+
/**
|
|
1325
|
+
* Resolver for page-geometry overrides forwarded to
|
|
1326
|
+
* {@link MejiroBook.computePageSize} — e.g. to shrink the reserved
|
|
1327
|
+
* `gutterOffset` / `headerOffset` so the pages fill their frame. Called on
|
|
1328
|
+
* every (re)layout so it may return a value that changes over time.
|
|
1329
|
+
*/
|
|
1330
|
+
pageGeometry?: () => ComputePageSizeOptions | undefined;
|
|
1331
|
+
/**
|
|
1332
|
+
* Capture the current reading position from the outgoing layout, just before
|
|
1333
|
+
* a **reflow** (non-blank) re-layout replaces it. The returned anchor is
|
|
1334
|
+
* exposed via {@link UseChapterLayoutReturn.pendingRestore} and should be
|
|
1335
|
+
* applied once the new layout commits. Return `null` to skip preservation.
|
|
1336
|
+
* Never called for blank (content-change) re-layouts.
|
|
1337
|
+
*
|
|
1338
|
+
* A reflow yields a brand-new {@link ChapterLayout} object, which resets any
|
|
1339
|
+
* downstream spread index to 0; capturing here and restoring after keeps the
|
|
1340
|
+
* reader on the same passage across resizes and font / option changes.
|
|
1341
|
+
*/
|
|
1342
|
+
capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
|
|
1343
|
+
}
|
|
1344
|
+
/** Options for {@link UseChapterLayoutReturn.recompute}. */
|
|
1345
|
+
interface RecomputeOptions {
|
|
1346
|
+
/**
|
|
1347
|
+
* Blank the current layout while the new one is computed. Use for content
|
|
1348
|
+
* changes (chapter / book swaps); skip for size or option changes so the
|
|
1349
|
+
* previous spread stays visible until the re-flow completes (no flicker).
|
|
1350
|
+
* @defaultValue true
|
|
1351
|
+
*/
|
|
1352
|
+
blank?: boolean;
|
|
1255
1353
|
}
|
|
1256
1354
|
/** Page dimensions returned by {@link MejiroBook.computePageSize}. */
|
|
1257
1355
|
interface PageDimensions {
|
|
@@ -1272,14 +1370,37 @@ interface UseChapterLayoutReturn {
|
|
|
1272
1370
|
/** Most recent layout time (ms). */
|
|
1273
1371
|
elapsedMs: number;
|
|
1274
1372
|
/** Force a fresh layout computation. */
|
|
1275
|
-
recompute: () => Promise<void>;
|
|
1373
|
+
recompute: (opts?: RecomputeOptions) => Promise<void>;
|
|
1374
|
+
/**
|
|
1375
|
+
* Anchor captured before the most recent reflow re-layout, awaiting
|
|
1376
|
+
* restoration into the new {@link layout}. Consume it in a layout effect keyed
|
|
1377
|
+
* on `layout` (after any index reset) and clear it back to `null`.
|
|
1378
|
+
*/
|
|
1379
|
+
pendingRestore: RefObject<InChapterAnchor | null>;
|
|
1276
1380
|
}
|
|
1277
1381
|
/**
|
|
1278
|
-
* React hook that lays out the currently-selected chapter and re-flows
|
|
1382
|
+
* React hook that lays out the currently-selected chapter and re-flows when the
|
|
1383
|
+
* surface resizes.
|
|
1279
1384
|
*
|
|
1280
|
-
* Re-layouts whenever `epub
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1385
|
+
* Re-layouts whenever `book`, `epub`, or `chapterIndex` change, against the
|
|
1386
|
+
* dimensions of `surface`. The `surface` element is observed with a
|
|
1387
|
+
* {@link ResizeObserver}: the first observation runs immediately (so a reader
|
|
1388
|
+
* mounted before its container had a final box is still sized correctly on
|
|
1389
|
+
* first paint), and later size changes trigger a debounced **full re-layout**.
|
|
1390
|
+
*
|
|
1391
|
+
* A full re-layout — rather than a `ChapterLayout.resize()` fast-path — is used
|
|
1392
|
+
* for size changes on purpose: the fast-path only stretches `pageWidth` and does
|
|
1393
|
+
* not re-paginate, leaving sparse, half-empty pages after a non-trivial size
|
|
1394
|
+
* delta. Because a full re-layout yields a new {@link ChapterLayout} object
|
|
1395
|
+
* (resetting any downstream spread index to 0), the reading position is
|
|
1396
|
+
* preserved across reflows via {@link UseChapterLayoutOptions.capturePosition}
|
|
1397
|
+
* and {@link UseChapterLayoutReturn.pendingRestore}.
|
|
1398
|
+
*
|
|
1399
|
+
* @param book - The book instance to lay out with.
|
|
1400
|
+
* @param epub - The current parsed EPUB.
|
|
1401
|
+
* @param chapterIndex - Zero-based chapter index to lay out.
|
|
1402
|
+
* @param surface - DOM ref for the reading surface used for page sizing.
|
|
1403
|
+
* @param options - Behavior overrides.
|
|
1283
1404
|
*/
|
|
1284
1405
|
declare function useChapterLayout(book: MejiroBook, epub: EpubBook | null, chapterIndex: number, surface: RefObject<HTMLElement | null>, options?: UseChapterLayoutOptions): UseChapterLayoutReturn;
|
|
1285
1406
|
|
|
@@ -1602,6 +1723,12 @@ interface UseSpreadReturn {
|
|
|
1602
1723
|
prev: () => void;
|
|
1603
1724
|
/** Jump to an arbitrary spread index (clamped). */
|
|
1604
1725
|
goTo: (index: number) => void;
|
|
1726
|
+
/**
|
|
1727
|
+
* Set the spread index immediately, with no page-turn animation, clamped to
|
|
1728
|
+
* `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
|
|
1729
|
+
* re-layout (where an animated {@link goTo} would briefly flash spread 0).
|
|
1730
|
+
*/
|
|
1731
|
+
setSpread: (index: number) => void;
|
|
1605
1732
|
/** Manually refresh `spread` from `layout` at the current index. */
|
|
1606
1733
|
refresh: () => void;
|
|
1607
1734
|
}
|
|
@@ -1611,4 +1738,4 @@ interface UseSpreadReturn {
|
|
|
1611
1738
|
*/
|
|
1612
1739
|
declare function useSpread(layout: ChapterLayout | null, options?: UseSpreadOptions): UseSpreadReturn;
|
|
1613
1740
|
|
|
1614
|
-
export { type Annotation, type AnnotationsStorage, type EditableEpubSelection, type EditableSettings, type EpubProjectChapterDraft, type FontChoice, type ImageRect, type ManuscriptEditorChapter, type ManuscriptPageDimensions, type ManuscriptPreviewProps, MejiroChapterNav, type MejiroChapterNavMode, type MejiroChapterNavProps, type MejiroChapterNavVariant, MejiroDropZone, type MejiroDropZoneProps, MejiroEditor, type MejiroEditorProps, type MejiroExportPolicy, MejiroI18nProvider, MejiroImageOverlay, type MejiroImageOverlayProps, type MejiroLocale, MejiroManuscriptEditor, type MejiroManuscriptEditorProps, type MejiroMessages, MejiroNotationHighlighter, type MejiroNotationHighlighterProps, MejiroPage, MejiroPageIndicator, type MejiroPageIndicatorProps, type MejiroPageProps, MejiroPageView, type MejiroPageViewProps, MejiroReader, type MejiroReaderControlledProps, type MejiroReaderEventMap, type MejiroReaderFileProps, type MejiroReaderHandle, type MejiroReaderMode, type MejiroReaderProps, type MejiroReaderUrlProps, MejiroScrollView, type MejiroScrollViewProps, MejiroSelectionLayer, type MejiroSelectionLayerProps, MejiroSettingsPanel, type MejiroSettingsPanelProps, MejiroShelf, type MejiroShelfProps, MejiroSpread, type MejiroSpreadMode, type MejiroSpreadProps, MejiroStats, type MejiroStatsProps, type MejiroTheme, type MejiroThemeName, MejiroToc, type MejiroTocProps, type MultiImageItem, type PageDimensions, type PageHeaderData, type ReadingPosition, type ReadingPositionStorage, type ReadingPositionValue, type UseAnnotationsOptions, type UseAnnotationsReturn, type UseChapterLayoutOptions, type UseChapterLayoutReturn, type UseEditableEpubOptions, type UseEditableEpubReturn, type UseEpubOptions, type UseEpubProjectOptions, type UseEpubProjectReturn, type UseEpubReturn, type UseI18nOptions, type UseImageOverlayOptions, type UseImageOverlayReturn, type UseLibraryOptions, type UseLibraryReturn, type UseManuscriptDraftOptions, type UseManuscriptDraftReturn, type UseManuscriptLayoutOptions, type UseManuscriptLayoutReturn, type UseMejiroBookReturn, type UseMultiImageOverlayOptions, type UseMultiImageOverlayReturn, type UseReadingPositionOptions, type UseReadingPositionReturn, type UseSpreadOptions, type UseSpreadReturn, type VolumeInfo, enMessages, format, jaMessages, resolveMessages, useAnnotations, useChapterLayout, useEditableEpub, useEpub, useEpubProject, useI18n, useImageOverlay, useLibrary, useManuscriptDraft, useManuscriptLayout, useMejiroBook, useMultiImageOverlay, useReadingPosition, useSpread };
|
|
1741
|
+
export { type Annotation, type AnnotationsStorage, type EditableEpubSelection, type EditableSettings, type EpubProjectChapterDraft, type FontChoice, type ImageRect, type ManuscriptEditorChapter, type ManuscriptPageDimensions, type ManuscriptPreviewProps, MejiroChapterNav, type MejiroChapterNavMode, type MejiroChapterNavProps, type MejiroChapterNavVariant, MejiroDropZone, type MejiroDropZoneProps, MejiroEditor, type MejiroEditorProps, type MejiroExportPolicy, MejiroI18nProvider, MejiroImageOverlay, type MejiroImageOverlayProps, type MejiroLocale, MejiroManuscriptEditor, type MejiroManuscriptEditorProps, type MejiroMessages, MejiroNotationHighlighter, type MejiroNotationHighlighterProps, MejiroPage, MejiroPageIndicator, type MejiroPageIndicatorProps, type MejiroPageProps, MejiroPageView, type MejiroPageViewProps, MejiroReader, type MejiroReaderControlledProps, type MejiroReaderEventMap, type MejiroReaderFileProps, type MejiroReaderFit, type MejiroReaderHandle, type MejiroReaderMode, type MejiroReaderProps, type MejiroReaderSettingsSlot, type MejiroReaderUrlProps, MejiroScrollView, type MejiroScrollViewProps, MejiroSelectionLayer, type MejiroSelectionLayerProps, MejiroSettingsPanel, type MejiroSettingsPanelProps, MejiroShelf, type MejiroShelfProps, MejiroSpread, type MejiroSpreadMode, type MejiroSpreadProps, MejiroStats, type MejiroStatsProps, type MejiroTheme, type MejiroThemeName, MejiroToc, type MejiroTocProps, type MultiImageItem, type PageDimensions, type PageHeaderData, type PageNumberDisplay, type ReadingPosition, type ReadingPositionStorage, type ReadingPositionValue, type UseAnnotationsOptions, type UseAnnotationsReturn, type UseChapterLayoutOptions, type UseChapterLayoutReturn, type UseEditableEpubOptions, type UseEditableEpubReturn, type UseEpubOptions, type UseEpubProjectOptions, type UseEpubProjectReturn, type UseEpubReturn, type UseI18nOptions, type UseImageOverlayOptions, type UseImageOverlayReturn, type UseLibraryOptions, type UseLibraryReturn, type UseManuscriptDraftOptions, type UseManuscriptDraftReturn, type UseManuscriptLayoutOptions, type UseManuscriptLayoutReturn, type UseMejiroBookReturn, type UseMultiImageOverlayOptions, type UseMultiImageOverlayReturn, type UseReadingPositionOptions, type UseReadingPositionReturn, type UseSpreadOptions, type UseSpreadReturn, type VolumeInfo, enMessages, format, jaMessages, resolveMessages, useAnnotations, useChapterLayout, useEditableEpub, useEpub, useEpubProject, useI18n, useImageOverlay, useLibrary, useManuscriptDraft, useManuscriptLayout, useMejiroBook, useMultiImageOverlay, useReadingPosition, useSpread };
|
package/dist/index.js
CHANGED
|
@@ -277,7 +277,7 @@ import { EditableEpub } from "@libraz/mejiro/epub";
|
|
|
277
277
|
import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef10, useState as useState8 } from "react";
|
|
278
278
|
|
|
279
279
|
// src/MejiroReader.tsx
|
|
280
|
-
import { DEFAULT_BOOK_OPTIONS } from "@libraz/mejiro/book";
|
|
280
|
+
import { DEFAULT_BOOK_OPTIONS, DEFAULT_PAGE_GEOMETRY } from "@libraz/mejiro/book";
|
|
281
281
|
import { normalizeFontFamily as normalizeFontFamily5 } from "@libraz/mejiro/browser";
|
|
282
282
|
import { manuscriptToEpubBook } from "@libraz/mejiro/epub";
|
|
283
283
|
import {
|
|
@@ -285,6 +285,7 @@ import {
|
|
|
285
285
|
useCallback as useCallback6,
|
|
286
286
|
useEffect as useEffect6,
|
|
287
287
|
useImperativeHandle,
|
|
288
|
+
useLayoutEffect as useLayoutEffect4,
|
|
288
289
|
useMemo as useMemo4,
|
|
289
290
|
useRef as useRef9,
|
|
290
291
|
useState as useState7
|
|
@@ -538,6 +539,10 @@ var DEFAULT_FONTS = [
|
|
|
538
539
|
{ value: "serif", label: "System Serif" },
|
|
539
540
|
{ value: "sans-serif", label: "System Sans" }
|
|
540
541
|
];
|
|
542
|
+
function fontLabelFromCss(css) {
|
|
543
|
+
const first = css.split(",")[0]?.trim() ?? css;
|
|
544
|
+
return first.replace(/^["']|["']$/g, "") || css;
|
|
545
|
+
}
|
|
541
546
|
function MejiroSettingsPanel({
|
|
542
547
|
open,
|
|
543
548
|
settings,
|
|
@@ -548,17 +553,12 @@ function MejiroSettingsPanel({
|
|
|
548
553
|
}) {
|
|
549
554
|
const messages = useI18n();
|
|
550
555
|
const patch = (next) => onChange({ ...settings, ...next });
|
|
551
|
-
|
|
556
|
+
const currentFont = normalizeFontFamily3(settings.fontFamily);
|
|
557
|
+
const fontOptions = fonts.some((f) => f.value === currentFont) ? fonts : [{ value: currentFont, label: fontLabelFromCss(currentFont) }, ...fonts];
|
|
558
|
+
return /* @__PURE__ */ jsx7("div", { className: `mejiro-reader-settings-panel${open ? " is-open" : ""}`, children: /* @__PURE__ */ jsx7("div", { className: "mejiro-reader-settings-inner", children: /* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-settings-content", children: [
|
|
552
559
|
/* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-settings-group", children: [
|
|
553
560
|
/* @__PURE__ */ jsx7("span", { className: "mejiro-reader-settings-group-title", children: messages.settingsFont }),
|
|
554
|
-
/* @__PURE__ */ jsx7("div", { className: "mejiro-reader-control", children: /* @__PURE__ */ jsx7(
|
|
555
|
-
"select",
|
|
556
|
-
{
|
|
557
|
-
value: normalizeFontFamily3(settings.fontFamily),
|
|
558
|
-
onChange: (e) => patch({ fontFamily: e.target.value }),
|
|
559
|
-
children: fonts.map((f) => /* @__PURE__ */ jsx7("option", { value: f.value, children: f.label }, f.value))
|
|
560
|
-
}
|
|
561
|
-
) }),
|
|
561
|
+
/* @__PURE__ */ jsx7("div", { className: "mejiro-reader-control", children: /* @__PURE__ */ jsx7("select", { value: currentFont, onChange: (e) => patch({ fontFamily: e.target.value }), children: fontOptions.map((f) => /* @__PURE__ */ jsx7("option", { value: f.value, children: f.label }, f.value)) }) }),
|
|
562
562
|
/* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-control", children: [
|
|
563
563
|
/* @__PURE__ */ jsx7("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-font-size", children: messages.settingsSize }),
|
|
564
564
|
/* @__PURE__ */ jsx7(
|
|
@@ -643,7 +643,7 @@ function MejiroSettingsPanel({
|
|
|
643
643
|
)
|
|
644
644
|
] })
|
|
645
645
|
] })
|
|
646
|
-
] }) });
|
|
646
|
+
] }) }) });
|
|
647
647
|
}
|
|
648
648
|
|
|
649
649
|
// src/MejiroSpread.tsx
|
|
@@ -685,11 +685,18 @@ function MejiroImageOverlay({
|
|
|
685
685
|
{
|
|
686
686
|
type: "button",
|
|
687
687
|
"aria-label": messages.imageRemoveButton,
|
|
688
|
+
title: messages.imageRemoveButton,
|
|
688
689
|
className: "mejiro-reader-image-overlay-close",
|
|
689
690
|
onPointerDown: (e) => {
|
|
690
691
|
e.stopPropagation();
|
|
691
692
|
e.preventDefault();
|
|
692
693
|
onClose?.();
|
|
694
|
+
},
|
|
695
|
+
onKeyDown: (e) => {
|
|
696
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
697
|
+
e.stopPropagation();
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
onClose?.();
|
|
693
700
|
}
|
|
694
701
|
}
|
|
695
702
|
)
|
|
@@ -978,7 +985,13 @@ function MejiroStats({
|
|
|
978
985
|
}
|
|
979
986
|
|
|
980
987
|
// src/useChapterLayout.ts
|
|
981
|
-
import {
|
|
988
|
+
import {
|
|
989
|
+
useCallback,
|
|
990
|
+
useEffect as useEffect2,
|
|
991
|
+
useLayoutEffect as useLayoutEffect2,
|
|
992
|
+
useRef as useRef4,
|
|
993
|
+
useState as useState2
|
|
994
|
+
} from "react";
|
|
982
995
|
function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
983
996
|
const enableResize = options.enableResize ?? true;
|
|
984
997
|
const resizeDebounce = options.resizeDebounce ?? 120;
|
|
@@ -989,31 +1002,43 @@ function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
|
989
1002
|
const [elapsedMs, setElapsedMs] = useState2(0);
|
|
990
1003
|
const layoutRef = useRef4(null);
|
|
991
1004
|
const requestIdRef = useRef4(0);
|
|
1005
|
+
const pendingRestore = useRef4(null);
|
|
992
1006
|
layoutRef.current = layout;
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1007
|
+
const optionsRef = useRef4(options);
|
|
1008
|
+
optionsRef.current = options;
|
|
1009
|
+
const recompute = useCallback(
|
|
1010
|
+
async (opts = {}) => {
|
|
1011
|
+
const blank = opts.blank ?? true;
|
|
1012
|
+
const o = optionsRef.current;
|
|
1013
|
+
const requestId = ++requestIdRef.current;
|
|
1014
|
+
const chapter = epub?.chapters[chapterIndex];
|
|
1015
|
+
if (!(chapter && surface.current)) {
|
|
1016
|
+
setLayout(null);
|
|
1017
|
+
setPageWidth(0);
|
|
1018
|
+
setPageHeight(0);
|
|
1019
|
+
setContentHeight(0);
|
|
1020
|
+
setElapsedMs(0);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const captured = !blank && layoutRef.current ? o.capturePosition?.(layoutRef.current) ?? null : null;
|
|
1024
|
+
pendingRestore.current = captured;
|
|
1025
|
+
if (blank) setLayout(null);
|
|
1026
|
+
const dims = book.computePageSize(surface.current, o.pageGeometry?.());
|
|
1027
|
+
setPageWidth(dims.pageWidth);
|
|
1028
|
+
setPageHeight(dims.pageHeight);
|
|
1029
|
+
setContentHeight(dims.contentHeight);
|
|
1030
|
+
const t0 = performance.now();
|
|
1031
|
+
const l = await book.layoutChapter(chapter);
|
|
1032
|
+
if (requestId !== requestIdRef.current) return;
|
|
1033
|
+
layoutRef.current = l;
|
|
1034
|
+
setLayout(l);
|
|
1035
|
+
setElapsedMs(performance.now() - t0);
|
|
1036
|
+
},
|
|
1037
|
+
[book, epub, chapterIndex, surface]
|
|
1038
|
+
);
|
|
1015
1039
|
useLayoutEffect2(() => {
|
|
1016
1040
|
requestIdRef.current++;
|
|
1041
|
+
pendingRestore.current = null;
|
|
1017
1042
|
setLayout(null);
|
|
1018
1043
|
setPageWidth(0);
|
|
1019
1044
|
setPageHeight(0);
|
|
@@ -1028,29 +1053,55 @@ function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
|
1028
1053
|
}, [recompute]);
|
|
1029
1054
|
useEffect2(() => {
|
|
1030
1055
|
if (!enableResize) return;
|
|
1056
|
+
const el = surface.current;
|
|
1057
|
+
if (!el) return;
|
|
1031
1058
|
let timer = null;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (timer)
|
|
1059
|
+
let observed = false;
|
|
1060
|
+
const clear = () => {
|
|
1061
|
+
if (timer) {
|
|
1062
|
+
clearTimeout(timer);
|
|
1063
|
+
timer = null;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
const scheduleReflow = (immediate) => {
|
|
1067
|
+
clear();
|
|
1068
|
+
if (immediate) {
|
|
1069
|
+
void recompute({ blank: false });
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1035
1072
|
timer = setTimeout(() => {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
setPageWidth(dims.pageWidth);
|
|
1039
|
-
setPageHeight(dims.pageHeight);
|
|
1040
|
-
setContentHeight(dims.contentHeight);
|
|
1041
|
-
layoutRef.current.resize({
|
|
1042
|
-
pageWidth: dims.pageWidth,
|
|
1043
|
-
lineWidth: dims.contentHeight - book.getOptions().fontSize * 0.5
|
|
1044
|
-
});
|
|
1073
|
+
timer = null;
|
|
1074
|
+
void recompute({ blank: false });
|
|
1045
1075
|
}, resizeDebounce);
|
|
1046
1076
|
};
|
|
1047
|
-
|
|
1077
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1078
|
+
const observer = new ResizeObserver(() => {
|
|
1079
|
+
const immediate = !observed;
|
|
1080
|
+
observed = true;
|
|
1081
|
+
scheduleReflow(immediate);
|
|
1082
|
+
});
|
|
1083
|
+
observer.observe(el);
|
|
1084
|
+
return () => {
|
|
1085
|
+
observer.disconnect();
|
|
1086
|
+
clear();
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
const onWindowResize = () => scheduleReflow(false);
|
|
1090
|
+
window.addEventListener("resize", onWindowResize);
|
|
1048
1091
|
return () => {
|
|
1049
|
-
window.removeEventListener("resize",
|
|
1050
|
-
|
|
1092
|
+
window.removeEventListener("resize", onWindowResize);
|
|
1093
|
+
clear();
|
|
1051
1094
|
};
|
|
1052
|
-
}, [
|
|
1053
|
-
return {
|
|
1095
|
+
}, [surface, enableResize, resizeDebounce, recompute]);
|
|
1096
|
+
return {
|
|
1097
|
+
layout,
|
|
1098
|
+
pageWidth,
|
|
1099
|
+
pageHeight,
|
|
1100
|
+
contentHeight,
|
|
1101
|
+
elapsedMs,
|
|
1102
|
+
recompute,
|
|
1103
|
+
pendingRestore
|
|
1104
|
+
};
|
|
1054
1105
|
}
|
|
1055
1106
|
|
|
1056
1107
|
// src/useEpub.ts
|
|
@@ -1457,6 +1508,16 @@ function useSpread(layout, options = {}) {
|
|
|
1457
1508
|
},
|
|
1458
1509
|
[turnDuration]
|
|
1459
1510
|
);
|
|
1511
|
+
const setSpreadIndex = useCallback5((index) => {
|
|
1512
|
+
if (!layoutRef.current) return;
|
|
1513
|
+
if (turnTimerRef.current) {
|
|
1514
|
+
clearTimeout(turnTimerRef.current);
|
|
1515
|
+
turnTimerRef.current = null;
|
|
1516
|
+
}
|
|
1517
|
+
setTurning(false);
|
|
1518
|
+
const max = Math.max(1, Math.ceil(layoutRef.current.totalPages / 2)) - 1;
|
|
1519
|
+
setSpreadIdx(Math.max(0, Math.min(max, index)));
|
|
1520
|
+
}, []);
|
|
1460
1521
|
const next = useCallback5(() => goTo(spreadIdxRef.current + 1), [goTo]);
|
|
1461
1522
|
const prev = useCallback5(() => goTo(spreadIdxRef.current - 1), [goTo]);
|
|
1462
1523
|
useEffect5(() => {
|
|
@@ -1475,7 +1536,18 @@ function useSpread(layout, options = {}) {
|
|
|
1475
1536
|
},
|
|
1476
1537
|
[]
|
|
1477
1538
|
);
|
|
1478
|
-
return {
|
|
1539
|
+
return {
|
|
1540
|
+
spreadIdx,
|
|
1541
|
+
spread,
|
|
1542
|
+
totalPages,
|
|
1543
|
+
totalSpreads,
|
|
1544
|
+
turning,
|
|
1545
|
+
next,
|
|
1546
|
+
prev,
|
|
1547
|
+
goTo,
|
|
1548
|
+
setSpread: setSpreadIndex,
|
|
1549
|
+
refresh
|
|
1550
|
+
};
|
|
1479
1551
|
}
|
|
1480
1552
|
|
|
1481
1553
|
// src/MejiroReader.tsx
|
|
@@ -1485,6 +1557,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1485
1557
|
const dialectProp = "dialect" in props ? props.dialect : void 0;
|
|
1486
1558
|
const {
|
|
1487
1559
|
options = DEFAULT_BOOK_OPTIONS,
|
|
1560
|
+
pageGeometry: pageGeometryProp,
|
|
1488
1561
|
fonts,
|
|
1489
1562
|
epub: epubProp,
|
|
1490
1563
|
epubUrl,
|
|
@@ -1493,6 +1566,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1493
1566
|
theme = "light",
|
|
1494
1567
|
mode = "paginated",
|
|
1495
1568
|
spreadMode = "double",
|
|
1569
|
+
fit = "fill",
|
|
1496
1570
|
enableSurfaceTap = true,
|
|
1497
1571
|
fallback,
|
|
1498
1572
|
fetchOptions,
|
|
@@ -1508,10 +1582,12 @@ function MejiroReaderInner(props, ref) {
|
|
|
1508
1582
|
enableChapterNav = !bare,
|
|
1509
1583
|
chapterNavMode = "select",
|
|
1510
1584
|
enableSettings = !bare,
|
|
1585
|
+
renderSettings,
|
|
1511
1586
|
enableImageOverlay = false,
|
|
1512
1587
|
enableStats = !bare,
|
|
1513
1588
|
enableKeyboard = true,
|
|
1514
1589
|
enablePageIndicator = !bare,
|
|
1590
|
+
pageNumbers = "both",
|
|
1515
1591
|
annotations,
|
|
1516
1592
|
onLoad,
|
|
1517
1593
|
onError,
|
|
@@ -1551,6 +1627,24 @@ function MejiroReaderInner(props, ref) {
|
|
|
1551
1627
|
return () => observer.disconnect();
|
|
1552
1628
|
}, [spreadMode]);
|
|
1553
1629
|
const effectiveSingle = spreadMode === "single" || spreadMode === "auto" && autoSingle;
|
|
1630
|
+
const resolvedGeometry = useMemo4(() => {
|
|
1631
|
+
const columns = effectiveSingle ? 1 : 2;
|
|
1632
|
+
if (fit !== "width") return { columns, ...pageGeometryProp };
|
|
1633
|
+
return {
|
|
1634
|
+
columns,
|
|
1635
|
+
gutterOffset: 0,
|
|
1636
|
+
headerOffset: 0,
|
|
1637
|
+
maxHeight: Number.POSITIVE_INFINITY,
|
|
1638
|
+
minWidth: 0,
|
|
1639
|
+
minHeight: 0,
|
|
1640
|
+
...pageGeometryProp
|
|
1641
|
+
};
|
|
1642
|
+
}, [fit, pageGeometryProp, effectiveSingle]);
|
|
1643
|
+
const surfaceAspect = useMemo4(() => {
|
|
1644
|
+
const columns = effectiveSingle ? 1 : 2;
|
|
1645
|
+
const aspect = resolvedGeometry?.aspect ?? DEFAULT_PAGE_GEOMETRY.aspect;
|
|
1646
|
+
return `${columns} / ${aspect}`;
|
|
1647
|
+
}, [effectiveSingle, resolvedGeometry]);
|
|
1554
1648
|
const { book, options: bookOptions, setOptions } = useMejiroBook(options);
|
|
1555
1649
|
const synthesizedEpub = useMemo4(() => {
|
|
1556
1650
|
if (manuscriptProp === void 0) return null;
|
|
@@ -1575,7 +1669,18 @@ function MejiroReaderInner(props, ref) {
|
|
|
1575
1669
|
useEffect6(() => {
|
|
1576
1670
|
if (epubCtx.error) onErrorRef.current?.(epubCtx.error);
|
|
1577
1671
|
}, [epubCtx.error]);
|
|
1578
|
-
const
|
|
1672
|
+
const pageGeometryRef = useRef9(resolvedGeometry);
|
|
1673
|
+
pageGeometryRef.current = resolvedGeometry;
|
|
1674
|
+
const curSpreadIdxRef = useRef9(0);
|
|
1675
|
+
const spreadIdxPropRef = useRef9(spreadIdxProp);
|
|
1676
|
+
spreadIdxPropRef.current = spreadIdxProp;
|
|
1677
|
+
const layoutCtx = useChapterLayout(book, e, chapter, surfaceRef, {
|
|
1678
|
+
pageGeometry: () => pageGeometryRef.current,
|
|
1679
|
+
// Preserve the reading position across a reflow re-layout (size / option
|
|
1680
|
+
// changes), but only in uncontrolled mode — when `spreadIdx` is controlled
|
|
1681
|
+
// the host owns the position and the controlled-restore effect handles it.
|
|
1682
|
+
capturePosition: (layout2) => spreadIdxPropRef.current != null ? null : layout2.anchorAt(curSpreadIdxRef.current, "right")
|
|
1683
|
+
});
|
|
1579
1684
|
const spreadChangedRef = useRef9(void 0);
|
|
1580
1685
|
spreadChangedRef.current = (i) => {
|
|
1581
1686
|
onSpreadChange?.(i);
|
|
@@ -1585,6 +1690,46 @@ function MejiroReaderInner(props, ref) {
|
|
|
1585
1690
|
enableKeyboard,
|
|
1586
1691
|
onChange: (i) => spreadChangedRef.current?.(i)
|
|
1587
1692
|
});
|
|
1693
|
+
curSpreadIdxRef.current = spreadCtx.spreadIdx;
|
|
1694
|
+
const setSpreadRef = useRef9(spreadCtx.setSpread);
|
|
1695
|
+
setSpreadRef.current = spreadCtx.setSpread;
|
|
1696
|
+
useLayoutEffect4(() => {
|
|
1697
|
+
const anchor = layoutCtx.pendingRestore.current;
|
|
1698
|
+
if (!(anchor && layoutCtx.layout)) return;
|
|
1699
|
+
layoutCtx.pendingRestore.current = null;
|
|
1700
|
+
const loc = layoutCtx.layout.locateAnchor(anchor);
|
|
1701
|
+
setSpreadRef.current(loc?.spreadIdx ?? 0);
|
|
1702
|
+
}, [layoutCtx.layout]);
|
|
1703
|
+
const optionsKey = [
|
|
1704
|
+
bookOptions.fontFamily,
|
|
1705
|
+
bookOptions.fontSize,
|
|
1706
|
+
bookOptions.lineSpacing,
|
|
1707
|
+
bookOptions.mode,
|
|
1708
|
+
bookOptions.enableHanging
|
|
1709
|
+
].join("|");
|
|
1710
|
+
const optionsKeyRef = useRef9(optionsKey);
|
|
1711
|
+
const bookOptionsRef = useRef9(bookOptions);
|
|
1712
|
+
bookOptionsRef.current = bookOptions;
|
|
1713
|
+
const recomputeRef = useRef9(layoutCtx.recompute);
|
|
1714
|
+
recomputeRef.current = layoutCtx.recompute;
|
|
1715
|
+
useEffect6(() => {
|
|
1716
|
+
if (optionsKeyRef.current === optionsKey) return;
|
|
1717
|
+
optionsKeyRef.current = optionsKey;
|
|
1718
|
+
const timer = setTimeout(() => {
|
|
1719
|
+
void (async () => {
|
|
1720
|
+
await setOptions({ ...bookOptionsRef.current });
|
|
1721
|
+
await recomputeRef.current({ blank: false });
|
|
1722
|
+
})();
|
|
1723
|
+
}, 60);
|
|
1724
|
+
return () => clearTimeout(timer);
|
|
1725
|
+
}, [optionsKey, setOptions]);
|
|
1726
|
+
const geometryKey = JSON.stringify(resolvedGeometry ?? null);
|
|
1727
|
+
const geometryKeyRef = useRef9(geometryKey);
|
|
1728
|
+
useEffect6(() => {
|
|
1729
|
+
if (geometryKeyRef.current === geometryKey) return;
|
|
1730
|
+
geometryKeyRef.current = geometryKey;
|
|
1731
|
+
void recomputeRef.current({ blank: false });
|
|
1732
|
+
}, [geometryKey]);
|
|
1588
1733
|
const imageCtx = useMultiImageOverlay(layoutCtx.layout, spreadCtx.spreadIdx, {
|
|
1589
1734
|
onUpdate: () => spreadCtx.refresh()
|
|
1590
1735
|
});
|
|
@@ -1616,9 +1761,13 @@ function MejiroReaderInner(props, ref) {
|
|
|
1616
1761
|
spreadGoToRef.current = spreadCtx.goTo;
|
|
1617
1762
|
useEffect6(() => {
|
|
1618
1763
|
if (spreadIdxProp == null) return;
|
|
1619
|
-
if (spreadIdxProp === spreadCtx.spreadIdx) return;
|
|
1620
1764
|
spreadGoToRef.current(spreadIdxProp);
|
|
1621
|
-
}, [spreadIdxProp
|
|
1765
|
+
}, [spreadIdxProp]);
|
|
1766
|
+
useLayoutEffect4(() => {
|
|
1767
|
+
const next = spreadIdxPropRef.current;
|
|
1768
|
+
if (next == null || !layoutCtx.layout) return;
|
|
1769
|
+
setSpreadRef.current(next);
|
|
1770
|
+
}, [layoutCtx.layout]);
|
|
1622
1771
|
const onChapter = useCallback6(
|
|
1623
1772
|
(i) => {
|
|
1624
1773
|
if (i === chapter) return;
|
|
@@ -1788,7 +1937,17 @@ function MejiroReaderInner(props, ref) {
|
|
|
1788
1937
|
const layoutReady = e && spreadCtx.spread && layoutCtx.layout && layoutCtx.pageWidth > 0;
|
|
1789
1938
|
const themeName = typeof theme === "string" ? theme : theme.name;
|
|
1790
1939
|
const themeOverride = typeof theme === "string" ? void 0 : theme.override;
|
|
1791
|
-
const themeStyle =
|
|
1940
|
+
const themeStyle = useMemo4(() => {
|
|
1941
|
+
const pad = pageGeometryProp?.padding;
|
|
1942
|
+
const styleVars = {};
|
|
1943
|
+
if (pad?.x != null) styleVars["--mejiro-page-pad-x"] = `${pad.x}px`;
|
|
1944
|
+
if (pad?.y != null) styleVars["--mejiro-page-pad-y"] = `${pad.y}px`;
|
|
1945
|
+
if (pad?.bottom != null) styleVars["--mejiro-page-pad-bottom"] = `${pad.bottom}px`;
|
|
1946
|
+
if (fit === "width") styleVars["--mejiro-surface-aspect"] = surfaceAspect;
|
|
1947
|
+
const hasVars = Object.keys(styleVars).length > 0;
|
|
1948
|
+
if (!(themeOverride || hasVars)) return void 0;
|
|
1949
|
+
return { ...themeOverride, ...styleVars };
|
|
1950
|
+
}, [themeOverride, pageGeometryProp, fit, surfaceAspect]);
|
|
1792
1951
|
const resolvedMessages = useI18n({ locale, messages });
|
|
1793
1952
|
const effectiveSubtitle = subtitle ?? resolvedMessages.logoSubtitle;
|
|
1794
1953
|
const defaultLogo = /* @__PURE__ */ jsxs10("div", { className: "mejiro-reader-logo", children: [
|
|
@@ -1846,15 +2005,22 @@ function MejiroReaderInner(props, ref) {
|
|
|
1846
2005
|
const rightPage = currentSpread * 2 + 1;
|
|
1847
2006
|
const leftPage = currentSpread * 2 + 2;
|
|
1848
2007
|
const showLeft = spreadCtx.spread != null && leftPage <= spreadCtx.spread.totalPages;
|
|
2008
|
+
const showRightNum = pageNumbers === "both" || pageNumbers === "right";
|
|
2009
|
+
const showLeftNum = pageNumbers === "both" || pageNumbers === "left";
|
|
1849
2010
|
return /* @__PURE__ */ jsx12(MejiroI18nProvider, { messages: resolvedMessages, children: /* @__PURE__ */ jsxs10(
|
|
1850
2011
|
"div",
|
|
1851
2012
|
{
|
|
1852
|
-
className: `mejiro-reader${chromeHidden ? " mejiro-reader--chrome-hidden" : ""}`,
|
|
2013
|
+
className: `mejiro-reader${chromeHidden ? " mejiro-reader--chrome-hidden" : ""}${fit === "width" ? " mejiro-reader--fit-width" : ""}`,
|
|
1853
2014
|
"data-mejiro-theme": themeName,
|
|
1854
2015
|
style: themeStyle,
|
|
1855
2016
|
children: [
|
|
1856
2017
|
header,
|
|
1857
|
-
enableSettings && /* @__PURE__ */ jsx12(
|
|
2018
|
+
enableSettings && (renderSettings ? /* @__PURE__ */ jsx12("div", { className: `mejiro-reader-settings-panel${settingsOpen ? " is-open" : ""}`, children: /* @__PURE__ */ jsx12("div", { className: "mejiro-reader-settings-inner", children: /* @__PURE__ */ jsx12("div", { className: "mejiro-reader-settings-content", children: renderSettings({
|
|
2019
|
+
settings: editable,
|
|
2020
|
+
update: setOptions,
|
|
2021
|
+
open: settingsOpen,
|
|
2022
|
+
toggle: () => setSettingsOpen((v) => !v)
|
|
2023
|
+
}) }) }) }) : /* @__PURE__ */ jsx12(
|
|
1858
2024
|
MejiroSettingsPanel,
|
|
1859
2025
|
{
|
|
1860
2026
|
open: settingsOpen,
|
|
@@ -1862,7 +2028,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1862
2028
|
fonts,
|
|
1863
2029
|
onChange: setOptions
|
|
1864
2030
|
}
|
|
1865
|
-
),
|
|
2031
|
+
)),
|
|
1866
2032
|
/* @__PURE__ */ jsxs10("div", { className: `mejiro-reader-body${showChapterPanel ? " has-chapter-panel" : ""}`, children: [
|
|
1867
2033
|
showChapterPanel && /* @__PURE__ */ jsx12(MejiroChapterNav, { epub: e, chapter, onChange: onChapter, variant: "panel" }),
|
|
1868
2034
|
/* @__PURE__ */ jsxs10("div", { ref: surfaceRef, className: "mejiro-reader-surface", children: [
|
|
@@ -1900,10 +2066,13 @@ function MejiroReaderInner(props, ref) {
|
|
|
1900
2066
|
fontSize: bookOptions.fontSize,
|
|
1901
2067
|
lineSpacing: bookOptions.lineSpacing,
|
|
1902
2068
|
turning: spreadCtx.turning,
|
|
1903
|
-
rightHeader: {
|
|
2069
|
+
rightHeader: {
|
|
2070
|
+
title: runningTitleRight,
|
|
2071
|
+
pageNumber: showRightNum ? rightPage : null
|
|
2072
|
+
},
|
|
1904
2073
|
leftHeader: {
|
|
1905
2074
|
title: runningTitleLeft,
|
|
1906
|
-
pageNumber: showLeft ? leftPage : null
|
|
2075
|
+
pageNumber: showLeft && showLeftNum ? leftPage : null
|
|
1907
2076
|
},
|
|
1908
2077
|
images: imageCtx.currentImages,
|
|
1909
2078
|
indicator: enablePageIndicator ? /* @__PURE__ */ jsx12(
|
|
@@ -3618,7 +3787,7 @@ function useLibrary(options) {
|
|
|
3618
3787
|
}
|
|
3619
3788
|
|
|
3620
3789
|
// src/useManuscriptLayout.ts
|
|
3621
|
-
import { useCallback as useCallback15, useEffect as useEffect15, useLayoutEffect as
|
|
3790
|
+
import { useCallback as useCallback15, useEffect as useEffect15, useLayoutEffect as useLayoutEffect5, useRef as useRef18, useState as useState17 } from "react";
|
|
3622
3791
|
function useManuscriptLayout(book, chapter, surface, options = {}) {
|
|
3623
3792
|
const dialect = options.dialect ?? "mejiro";
|
|
3624
3793
|
const enableResize = options.enableResize ?? true;
|
|
@@ -3653,7 +3822,7 @@ function useManuscriptLayout(book, chapter, surface, options = {}) {
|
|
|
3653
3822
|
setLayout(next);
|
|
3654
3823
|
setElapsedMs(performance.now() - t0);
|
|
3655
3824
|
}, [book, chapter, dialect, surface]);
|
|
3656
|
-
|
|
3825
|
+
useLayoutEffect5(() => {
|
|
3657
3826
|
requestIdRef.current++;
|
|
3658
3827
|
setLayout(null);
|
|
3659
3828
|
setPageWidth(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libraz/mejiro-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React components and hooks for mejiro — vertical text rendering, EPUB reader, and editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"build": "tsup"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@libraz/mejiro": "^0.
|
|
49
|
+
"@libraz/mejiro": "^0.6.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@types/react": ">=18",
|